diff --git a/android/filament-android/src/main/cpp/Material.cpp b/android/filament-android/src/main/cpp/Material.cpp index 6a3abb34b4..4a901efeec 100644 --- a/android/filament-android/src/main/cpp/Material.cpp +++ b/android/filament-android/src/main/cpp/Material.cpp @@ -25,7 +25,7 @@ using namespace filament; extern "C" JNIEXPORT jlong JNICALL Java_com_google_android_filament_Material_nBuilderBuild(JNIEnv *env, jclass, - jlong nativeEngine, jobject buffer_, jint size, jint shBandCount, jint shadowQuality) { + jlong nativeEngine, jobject buffer_, jint size, jint shBandCount, jint shadowQuality, jint uboBatchingMode) { Engine* engine = (Engine*) nativeEngine; AutoBuffer buffer(env, buffer_, size); auto builder = Material::Builder(); @@ -33,6 +33,7 @@ Java_com_google_android_filament_Material_nBuilderBuild(JNIEnv *env, jclass, builder.sphericalHarmonicsBandCount(shBandCount); } builder.shadowSamplingQuality((Material::Builder::ShadowSamplingQuality)shadowQuality); + builder.uboBatching((Material::UboBatchingMode)uboBatchingMode); Material* material = builder .package(buffer.getData(), buffer.getSize()) .build(*engine); diff --git a/android/filament-android/src/main/java/com/google/android/filament/Material.java b/android/filament-android/src/main/java/com/google/android/filament/Material.java index afa968f88b..4578706a5d 100644 --- a/android/filament-android/src/main/java/com/google/android/filament/Material.java +++ b/android/filament-android/src/main/java/com/google/android/filament/Material.java @@ -261,6 +261,20 @@ public class Material { LOW } + /** + * Defines whether a material instance should use UBO batching or not. + */ + public enum UboBatchingMode { + /** + * For default, it follows the engine settings. + * If UBO batching is enabled on the engine and the material domain is SURFACE, it + * turns on the UBO batching. Otherwise, it turns off the UBO batching. + */ + DEFAULT, + /** Disable the Ubo Batching for this material */ + DISABLED + } + public static class UserVariantFilterBit { /** Directional lighting */ public static int DIRECTIONAL_LIGHTING = 0x01; @@ -372,6 +386,7 @@ public class Material { private int mSize; private int mShBandCount = 0; private ShadowSamplingQuality mShadowSamplingQuality = ShadowSamplingQuality.LOW; + private UboBatchingMode mUboBatchingMode = UboBatchingMode.DEFAULT; /** @@ -416,6 +431,17 @@ public class Material { return this; } + /** + * Set the batching mode of the instances created from this material. + * @param uboBatchingMode + * @return Reference to this Builder for chaining calls. + */ + @NonNull + public Builder uboBatching(UboBatchingMode mode) { + mUboBatchingMode = mode; + return this; + } + /** * Creates and returns the Material object. * @@ -428,7 +454,7 @@ public class Material { @NonNull public Material build(@NonNull Engine engine) { long nativeMaterial = nBuilderBuild(engine.getNativeObject(), - mBuffer, mSize, mShBandCount, mShadowSamplingQuality.ordinal()); + mBuffer, mSize, mShBandCount, mShadowSamplingQuality.ordinal(), mUboBatchingMode.ordinal()); if (nativeMaterial == 0) throw new IllegalStateException("Couldn't create Material"); return new Material(nativeMaterial); } @@ -1094,7 +1120,7 @@ public class Material { mNativeObject = 0; } - private static native long nBuilderBuild(long nativeEngine, @NonNull Buffer buffer, int size, int shBandCount, int shadowQuality); + private static native long nBuilderBuild(long nativeEngine, @NonNull Buffer buffer, int size, int shBandCount, int shadowQuality, int uboBatchingMode); private static native long nCreateInstance(long nativeMaterial); private static native long nCreateInstanceWithName(long nativeMaterial, @NonNull String name); private static native long nGetDefaultInstance(long nativeMaterial); diff --git a/filament/include/filament/Material.h b/filament/include/filament/Material.h index c9151f5cd3..c179da048f 100644 --- a/filament/include/filament/Material.h +++ b/filament/include/filament/Material.h @@ -69,6 +69,20 @@ public: using ShaderModel = backend::ShaderModel; using SubpassType = backend::SubpassType; + /** + * Defines whether a material instance should use UBO batching or not. + */ + enum class UboBatchingMode { + /** + * For default, it follows the engine settings. + * If UBO batching is enabled on the engine and the material domain is SURFACE, it + * turns on the UBO batching. Otherwise, it turns off the UBO batching. + */ + DEFAULT, + //! Disable the Ubo Batching for this material + DISABLED, + }; + /** * Holds information about a material parameter. */ @@ -165,6 +179,13 @@ public: */ Builder& shadowSamplingQuality(ShadowSamplingQuality quality) noexcept; + /** + * Set the batching mode of the instances created from this material. + * @param uboBatchingMode + * @return + */ + Builder& uboBatching(UboBatchingMode uboBatchingMode) noexcept; + /** * Creates the Material object and returns a pointer to it. * diff --git a/filament/src/details/Engine.cpp b/filament/src/details/Engine.cpp index 5710fb64b9..0697ac2e2f 100644 --- a/filament/src/details/Engine.cpp +++ b/filament/src/details/Engine.cpp @@ -990,10 +990,9 @@ FMaterialInstance* FEngine::createMaterialInstance(const FMaterial* material, return p; } -FMaterialInstance* FEngine::createMaterialInstance(const FMaterial* material, const char* name, - UboBatchingMode batchingMode) noexcept { - FMaterialInstance* p = - mHeapAllocator.make(*this, material, name, batchingMode); +FMaterialInstance* FEngine::createMaterialInstance(const FMaterial* material, + const char* name) noexcept { + FMaterialInstance* p = mHeapAllocator.make(*this, material, name); if (UTILS_LIKELY(p)) { auto pos = mMaterialInstances.emplace(material, "MaterialInstance"); pos.first->second.insert(p); diff --git a/filament/src/details/Engine.h b/filament/src/details/Engine.h index 83c731f324..34d0550042 100644 --- a/filament/src/details/Engine.h +++ b/filament/src/details/Engine.h @@ -345,21 +345,10 @@ public: FRenderer* createRenderer() noexcept; - // Defines whether a material instance should use UBO batching or not. - enum class UboBatchingMode { - // For default, it follows the engine settings. - // If UBO batching is enabled on the engine and the material domain is not SURFACE, it - // turns on the UBO batching. Otherwise, it turns off the UBO batching. - DEFAULT, - NO_UBO_BATCHING, - UBO_BATCHING - }; - FMaterialInstance* createMaterialInstance(const FMaterial* material, const FMaterialInstance* other, const char* name) noexcept; - FMaterialInstance* createMaterialInstance(const FMaterial* material, const char* name, - UboBatchingMode batchingMode) noexcept; + FMaterialInstance* createMaterialInstance(const FMaterial* material, const char* name) noexcept; FScene* createScene() noexcept; FView* createView() noexcept; diff --git a/filament/src/details/Material.cpp b/filament/src/details/Material.cpp index 5667e4067e..a58f320509 100644 --- a/filament/src/details/Material.cpp +++ b/filament/src/details/Material.cpp @@ -70,10 +70,19 @@ namespace filament { +namespace { + using namespace backend; using namespace filaflat; using namespace utils; -using UboBatchingMode = FEngine::UboBatchingMode; +using UboBatchingMode = Material::UboBatchingMode; + +bool shouldEnableBatching(FEngine& engine, UboBatchingMode batchingMode, MaterialDomain domain) { + return batchingMode != UboBatchingMode::DISABLED && engine.isUboBatchingEnabled() && + domain == MaterialDomain::SURFACE; +} + +} // anonymous namespace struct Material::BuilderDetails { const void* mPayload = nullptr; @@ -81,6 +90,7 @@ struct Material::BuilderDetails { bool mDefaultMaterial = false; int32_t mShBandsCount = 3; Builder::ShadowSamplingQuality mShadowSamplingQuality = Builder::ShadowSamplingQuality::LOW; + UboBatchingMode mUboBatchingMode = UboBatchingMode::DEFAULT; std::unordered_map< CString, std::variant, @@ -115,6 +125,11 @@ Material::Builder& Material::Builder::shadowSamplingQuality(ShadowSamplingQualit return *this; } +Material::Builder& Material::Builder::uboBatching(UboBatchingMode const mode) noexcept { + mImpl->mUboBatchingMode = mode; + return *this; +} + template Material::Builder& Material::Builder::constant(const char* name, size_t nameLength, T value) { FILAMENT_CHECK_PRECONDITION(name != nullptr) << "name cannot be null"; @@ -148,8 +163,12 @@ Material* Material::Builder::build(Engine& engine) const { FMaterial::FMaterial(FEngine& engine, const Builder& builder, MaterialDefinition const& definition) : mDefinition(definition), mIsDefaultMaterial(builder->mDefaultMaterial), + mUseUboBatching(shouldEnableBatching(engine, builder->mUboBatchingMode, + definition.materialDomain)), mEngine(engine), mMaterialId(engine.getMaterialId()) { + FILAMENT_CHECK_PRECONDITION(!mUseUboBatching || engine.isUboBatchingEnabled()) + << "UBO batching is not enabled."; mSpecializationConstants = processSpecializationConstants(builder); precacheDepthVariants(engine); @@ -282,14 +301,14 @@ FMaterialInstance* FMaterial::createInstance(const char* name) const noexcept { return FMaterialInstance::duplicate(mDefaultMaterialInstance, name); } else { // but if we don't, just create an instance with all the default parameters - return mEngine.createMaterialInstance(this, name, UboBatchingMode::DEFAULT); + return mEngine.createMaterialInstance(this, name); } } FMaterialInstance* FMaterial::getDefaultInstance() noexcept { if (UTILS_UNLIKELY(!mDefaultMaterialInstance)) { mDefaultMaterialInstance = - mEngine.createMaterialInstance(this, mDefinition.name.c_str(), UboBatchingMode::DEFAULT); + mEngine.createMaterialInstance(this, mDefinition.name.c_str()); mDefaultMaterialInstance->setDefaultInstance(true); } return mDefaultMaterialInstance; diff --git a/filament/src/details/Material.h b/filament/src/details/Material.h index 6683f42572..318b3596db 100644 --- a/filament/src/details/Material.h +++ b/filament/src/details/Material.h @@ -248,6 +248,10 @@ public: return mDefinition.perViewLayoutIndex; } + bool useUboBatching() const noexcept { + return mUseUboBatching; + } + #if FILAMENT_ENABLE_MATDBG void applyPendingEdits() noexcept; @@ -310,6 +314,8 @@ private: bool mIsDefaultMaterial = false; + bool mUseUboBatching = false; + // reserve some space to construct the default material instance mutable FMaterialInstance* mDefaultMaterialInstance = nullptr; diff --git a/filament/src/details/MaterialInstance.cpp b/filament/src/details/MaterialInstance.cpp index 1c117887ff..015af420dc 100644 --- a/filament/src/details/MaterialInstance.cpp +++ b/filament/src/details/MaterialInstance.cpp @@ -58,18 +58,8 @@ using namespace utils; namespace filament { using namespace backend; -using UboBatchingMode = FEngine::UboBatchingMode; -bool shouldEnableBatching(FEngine& engine, UboBatchingMode batchingMode, MaterialDomain domain) { - if (batchingMode != UboBatchingMode::DEFAULT) { - return batchingMode == UboBatchingMode::UBO_BATCHING; - } - - return engine.isUboBatchingEnabled() && domain == MaterialDomain::SURFACE; -} - -FMaterialInstance::FMaterialInstance(FEngine& engine, FMaterial const* material, const char* name, - UboBatchingMode batchingMode) noexcept +FMaterialInstance::FMaterialInstance(FEngine& engine, FMaterial const* material, const char* name) noexcept : mMaterial(material), mDescriptorSet("MaterialInstance", material->getDescriptorSetLayout()), mCulling(CullingMode::BACK), @@ -80,12 +70,9 @@ FMaterialInstance::FMaterialInstance(FEngine& engine, FMaterial const* material, mHasScissor(false), mIsDoubleSided(false), mIsDefaultInstance(false), - mUseUboBatching( - shouldEnableBatching(engine, batchingMode, material->getMaterialDomain())), + mUseUboBatching(material->useUboBatching()), mTransparencyMode(TransparencyMode::DEFAULT), mName(name ? CString(name) : material->getName()) { - FILAMENT_CHECK_PRECONDITION(!mUseUboBatching || engine.isUboBatchingEnabled()) - << "UBO batching is not enabled."; FEngine::DriverApi& driver = engine.getDriverApi(); // even if the material doesn't have any parameters, we allocate a small UBO because it's @@ -160,7 +147,6 @@ FMaterialInstance::FMaterialInstance(FEngine& engine, mUseUboBatching(other->mUseUboBatching), mScissorRect(other->mScissorRect), mName(name ? CString(name) : other->mName) { - assert_invariant(!mUseUboBatching || engine.isUboBatchingEnabled()); FEngine::DriverApi& driver = engine.getDriverApi(); FMaterial const* const material = other->getMaterial(); @@ -232,8 +218,7 @@ void FMaterialInstance::commit(FEngine& engine) const { void FMaterialInstance::commit(FEngine::DriverApi& driver, UboManager* uboManager) const { if (mUniforms.isDirty()) { mUniforms.clean(); - if (mUseUboBatching) { - assert_invariant(uboManager != nullptr); + if (isUsingUboBatching()) { if (!BufferAllocator::isValid(getAllocationId())) { // The allocation hasn't happened yet, return. return; @@ -264,7 +249,7 @@ void FMaterialInstance::commit(FEngine::DriverApi& driver, UboManager* uboManage // TODO: eventually we should remove this in RELEASE builds fixMissingSamplers(); - if (mUseUboBatching && !BufferAllocator::isValid(getAllocationId())) { + if (isUsingUboBatching() && !BufferAllocator::isValid(getAllocationId())) { return; } @@ -414,7 +399,7 @@ void FMaterialInstance::use(FEngine::DriverApi& driver, Variant variant) const { return; } - if (mUseUboBatching && !BufferAllocator::isValid(getAllocationId())) { + if (isUsingUboBatching() && !BufferAllocator::isValid(getAllocationId())) { return; } @@ -451,7 +436,7 @@ void FMaterialInstance::assignUboAllocation( const Handle& ubHandle, BufferAllocator::AllocationId id, BufferAllocator::allocation_size_t offset) { - assert_invariant(mUseUboBatching); + assert_invariant(isUsingUboBatching()); mUboData = id; mUboOffset = offset; diff --git a/filament/src/details/MaterialInstance.h b/filament/src/details/MaterialInstance.h index 7f6d917323..d8d299c8e3 100644 --- a/filament/src/details/MaterialInstance.h +++ b/filament/src/details/MaterialInstance.h @@ -56,8 +56,7 @@ class FTexture; class FMaterialInstance : public MaterialInstance { public: - FMaterialInstance(FEngine& engine, FMaterial const* material, const char* name, - FEngine::UboBatchingMode batchingMode) noexcept; + FMaterialInstance(FEngine& engine, FMaterial const* material, const char* name) noexcept; FMaterialInstance(FEngine& engine, FMaterialInstance const* other, const char* name); FMaterialInstance(const FMaterialInstance& rhs) = delete; FMaterialInstance& operator=(const FMaterialInstance& rhs) = delete; diff --git a/web/filament-js/extensions.js b/web/filament-js/extensions.js index 36d0bf988b..b1799a8056 100644 --- a/web/filament-js/extensions.js +++ b/web/filament-js/extensions.js @@ -125,10 +125,12 @@ Filament.loadClassExtensions = function() { /// createMaterial ::method:: /// package ::argument:: asset string, or Uint8Array, or [Buffer] with filamat contents + /// options ::argument:: optional dictionary with `uboBatching` key. /// ::retval:: an instance of [createMaterial] - Filament.Engine.prototype.createMaterial = function(buffer) { + Filament.Engine.prototype.createMaterial = function(buffer, options) { buffer = getBufferDescriptor(buffer); - const result = this._createMaterial(buffer); + const uboBatching = (options && options.uboBatching) || Filament.Material$UboBatchingMode.DEFAULT; // The default is DEFAULT + const result = this._createMaterial(buffer, uboBatching); buffer.delete(); return result; }; diff --git a/web/filament-js/filament.d.ts b/web/filament-js/filament.d.ts index adf0863952..fdcbecce8f 100644 --- a/web/filament-js/filament.d.ts +++ b/web/filament-js/filament.d.ts @@ -375,6 +375,10 @@ export class Material { public getName(): string; } +export enum Material$UboBatchingMode { + DISABLED, + DEFAULT, +} export class Frustum { constructor(pv: mat4); public setProjection(pv: mat4): void; @@ -542,7 +546,7 @@ export class Engine { public static destroy(engine: Engine): void; public execute(): void; public createCamera(entity: Entity): Camera; - public createMaterial(urlOrBuffer: BufferReference): Material; + public createMaterial(urlOrBuffer: BufferReference, options?: { uboBatching?: Material$UboBatchingMode }): Material; public createRenderer(): Renderer; public createScene(): Scene; public createSwapChain(): SwapChain; diff --git a/web/filament-js/jsbindings.cpp b/web/filament-js/jsbindings.cpp index 52ce386b64..5d8a408566 100644 --- a/web/filament-js/jsbindings.cpp +++ b/web/filament-js/jsbindings.cpp @@ -495,8 +495,11 @@ class_("Engine") (Engine* engine, utils::Entity camera) { engine->destroyCameraComponent(camera); }, allow_raw_pointers()) - .function("_createMaterial", EMBIND_LAMBDA(Material*, (Engine* engine, BufferDescriptor mbd), { - return Material::Builder().package(mbd.bd->buffer, mbd.bd->size).build(*engine); + .function("_createMaterial", EMBIND_LAMBDA(Material*, (Engine* engine, BufferDescriptor mbd, Material::UboBatchingMode uboBatching), { + return Material::Builder() + .package(mbd.bd->buffer, mbd.bd->size) + .uboBatching(uboBatching) + .build(*engine); }), allow_raw_pointers()) /// destroyMaterial ::method:: /// material ::argument:: an instance of [Material] @@ -1360,6 +1363,10 @@ class_("Material") return transformName ? std::string(transformName) : std::string(); }), allow_raw_pointers()); +enum_("Material$UboBatchingMode") + .value("DISABLED", Material::UboBatchingMode::DISABLED) + .value("DEFAULT", Material::UboBatchingMode::DEFAULT); + class_("MaterialInstance") .function("getName", EMBIND_LAMBDA(std::string, (MaterialInstance* self), { return std::string(self->getName());