Skip to content

Commit

Permalink
engine: add job system thread count configuration (#7223)
Browse files Browse the repository at this point in the history
- Plumb Engine::Config in Java
 - Add Engine::Builder for Java
 - Add jobSystemThreadCount to Engine::Config

BUG=303129581
  • Loading branch information
poweifeng authored Oct 3, 2023
1 parent b4d6f97 commit df935b7
Show file tree
Hide file tree
Showing 6 changed files with 271 additions and 26 deletions.
3 changes: 3 additions & 0 deletions NEW_RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,6 @@ for next branch cut* header.
appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md).

## Release notes for next branch cut

engine: Added parameter for configuring JobSystem thread count
engine: In Java, decpreate Engine.create() and recommend Engine.Builder instead
57 changes: 48 additions & 9 deletions android/filament-android/src/main/cpp/Engine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,8 @@
using namespace filament;
using namespace utils;

extern "C" JNIEXPORT jlong JNICALL
Java_com_google_android_filament_Engine_nCreateEngine(JNIEnv*, jclass, jlong backend,
jlong sharedContext) {
return (jlong) Engine::create((Engine::Backend) backend, nullptr, (void*) sharedContext);
}

extern "C" JNIEXPORT void JNICALL
Java_com_google_android_filament_Engine_nDestroyEngine(JNIEnv*, jclass,
jlong nativeEngine) {
Java_com_google_android_filament_Engine_nDestroyEngine(JNIEnv*, jclass, jlong nativeEngine) {
Engine* engine = (Engine*) nativeEngine;
Engine::destroy(&engine);
}
Expand Down Expand Up @@ -454,4 +447,50 @@ Java_com_google_android_filament_Engine_nGetActiveFeatureLevel(JNIEnv *, jclass,
jlong nativeEngine) {
Engine* engine = (Engine*) nativeEngine;
return (jint)engine->getActiveFeatureLevel();
}
}

extern "C" JNIEXPORT jlong JNICALL Java_com_google_android_filament_Engine_nCreateBuilder(JNIEnv*,
jclass) {
Engine::Builder* builder = new Engine::Builder{};
return (jlong) builder;
}

extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Engine_nDestroyBuilder(JNIEnv*,
jclass, jlong nativeBuilder) {
Engine::Builder* builder = (Engine::Builder*) nativeBuilder;
delete builder;
}

extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Engine_nSetBuilderBackend(
JNIEnv*, jclass, jlong nativeBuilder, jlong backend) {
Engine::Builder* builder = (Engine::Builder*) nativeBuilder;
builder->backend((Engine::Backend) backend);
}

extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Engine_nSetBuilderConfig(JNIEnv*,
jclass, jlong nativeBuilder, jlong commandBufferSizeMB, jlong perRenderPassArenaSizeMB,
jlong driverHandleArenaSizeMB, jlong minCommandBufferSizeMB, jlong perFrameCommandsSizeMB,
jlong jobSystemThreadCount) {
Engine::Builder* builder = (Engine::Builder*) nativeBuilder;
Engine::Config config = {
.commandBufferSizeMB = (uint32_t) commandBufferSizeMB,
.perRenderPassArenaSizeMB = (uint32_t) perRenderPassArenaSizeMB,
.driverHandleArenaSizeMB = (uint32_t) driverHandleArenaSizeMB,
.minCommandBufferSizeMB = (uint32_t) minCommandBufferSizeMB,
.perFrameCommandsSizeMB = (uint32_t) perFrameCommandsSizeMB,
.jobSystemThreadCount = (uint32_t) jobSystemThreadCount,
};
builder->config(&config);
}

extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Engine_nSetBuilderSharedContext(
JNIEnv*, jclass, jlong nativeBuilder, jlong sharedContext) {
Engine::Builder* builder = (Engine::Builder*) nativeBuilder;
builder->sharedContext((void*) sharedContext);
}

