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;