From df935b75e5bdc9ba8de82ee8268d0a72592155d9 Mon Sep 17 00:00:00 2001 From: Powei Feng Date: Tue, 3 Oct 2023 15:22:46 -0700 Subject: [PATCH] engine: add job system thread count configuration (#7223) - Plumb Engine::Config in Java - Add Engine::Builder for Java - Add jobSystemThreadCount to Engine::Config BUG=303129581 --- NEW_RELEASE_NOTES.md | 3 + .../filament-android/src/main/cpp/Engine.cpp | 57 ++++- .../com/google/android/filament/Engine.java | 214 ++++++++++++++++-- filament/include/filament/Engine.h | 13 ++ filament/src/details/Engine.cpp | 8 +- filament/src/details/Engine.h | 2 +- 6 files changed, 271 insertions(+), 26 deletions(-) diff --git a/NEW_RELEASE_NOTES.md b/NEW_RELEASE_NOTES.md index 4a1a9c7fa7..4a42fb94ef 100644 --- a/NEW_RELEASE_NOTES.md +++ b/NEW_RELEASE_NOTES.md @@ -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 \ No newline at end of file diff --git a/android/filament-android/src/main/cpp/Engine.cpp b/android/filament-android/src/main/cpp/Engine.cpp index e530e7bb55..e5bb4e95fa 100644 --- a/android/filament-android/src/main/cpp/Engine.cpp +++ b/android/filament-android/src/main/cpp/Engine.cpp @@ -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); } @@ -454,4 +447,50 @@ Java_com_google_android_filament_Engine_nGetActiveFeatureLevel(JNIEnv *, jclass, jlong nativeEngine) { Engine* engine = (Engine*) nativeEngine; return (jint)engine->getActiveFeatureLevel(); -} \ No newline at end of file +} + +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(); +} diff --git a/android/filament-android/src/main/java/com/google/android/filament/Engine.java b/android/filament-android/src/main/java/com/google/android/filament/Engine.java index e6bc4d9243..291200adca 100644 --- a/android/filament-android/src/main/java/com/google/android/filament/Engine.java +++ b/android/filament-android/src/main/java/com/google/android/filament/Engine.java @@ -154,6 +154,184 @@ public class Engine { FEATURE_LEVEL_2 }; + /** + * Constructs Engine 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 + * must be 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 Engine, or null 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)); @@ -174,12 +352,12 @@ public class Engine { * @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(); } /** @@ -196,12 +374,14 @@ public class Engine { * @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(); } /** @@ -220,16 +400,14 @@ public class Engine { * @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(); } /** @@ -914,7 +1092,6 @@ public class Engine { } } - 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); @@ -971,4 +1148,13 @@ public class Engine { 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); } diff --git a/filament/include/filament/Engine.h b/filament/include/filament/Engine.h index f4173f2614..a7b4e3c048 100644 --- a/filament/include/filament/Engine.h +++ b/filament/include/filament/Engine.h @@ -267,6 +267,19 @@ public: * 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; }; diff --git a/filament/src/details/Engine.cpp b/filament/src/details/Engine.cpp index 3e50fc7135..2b33a564ec 100644 --- a/filament/src/details/Engine.cpp +++ b/filament/src/details/Engine.cpp @@ -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()), @@ -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 diff --git a/filament/src/details/Engine.h b/filament/src/details/Engine.h index c690619378..bca0346001 100644 --- a/filament/src/details/Engine.h +++ b/filament/src/details/Engine.h @@ -508,7 +508,7 @@ private: HeapAllocatorArena mHeapAllocator; utils::JobSystem mJobSystem; - static uint32_t getJobSystemThreadPoolSize() noexcept; + static uint32_t getJobSystemThreadPoolSize(Engine::Config const& config) noexcept; std::default_random_engine mRandomEngine;