extern "C" JNIEXPORT jlong JNICALL
Java_com_google_android_filament_Engine_nBuilderBuild(JNIEnv*, jclass, jlong nativeBuilder) {
Engine::Builder* builder = (Engine::Builder*) nativeBuilder;
return (jlong) builder->build();
}
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,184 @@ public enum FeatureLevel {
FEATURE_LEVEL_2
};

/**
* Constructs <code>Engine</code> objects using a builder pattern.
*/
public static class Builder {
@SuppressWarnings({"FieldCanBeLocal", "UnusedDeclaration"})
private final BuilderFinalizer mFinalizer;
private final long mNativeBuilder;

public Builder() {
mNativeBuilder = nCreateBuilder();
mFinalizer = new BuilderFinalizer(mNativeBuilder);
}

/**
* Sets the {@link Backend} for the Engine.
*
* @param backend Driver backend to use
* @return A reference to this Builder for chaining calls.
*/
Builder backend(Backend backend) {
nSetBuilderBackend(mNativeBuilder, backend.ordinal());
return this;
}

/**
* Sets a sharedContext for the Engine.
*
* @param sharedContext A platform-dependant OpenGL context used as a shared context
* when creating filament's internal context. On Android this parameter
* <b>must be</b> an instance of {@link android.opengl.EGLContext}.
* @return A reference to this Builder for chaining calls.
*/
Builder sharedContext(Object sharedContext) {
if (Platform.get().validateSharedContext(sharedContext)) {
nSetBuilderSharedContext(mNativeBuilder,
Platform.get().getSharedContextNativeHandle(sharedContext));
return this;
}
throw new IllegalArgumentException("Invalid shared context " + sharedContext);
}

/**
* Configure the Engine with custom parameters.
*
* @param config A {@link Config} object
* @return A reference to this Builder for chaining calls.
*/
Builder config(Config config) {
nSetBuilderConfig(mNativeBuilder, config.commandBufferSizeMB,
config.perRenderPassArenaSizeMB, config.driverHandleArenaSizeMB,
config.minCommandBufferSizeMB, config.perFrameCommandsSizeMB,
config.jobSystemThreadCount);
return this;
}

/**
* Creates an instance of Engine
*
* @return A newly created <code>Engine</code>, or <code>null</code> if the GPU driver couldn't
* be initialized, for instance if it doesn't support the right version of OpenGL or
* OpenGL ES.
*
* @exception IllegalStateException can be thrown if there isn't enough memory to
* allocate the command buffer.
*/
Engine build() {
long nativeEngine = nBuilderBuild(mNativeBuilder);
if (nativeEngine == 0) throw new IllegalStateException("Couldn't create Engine");
return new Engine(nativeEngine);
}

private static class BuilderFinalizer {
private final long mNativeObject;

BuilderFinalizer(long nativeObject) {
mNativeObject = nativeObject;
}

@Override
public void finalize() {
try {
super.finalize();
} catch (Throwable t) { // Ignore
} finally {
nDestroyBuilder(mNativeObject);
}
}
}
}

/**
* Parameters for customizing the initialization of {@link Engine}.
*/
public static class Config {

// #defines in Engine.h
private static final long FILAMENT_PER_RENDER_PASS_ARENA_SIZE_IN_MB = 3;
private static final long FILAMENT_PER_FRAME_COMMANDS_SIZE_IN_MB = 2;
private static final long FILAMENT_MIN_COMMAND_BUFFERS_SIZE_IN_MB = 1;
private static final long FILAMENT_COMMAND_BUFFER_SIZE_IN_MB =
FILAMENT_MIN_COMMAND_BUFFERS_SIZE_IN_MB * 3;

/**
* Size in MiB of the low-level command buffer arena.
*
* Each new command buffer is allocated from here. If this buffer is too small the program
* might terminate or rendering errors might occur.
*
* This is typically set to minCommandBufferSizeMB * 3, so that up to 3 frames can be
* batched-up at once.
*
* This value affects the application's memory usage.
*/
public long commandBufferSizeMB = FILAMENT_COMMAND_BUFFER_SIZE_IN_MB;

/**
* Size in MiB of the per-frame data arena.
*
* This is the main arena used for allocations when preparing a frame.
* e.g.: Froxel data and high-level commands are allocated from this arena.
*
* If this size is too small, the program will abort on debug builds and have undefined
* behavior otherwise.
*
* This value affects the application's memory usage.
*/
public long perRenderPassArenaSizeMB = FILAMENT_PER_RENDER_PASS_ARENA_SIZE_IN_MB;

/**
* Size in MiB of the backend's handle arena.
*
* Backends will fallback to slower heap-based allocations when running out of space and
* log this condition.
*
* If 0, then the default value for the given platform is used
*
* This value affects the application's memory usage.
*/
public long driverHandleArenaSizeMB = 0;

/**
* Minimum size in MiB of a low-level command buffer.
*
* This is how much space is guaranteed to be available for low-level commands when a new
* buffer is allocated. If this is too small, the engine might have to stall to wait for
* more space to become available, this situation is logged.
*
* This value does not affect the application's memory usage.
*/
public long minCommandBufferSizeMB = FILAMENT_MIN_COMMAND_BUFFERS_SIZE_IN_MB;

/**
* Size in MiB of the per-frame high level command buffer.
*
* This buffer is related to the number of draw calls achievable within a frame, if it is
* too small, the program will abort on debug builds and have undefined behavior otherwise.
*
* It is allocated from the 'per-render-pass arena' above. Make sure that at least 1 MiB is
* left in the per-render-pass arena when deciding the size of this buffer.
*
* This value does not affect the application's memory usage.
*/
public long perFrameCommandsSizeMB = FILAMENT_PER_FRAME_COMMANDS_SIZE_IN_MB;

/**
* Number of threads to use in Engine's JobSystem.
*
* Engine uses a utils::JobSystem to carry out paralleization of Engine workloads. This
* value sets the number of threads allocated for JobSystem. Configuring this value can be
* helpful in CPU-constrained environments where too many threads can cause contention of
* CPU and reduce performance.
*
* The default value is 0, which implies that the Engine will use a heuristic to determine
* the number of threads to use.
*/
public long jobSystemThreadCount = 0;
}

private Engine(long nativeEngine) {
mNativeObject = nativeEngine;
mTransformManager = new TransformManager(nGetTransformManager(nativeEngine));
Expand All @@ -174,12 +352,12 @@ private Engine(long nativeEngine) {
* @exception IllegalStateException can be thrown if there isn't enough memory to
* allocate the command buffer.
*
* @deprecated use {@link Builder}
*/
@Deprecated
@NonNull
public static Engine create() {
long nativeEngine = nCreateEngine(0, 0);
if (nativeEngine == 0) throw new IllegalStateException("Couldn't create Engine");
return new Engine(nativeEngine);
return new Builder().build();
}

/**
Expand All @@ -196,12 +374,14 @@ public static Engine create() {
* @exception IllegalStateException can be thrown if there isn't enough memory to
* allocate the command buffer.
*
* @deprecated use {@link Builder}
*/
@Deprecated
@NonNull
public static Engine create(@NonNull Backend backend) {
long nativeEngine = nCreateEngine(backend.ordinal(), 0);
if (nativeEngine == 0) throw new IllegalStateException("Couldn't create Engine");
return new Engine(nativeEngine);
return new Builder()
.backend(backend)
.build();
}

/**
Expand All @@ -220,16 +400,14 @@ public static Engine create(@NonNull Backend backend) {
* @exception IllegalStateException can be thrown if there isn't enough memory to
* allocate the command buffer.
*
* @deprecated use {@link Builder}
*/
@Deprecated
@NonNull
public static Engine create(@NonNull Object sharedContext) {
if (Platform.get().validateSharedContext(sharedContext)) {
long nativeEngine = nCreateEngine(0,
Platform.get().getSharedContextNativeHandle(sharedContext));
if (nativeEngine == 0) throw new IllegalStateException("Couldn't create Engine");
return new Engine(nativeEngine);
}
throw new IllegalArgumentException("Invalid shared context " + sharedContext);
return new Builder()
.sharedContext(sharedContext)
.build();
}

/**
Expand Down Expand Up @@ -914,7 +1092,6 @@ private static void assertDestroy(boolean success) {
}
}

private static native long nCreateEngine(long backend, long sharedContext);
private static native void nDestroyEngine(long nativeEngine);
private static native long nGetBackend(long nativeEngine);
private static native long nCreateSwapChain(long nativeEngine, Object nativeWindow, long flags);
Expand Down Expand Up @@ -971,4 +1148,13 @@ private static void assertDestroy(boolean success) {
private static native int nGetSupportedFeatureLevel(long nativeEngine);
private static native int nSetActiveFeatureLevel(long nativeEngine, int ordinal);
private static native int nGetActiveFeatureLevel(long nativeEngine);

private static native long nCreateBuilder();
private static native void nDestroyBuilder(long nativeBuilder);
private static native void nSetBuilderBackend(long nativeBuilder, long backend);
private static native void nSetBuilderConfig(long nativeBuilder, long commandBufferSizeMB,
long perRenderPassArenaSizeMB, long driverHandleArenaSizeMB,
long minCommandBufferSizeMB, long perFrameCommandsSizeMB, long jobSystemThreadCount);
private static native void nSetBuilderSharedContext(long nativeBuilder, long sharedContext);
private static native long nBuilderBuild(long nativeBuilder);
}
13 changes: 13 additions & 0 deletions filament/include/filament/Engine.h
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,19 @@ class UTILS_PUBLIC Engine {
* This value does not affect the application's memory usage.
*/
uint32_t perFrameCommandsSizeMB = FILAMENT_PER_FRAME_COMMANDS_SIZE_IN_MB;

/**
* Number of threads to use in Engine's JobSystem.
*
* Engine uses a utils::JobSystem to carry out paralleization of Engine workloads. This
* value sets the number of threads allocated for JobSystem. Configuring this value can be
* helpful in CPU-constrained environments where too many threads can cause contention of
* CPU and reduce performance.
*
* The default value is 0, which implies that the Engine will use a heuristic to determine
* the number of threads to use.
*/
uint32_t jobSystemThreadCount = 0;
};


Expand Down
8 changes: 6 additions & 2 deletions filament/src/details/Engine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ FEngine::FEngine(Engine::Builder const& builder) :
"FEngine::mPerRenderPassAllocator",
builder->mConfig.perRenderPassArenaSizeMB * MiB),
mHeapAllocator("FEngine::mHeapAllocator", AreaPolicy::NullArea{}),
mJobSystem(getJobSystemThreadPoolSize()),
mJobSystem(getJobSystemThreadPoolSize(builder->mConfig)),
mEngineEpoch(std::chrono::steady_clock::now()),
mDriverBarrier(1),
mMainThreadId(ThreadUtils::getThreadId()),
Expand All @@ -214,7 +214,11 @@ FEngine::FEngine(Engine::Builder const& builder) :
<< "(threading is " << (UTILS_HAS_THREADING ? "enabled)" : "disabled)") << io::endl;
}

uint32_t FEngine::getJobSystemThreadPoolSize() noexcept {
uint32_t FEngine::getJobSystemThreadPoolSize(Engine::Config const& config) noexcept {
if (config.jobSystemThreadCount > 0) {
return config.jobSystemThreadCount;
}

// 1 thread for the user, 1 thread for the backend
int threadCount = (int)std::thread::hardware_concurrency() - 2;
// make sure we have at least 1 thread though
Expand Down
2 changes: 1 addition & 1 deletion filament/src/details/Engine.h
Original file line number Diff line number Diff line change
Expand Up @@ -508,7 +508,7 @@ class FEngine : public Engine {
HeapAllocatorArena mHeapAllocator;

utils::JobSystem mJobSystem;
static uint32_t getJobSystemThreadPoolSize() noexcept;
static uint32_t getJobSystemThreadPoolSize(Engine::Config const& config) noexcept;

std::default_random_engine mRandomEngine;

Expand Down

0 comments on commit df935b7

Please sign in to comment.