diff --git a/NEW_RELEASE_NOTES.md b/NEW_RELEASE_NOTES.md index f408ba3462..e9f88f5956 100644 --- a/NEW_RELEASE_NOTES.md +++ b/NEW_RELEASE_NOTES.md @@ -6,3 +6,5 @@ appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md). ## Release notes for next branch cut + +- engine: add `MaterialInstance::setConstant()` and `MaterialInstance::getConstant()` methods. These allow for per-material instance specialization constant overrides. diff --git a/android/filament-android/src/main/cpp/MaterialInstance.cpp b/android/filament-android/src/main/cpp/MaterialInstance.cpp index 48997326ea..2c4284a14d 100644 --- a/android/filament-android/src/main/cpp/MaterialInstance.cpp +++ b/android/filament-android/src/main/cpp/MaterialInstance.cpp @@ -246,6 +246,69 @@ Java_com_google_android_filament_MaterialInstance_nSetFloatParameterArray(JNIEnv env->ReleaseStringUTFChars(name_, name); } +extern "C" +JNIEXPORT jboolean JNICALL +Java_com_google_android_filament_MaterialInstance_nGetConstantBool(JNIEnv *env, jclass, + jlong nativeMaterialInstance, jstring name_) { + MaterialInstance* instance = (MaterialInstance*) nativeMaterialInstance; + const char *name = env->GetStringUTFChars(name_, 0); + jboolean result = instance->getConstant(name); + env->ReleaseStringUTFChars(name_, name); + return result; +} + +extern "C" +JNIEXPORT jfloat JNICALL +Java_com_google_android_filament_MaterialInstance_nGetConstantFloat(JNIEnv *env, jclass, + jlong nativeMaterialInstance, jstring name_) { + MaterialInstance* instance = (MaterialInstance*) nativeMaterialInstance; + const char *name = env->GetStringUTFChars(name_, 0); + jfloat result = instance->getConstant(name); + env->ReleaseStringUTFChars(name_, name); + return result; +} + +extern "C" +JNIEXPORT jint JNICALL +Java_com_google_android_filament_MaterialInstance_nGetConstantInt(JNIEnv *env, jclass, + jlong nativeMaterialInstance, jstring name_) { + MaterialInstance* instance = (MaterialInstance*) nativeMaterialInstance; + const char *name = env->GetStringUTFChars(name_, 0); + jint result = instance->getConstant(name); + env->ReleaseStringUTFChars(name_, name); + return result; +} + +extern "C" +JNIEXPORT void JNICALL +Java_com_google_android_filament_MaterialInstance_nSetConstantBool(JNIEnv *env, jclass, + jlong nativeMaterialInstance, jstring name_, jboolean x) { + MaterialInstance* instance = (MaterialInstance*) nativeMaterialInstance; + const char *name = env->GetStringUTFChars(name_, 0); + instance->setConstant(name, x); + env->ReleaseStringUTFChars(name_, name); +} + +extern "C" +JNIEXPORT void JNICALL +Java_com_google_android_filament_MaterialInstance_nSetConstantFloat(JNIEnv *env, jclass, + jlong nativeMaterialInstance, jstring name_, jfloat x) { + MaterialInstance* instance = (MaterialInstance*) nativeMaterialInstance; + const char *name = env->GetStringUTFChars(name_, 0); + instance->setConstant(name, x); + env->ReleaseStringUTFChars(name_, name); +} + +extern "C" +JNIEXPORT void JNICALL +Java_com_google_android_filament_MaterialInstance_nSetConstantInt(JNIEnv *env, jclass, + jlong nativeMaterialInstance, jstring name_, jint x) { + MaterialInstance* instance = (MaterialInstance*) nativeMaterialInstance; + const char *name = env->GetStringUTFChars(name_, 0); + instance->setConstant(name, x); + env->ReleaseStringUTFChars(name_, name); +} + // defined in TextureSampler.cpp namespace filament::JniUtils { TextureSampler from_long(jlong params) noexcept; diff --git a/android/filament-android/src/main/java/com/google/android/filament/MaterialInstance.java b/android/filament-android/src/main/java/com/google/android/filament/MaterialInstance.java index 72b4a171dd..7c8d5332a3 100644 --- a/android/filament-android/src/main/java/com/google/android/filament/MaterialInstance.java +++ b/android/filament-android/src/main/java/com/google/android/filament/MaterialInstance.java @@ -402,6 +402,69 @@ public class MaterialInstance { nSetParameterFloat4(getNativeObject(), name, color[0], color[1], color[2], color[3]); } + /** + * Overrides a specialization constant of this material instance. + * + * @param name The name of the constant as defined in the material. + * @param value The value of the constant. + * @see Material.Builder#constant + */ + public void setConstant(@NonNull String name, boolean value) { + nSetConstantBool(getNativeObject(), name, value); + } + + /** + * Overrides a specialization constant of this material instance. + * + * @param name The name of the constant as defined in the material. + * @param value The value of the constant. + * @see Material.Builder#constant + */ + public void setConstant(@NonNull String name, float value) { + nSetConstantFloat(getNativeObject(), name, value); + } + + /** + * Overrides a specialization constant of this material instance. + * + * @param name The name of the constant as defined in the material. + * @param value The value of the constant. + * @see Material.Builder#constant + */ + public void setConstant(@NonNull String name, int value) { + nSetConstantInt(getNativeObject(), name, value); + } + + /** + * Gets the value of a specialization constant by name. + * + * @param name The name of the constant as defined in the material. + * @return The value of the constant. + */ + public boolean getConstantBoolean(@NonNull String name) { + return nGetConstantBool(getNativeObject(), name); + } + + /** + * Gets the value of a specialization constant by name. + * + * @param name The name of the constant as defined in the material. + * @return The value of the constant. + */ + public float getConstantFloat(@NonNull String name) { + return nGetConstantFloat(getNativeObject(), name); + } + + /** + * Gets the value of a specialization constant by name. + * + * @param name The name of the constant as defined in the material. + * @return The value of the constant. + */ + public int getConstantInt(@NonNull String name) { + return nGetConstantInt(getNativeObject(), name); + } + /** * Set-up a custom scissor rectangle; by default it is disabled. * @@ -937,6 +1000,17 @@ public class MaterialInstance { @NonNull String name, int element, @NonNull @Size(min = 1) float[] v, @IntRange(from = 0) int offset, @IntRange(from = 1) int count); + private static native boolean nGetConstantBool(long nativeMaterialInstance, @NonNull String name); + private static native float nGetConstantFloat(long nativeMaterialInstance, @NonNull String name); + private static native int nGetConstantInt(long nativeMaterialInstance, @NonNull String name); + + private static native void nSetConstantBool(long nativeMaterialInstance, + @NonNull String name, boolean x); + private static native void nSetConstantFloat(long nativeMaterialInstance, + @NonNull String name, float x); + private static native void nSetConstantInt(long nativeMaterialInstance, + @NonNull String name, int x); + private static native void nSetParameterTexture(long nativeMaterialInstance, @NonNull String name, long nativeTexture, long sampler); diff --git a/filament/include/filament/MaterialInstance.h b/filament/include/filament/MaterialInstance.h index 74cc243203..c50e15352d 100644 --- a/filament/include/filament/MaterialInstance.h +++ b/filament/include/filament/MaterialInstance.h @@ -94,6 +94,12 @@ public: std::is_same_v >; + template + using is_supported_constant_parameter_t = std::enable_if_t< + std::is_same_v || + std::is_same_v || + std::is_same_v>; + /** * Creates a new MaterialInstance using another MaterialInstance as a template for initialization. * The new MaterialInstance is an instance of the same Material of the template instance and @@ -276,6 +282,54 @@ public: return getParameter(name, strlen(name)); } + /** + * Overrides a specialization constant of this material instance. + * + * @tparam T The type of the constant. Must be int32_t, float, or bool. + * @param name The name of the constant as defined in the material. Cannot be nullptr. + * @param nameLength Length in `char` of the name parameter. + * @param value The value of the constant. + * + * @see Material::Builder::constant + */ + template> + void setConstant(const char* UTILS_NONNULL name, size_t nameLength, T value); + + /** inline helper to provide the name as a null-terminated string literal */ + template> + void setConstant(StringLiteral const name, T value) { + setConstant(name.data, name.size, value); + } + + /** inline helper to provide the name as a null-terminated C string */ + template> + void setConstant(const char* UTILS_NONNULL name, T value) { + setConstant(name, strlen(name), value); + } + + /** + * Gets the value of a specialization constant by name. + * + * @tparam T The type of the constant. Must be int32_t, float, or bool. + * @param name The name of the constant as defined in the material. Cannot be nullptr. + * @param nameLength Length in `char` of the name parameter. + * @return The value of the constant. + */ + template> + T getConstant(const char* UTILS_NONNULL name, size_t nameLength) const; + + /** inline helper to provide the name as a null-terminated C string */ + template> + T getConstant(StringLiteral const name) const { + return getConstant(name.data, name.size); + } + + /** inline helper to provide the name as a null-terminated C string */ + template> + T getConstant(const char* UTILS_NONNULL name) const { + return getConstant(name, strlen(name)); + } + /** * Set-up a custom scissor rectangle; by default it is disabled. * diff --git a/filament/src/LocalProgramCache.cpp b/filament/src/LocalProgramCache.cpp index b79efaeb35..886b8cad74 100644 --- a/filament/src/LocalProgramCache.cpp +++ b/filament/src/LocalProgramCache.cpp @@ -176,15 +176,11 @@ Program::SpecializationConstant LocalProgramCache::getConstantImpl( std::string_view name) const noexcept { assert_invariant(mMaterial != nullptr); - MaterialDefinition const& definition = mMaterial->getDefinition(); - auto it = definition.specializationConstantsNameToIndex.find(name); - if (it != definition.specializationConstantsNameToIndex.cend()) { - return getConstantImpl(it->second + CONFIG_MAX_RESERVED_SPEC_CONSTANTS); - } + auto const& constants = mMaterial->getDefinition().specializationConstantsNameToIndex; + auto it = constants.find(name); + FILAMENT_CHECK_PRECONDITION(it != constants.end()) << "Constant " << name << " does not exist"; - std::string name_cstring(name); - PANIC_PRECONDITION("No such constant exists: %s", name_cstring.c_str()); - return {}; + return getConstantImpl(it->second + CONFIG_MAX_RESERVED_SPEC_CONSTANTS); } void LocalProgramCache::setConstants( @@ -234,6 +230,13 @@ void LocalProgramCache::setConstants( } } +void LocalProgramCache::setConstants( + FixedCapacityVector constants) noexcept { + assert_invariant(mMaterial != nullptr); + + setConstantsImpl(std::move(constants)); +} + void LocalProgramCache::setConstantsImpl( FixedCapacityVector constants) noexcept { FEngine& engine = mMaterial->getEngine(); diff --git a/filament/src/LocalProgramCache.h b/filament/src/LocalProgramCache.h index b216aa9445..ade3822802 100644 --- a/filament/src/LocalProgramCache.h +++ b/filament/src/LocalProgramCache.h @@ -119,7 +119,14 @@ public: std::pair> constants) noexcept; + // Set constants list directly. + void setConstants(utils::FixedCapacityVector + constants) noexcept; + private: + // Apply any pending specialization constants. Invalidates programs as necessary. + void flushConstants() const; + backend::Handle prepareProgramSlow(backend::DriverApi& driver, Variant const variant, backend::CompilerPriorityQueue const priorityQueue) const noexcept; diff --git a/filament/src/MaterialInstance.cpp b/filament/src/MaterialInstance.cpp index 59d2d0dc57..49904b3123 100644 --- a/filament/src/MaterialInstance.cpp +++ b/filament/src/MaterialInstance.cpp @@ -233,6 +233,26 @@ template UTILS_PUBLIC mat3f MaterialInstance::getParameter (const ch // ------------------------------------------------------------------------------------------------ +template +void MaterialInstance::setConstant(const char* name, size_t nameLength, T value) { + downcast(this)->setConstantImpl(std::string_view{name, nameLength}, value); +} + +template UTILS_PUBLIC void MaterialInstance::setConstant(const char* name, size_t nameLength, int32_t value); +template UTILS_PUBLIC void MaterialInstance::setConstant(const char* name, size_t nameLength, float value); +template UTILS_PUBLIC void MaterialInstance::setConstant(const char* name, size_t nameLength, bool value); + +template +T MaterialInstance::getConstant(const char* name, size_t nameLength) const { + return downcast(this)->getConstantImpl(std::string_view{name, nameLength}); +} + +template UTILS_PUBLIC int32_t MaterialInstance::getConstant(const char* name, size_t nameLength) const; +template UTILS_PUBLIC float MaterialInstance::getConstant(const char* name, size_t nameLength) const; +template UTILS_PUBLIC bool MaterialInstance::getConstant(const char* name, size_t nameLength) const; + +// ------------------------------------------------------------------------------------------------ + Material const* MaterialInstance::getMaterial() const noexcept { return downcast(this)->getMaterial(); } diff --git a/filament/src/PostProcessManager.cpp b/filament/src/PostProcessManager.cpp index 0ff6c13b35..7cf355ba28 100644 --- a/filament/src/PostProcessManager.cpp +++ b/filament/src/PostProcessManager.cpp @@ -189,7 +189,8 @@ FMaterial* PostProcessManager::PostProcessMaterial::getMaterial(FEngine& engine, if (UTILS_UNLIKELY(mSize)) { loadMaterial(engine); } - mMaterial->prepareProgram(driver, Variant{ variant }, CompilerPriorityQueue::CRITICAL); + mMaterial->getDefaultInstance()->prepareProgram(driver, Variant{ variant }, + CompilerPriorityQueue::CRITICAL); return mMaterial; } @@ -464,9 +465,10 @@ void PostProcessManager::unbindAllDescriptorSets(DriverApi& driver) noexcept { UTILS_NOINLINE PipelineState PostProcessManager::getPipelineState( - FMaterial const* const ma, Variant::type_t const variant) const noexcept { + FMaterialInstance const* const mi, Variant::type_t const variant) const noexcept { + FMaterial const* const ma = mi->getMaterial(); return { - .program = ma->getProgram(Variant{ variant }), + .program = mi->getProgram(Variant{ variant }), .vertexBufferInfo = mFullScreenQuadVbih, .pipelineLayout = { .setLayout = { @@ -474,7 +476,7 @@ PipelineState PostProcessManager::getPipelineState( mPerRenderableDslh, ma->getDescriptorSetLayout().getHandle() }}, - .rasterState = ma->getRasterState() + .rasterState = mi->getRasterState() }; } @@ -514,8 +516,7 @@ void PostProcessManager::commitAndRenderFullScreenQuad(DriverApi& driver, PostProcessVariant const variant) const noexcept { mi->commit(driver, getUboManager()); mi->use(driver); - FMaterial const* const ma = mi->getMaterial(); - PipelineState const pipeline = getPipelineState(ma, variant); + PipelineState const pipeline = getPipelineState(mi, variant); assert_invariant( ((out.params.readOnlyDepthStencil & RenderPassParams::READONLY_DEPTH) @@ -637,7 +638,7 @@ PostProcessManager::StructurePassOutput PostProcessManager::structure(FrameGraph // Only the depth texture is changing in the material instance (no UBO updates), // we do not move getMaterialInstance() inside the loop. - auto pipeline = getPipelineState(ma); + auto pipeline = getPipelineState(mi); // The first mip already exists, so we process n-1 lods for (size_t level = 0; level < levelCount - 1; level++) { @@ -1080,7 +1081,7 @@ FrameGraphId PostProcessManager::screenSpaceAmbientOcclusion( mi->commit(driver, getUboManager()); mi->use(driver); - auto pipeline = getPipelineState(ma); + auto pipeline = getPipelineState(mi); pipeline.rasterState.depthFunc = RasterState::DepthFunc::L; assert_invariant(ssao.params.readOnlyDepthStencil & RenderPassParams::READONLY_DEPTH); renderFullScreenQuad(ssao, pipeline, driver); @@ -1201,7 +1202,7 @@ FrameGraphId PostProcessManager::bilateralBlurPass(FrameGraph mi->commit(driver, getUboManager()); mi->use(driver); - auto pipeline = getPipelineState(ma); + auto pipeline = getPipelineState(mi); pipeline.rasterState.depthFunc = RasterState::DepthFunc::L; renderFullScreenQuad(blurred, pipeline, driver); unbindAllDescriptorSets(driver); @@ -1876,8 +1877,9 @@ FrameGraphId PostProcessManager::dof(FrameGraph& fg, auto const& material = getPostProcessMaterial("dofMipmap"); FMaterial const* const ma = material.getMaterial(mEngine, driver); + FMaterialInstance* const mi = getMaterialInstance(ma); - auto const pipeline = getPipelineState(ma, variant); + auto const pipeline = getPipelineState(mi, variant); for (size_t level = 0 ; level < mipmapCount - 1u ; level++) { const float w = FTexture::valueForLevel(level, desc.width); @@ -2404,7 +2406,7 @@ PostProcessManager::BloomPassOutput PostProcessManager::bloom(FrameGraph& fg, auto const& material = getPostProcessMaterial("bloomUpsample"); FMaterial const* const ma = material.getMaterial(mEngine, driver); - auto pipeline = getPipelineState(ma); + auto pipeline = getPipelineState(getMaterialInstance(ma)); pipeline.rasterState.blendFunctionSrcRGB = BlendFunction::ONE; pipeline.rasterState.blendFunctionDstRGB = BlendFunction::ONE; @@ -2549,7 +2551,7 @@ void PostProcessManager::colorGradingSubpass(DriverApi& driver, // the UBO has been set and committed in colorGradingPrepareSubpass() FMaterialInstance const* mi = mMaterialInstanceManager.getMaterialInstance(ma, colorGradingConfig.translucent); mi->use(driver); - auto const pipeline = getPipelineState(ma, variant); + auto const pipeline = getPipelineState(mi, variant); driver.nextSubpass(); driver.scissor(mi->getScissor()); driver.draw(pipeline, mFullScreenQuadRph, 0, 3, 1); @@ -2573,7 +2575,7 @@ void PostProcessManager::customResolveSubpass(DriverApi& driver) noexcept { FMaterialInstance const* mi = mMaterialInstanceManager.getMaterialInstance(ma, 0); mi->use(driver); - auto const pipeline = getPipelineState(ma); + auto const pipeline = getPipelineState(mi); driver.nextSubpass(); driver.scissor(mi->getScissor()); driver.draw(pipeline, mFullScreenQuadRph, 0, 3, 1); @@ -2631,7 +2633,7 @@ void PostProcessManager::clearAncillaryBuffers(DriverApi& driver, FMaterialInstance const* const mi = mMaterialInstanceManager.getMaterialInstance(ma, 0); mi->use(driver); - auto pipeline = getPipelineState(ma, variant); + auto pipeline = getPipelineState(mi, variant); pipeline.rasterState.depthFunc = RasterState::DepthFunc::A; driver.scissor(mi->getScissor()); @@ -2656,7 +2658,7 @@ void PostProcessManager::fog(DriverApi& driver) noexcept { FMaterialInstance const* mi = ma->getDefaultInstance(); mi->use(driver); - auto pipeline = getPipelineState(ma, Variant::NO_VARIANT); + auto pipeline = getPipelineState(mi, Variant::NO_VARIANT); driver.scissor(mi->getScissor()); driver.draw(pipeline, mFullScreenQuadRph, 0, 3, 1); } @@ -3096,7 +3098,7 @@ FrameGraphId PostProcessManager::taa(FrameGraph& fg, if (colorGradingConfig.asSubpass) { out.params.subpassMask = 1; } - auto const pipeline = getPipelineState(ma, variant); + auto const pipeline = getPipelineState(mi, variant); driver.beginRenderPass(out.target, out.params); driver.draw(pipeline, mFullScreenQuadRph, 0, 3, 1); @@ -3178,7 +3180,7 @@ FrameGraphId PostProcessManager::rcas( mi->commit(driver, getUboManager()); mi->use(driver); - auto pipeline = getPipelineState(material.getMaterial(mEngine, driver), variant); + auto pipeline = getPipelineState(mi, variant); if (mode == RcasMode::BLENDED) { pipeline.rasterState.blendFunctionSrcRGB = BlendFunction::ONE; pipeline.rasterState.blendFunctionSrcAlpha = BlendFunction::ONE; @@ -3271,7 +3273,7 @@ FrameGraphId PostProcessManager::upscaleBilinear(FrameGraph& auto out = resources.getRenderPassInfo(); - auto pipeline = getPipelineState(material.getMaterial(mEngine, driver)); + auto pipeline = getPipelineState(mi); if (blended) { pipeline.rasterState.blendFunctionSrcRGB = BlendFunction::ONE; pipeline.rasterState.blendFunctionSrcAlpha = BlendFunction::ONE; @@ -3485,15 +3487,15 @@ FrameGraphId PostProcessManager::upscaleFSR1(FrameGraph& fg, auto out = resources.getRenderPassInfo(); if (UTILS_UNLIKELY(twoPassesEASU)) { - auto pipeline0 = getPipelineState(splitEasuMaterial->getMaterial(mEngine, driver)); - auto pipeline1 = getPipelineState(easuMaterial->getMaterial(mEngine, driver)); + auto pipeline0 = getPipelineState(getMaterialInstance(mEngine, driver, *splitEasuMaterial)); + auto pipeline1 = getPipelineState(getMaterialInstance(mEngine, driver, *easuMaterial)); pipeline1.rasterState.depthFunc = SamplerCompareFunc::NE; driver.beginRenderPass(out.target, out.params); driver.draw(pipeline0, mFullScreenQuadRph, 0, 3, 1); driver.draw(pipeline1, mFullScreenQuadRph, 0, 3, 1); driver.endRenderPass(); } else { - auto pipeline = getPipelineState(easuMaterial->getMaterial(mEngine, driver)); + auto pipeline = getPipelineState(getMaterialInstance(mEngine, driver, *easuMaterial)); renderFullScreenQuad(out, pipeline, driver); } unbindAllDescriptorSets(driver); @@ -3564,7 +3566,7 @@ FrameGraphId PostProcessManager::blit(FrameGraph& fg, bool co mi->commit(driver, getUboManager()); mi->use(driver); - auto pipeline = getPipelineState(ma); + auto pipeline = getPipelineState(mi); if (translucent) { pipeline.rasterState.blendFunctionSrcRGB = BlendFunction::ONE; pipeline.rasterState.blendFunctionSrcAlpha = BlendFunction::ONE; @@ -3805,11 +3807,11 @@ FrameGraphId PostProcessManager::vsmMipmapPass(FrameGraph& fg auto& material = getPostProcessMaterial("vsmMipmap"); FMaterial const* const ma = material.getMaterial(mEngine, driver); + FMaterialInstance* const mi = getMaterialInstance(ma); - auto const pipeline = getPipelineState(ma); + auto const pipeline = getPipelineState(mi); backend::Viewport const scissor = { 0, 0, dim, dim }; - FMaterialInstance* const mi = getMaterialInstance(ma); mi->setParameter("color", in, SamplerParams{ .filterMag = SamplerMagFilter::LINEAR, .filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST @@ -3921,7 +3923,7 @@ FrameGraphId PostProcessManager::debugCombineArrayTexture(Fra mi->commit(driver, getUboManager()); mi->use(driver); - auto pipeline = getPipelineState(ma); + auto pipeline = getPipelineState(mi); if (translucent) { pipeline.rasterState.blendFunctionSrcRGB = BlendFunction::ONE; pipeline.rasterState.blendFunctionSrcAlpha = BlendFunction::ONE; diff --git a/filament/src/PostProcessManager.h b/filament/src/PostProcessManager.h index 99b7ced099..3c4a96f84d 100644 --- a/filament/src/PostProcessManager.h +++ b/filament/src/PostProcessManager.h @@ -388,11 +388,12 @@ public: void bindPostProcessDescriptorSet(backend::DriverApi& driver) const noexcept; - backend::PipelineState getPipelineState(FMaterial const* ma, Variant::type_t variant) const noexcept; + backend::PipelineState getPipelineState(FMaterialInstance const* mi, + Variant::type_t variant) const noexcept; - backend::PipelineState getPipelineState(FMaterial const* ma, - PostProcessVariant variant = PostProcessVariant::OPAQUE) const noexcept { - return getPipelineState(ma, Variant::type_t(variant)); + backend::PipelineState getPipelineState(FMaterialInstance const* mi, + PostProcessVariant variant = PostProcessVariant::OPAQUE) const noexcept { + return getPipelineState(mi, Variant::type_t(variant)); } void renderFullScreenQuad(FrameGraphResources::RenderPassInfo const& out, diff --git a/filament/src/RenderPass.cpp b/filament/src/RenderPass.cpp index d2936f951f..7a87ebc964 100644 --- a/filament/src/RenderPass.cpp +++ b/filament/src/RenderPass.cpp @@ -243,8 +243,8 @@ void RenderPass::appendCommands(FEngine const& engine, backend::DriverApi& drive // This must be done from the main thread. for (Command const* first = curr, *last = curr + commandCount ; first != last ; ++first) { if (UTILS_LIKELY((first->key & CUSTOM_MASK) == uint64_t(CustomCommand::PASS))) { - auto ma = first->info.mi->getMaterial(); - ma->prepareProgram(driver, first->info.materialVariant, CompilerPriorityQueue::CRITICAL); + first->info.mi->prepareProgram(driver, first->info.materialVariant, + CompilerPriorityQueue::CRITICAL); } } } @@ -435,7 +435,7 @@ void RenderPass::setupColorCommand(Command& cmdDraw, Variant variant, keyDraw |= makeField(ma->getRasterState().alphaToCoverage, BLENDING_MASK, BLENDING_SHIFT); cmdDraw.key = isBlendingCommand ? keyBlending : keyDraw; - cmdDraw.info.rasterState = ma->getRasterState(); + cmdDraw.info.rasterState = mi->getRasterState(); // for SSR pass, the blending mode of opaques (including MASKED) must be off // see Material.cpp. @@ -446,10 +446,6 @@ void RenderPass::setupColorCommand(Command& cmdDraw, Variant variant, BlendFunction::ZERO : cmdDraw.info.rasterState.blendFunctionDstAlpha; cmdDraw.info.rasterState.inverseFrontFaces = inverseFrontFaces; - cmdDraw.info.rasterState.culling = mi->getCullingMode(); - cmdDraw.info.rasterState.colorWrite = mi->isColorWriteEnabled(); - cmdDraw.info.rasterState.depthWrite = mi->isDepthWriteEnabled(); - cmdDraw.info.rasterState.depthFunc = mi->getDepthFunc(); cmdDraw.info.rasterState.depthClamp = hasDepthClamp; cmdDraw.info.materialVariant = variant; // we keep "RasterState::colorWrite" to the value set by material (could be disabled) @@ -1090,8 +1086,7 @@ void RenderPass::Executor::execute(FEngine const& engine, DriverApi& driver, mi->use(driver, info.materialVariant); } - assert_invariant(ma); - pipeline.program = ma->getProgram(info.materialVariant); + pipeline.program = mi->getProgram(info.materialVariant); if (UTILS_UNLIKELY(memcmp(&pipeline, ¤tPipeline, sizeof(PipelineState)) != 0)) { currentPipeline = pipeline; diff --git a/filament/src/details/Material.cpp b/filament/src/details/Material.cpp index 0c08842478..a487a88cea 100644 --- a/filament/src/details/Material.cpp +++ b/filament/src/details/Material.cpp @@ -170,8 +170,6 @@ FMaterial::FMaterial(FEngine& engine, const Builder& builder, MaterialDefinition DriverApi& driver = engine.getDriverApi(); - mIsStereoSupported = driver.isStereoSupported(); - mIsParallelShaderCompileSupported = driver.isParallelShaderCompileSupported(); mDepthPrecacheDisabled = driver.isWorkaroundNeeded(Workaround::DISABLE_DEPTH_PRECACHE_FOR_DEFAULT_MATERIAL); mDefaultMaterial = engine.getDefaultMaterial(); @@ -241,44 +239,7 @@ void FMaterial::compile(CompilerPriorityQueue const priority, UserVariantFilterMask variantSpec, CallbackHandler* handler, Invocable&& callback) noexcept { - - DriverApi& driver = mEngine.getDriverApi(); - - // Turn off the STE variant if stereo is not supported. - if (!mIsStereoSupported) { - variantSpec &= ~UserVariantFilterMask(UserVariantFilterBit::STE); - } - - UserVariantFilterMask const variantFilter = - ~variantSpec & UserVariantFilterMask(UserVariantFilterBit::ALL); - ShaderModel const shaderModel = mEngine.getShaderModel(); - bool const isStereoSupported = mEngine.getDriverApi().isStereoSupported(); - - if (UTILS_LIKELY(mIsParallelShaderCompileSupported)) { - for (auto const variant: mDefinition.getVariants()) { - if (!variantFilter || variant == Variant::filterUserVariant(variant, variantFilter)) { - if (mDefinition.hasVariant(variant, shaderModel, isStereoSupported)) { - prepareProgram(driver, variant, priority); - } - } - } - } - - if (callback) { - struct Callback { - Invocable f; - Material* m; - static void func(void* user) { - auto* const c = static_cast(user); - c->f(c->m); - delete c; - } - }; - auto* const user = new(std::nothrow) Callback{ std::move(callback), this }; - driver.compilePrograms(priority, handler, &Callback::func, user); - } else { - driver.compilePrograms(priority, nullptr, nullptr, nullptr); - } + getDefaultInstance()->compile(mEngine, priority, variantSpec, handler, std::move(callback)); } FMaterialInstance* FMaterial::createInstance(const char* name) const noexcept { diff --git a/filament/src/details/Material.h b/filament/src/details/Material.h index 6f3fbbce4c..472fca62ec 100644 --- a/filament/src/details/Material.h +++ b/filament/src/details/Material.h @@ -125,40 +125,6 @@ public: FEngine& getEngine() const noexcept { return mEngine; } - // prepareProgram creates the program for the material's given variant at the backend level. - // Must be called outside of backend render pass. - // Must be called before getProgram() below. - backend::Handle prepareProgram(backend::DriverApi& driver, - Variant const variant, - backend::CompilerPriorityQueue const priorityQueue) const noexcept { - return mPrograms.prepareProgram(driver, variant, priorityQueue); - } - - // getProgram returns the backend program for the material's given variant. - // Must be called after prepareProgram(). - [[nodiscard]] - backend::Handle getProgram(Variant variant) const noexcept { - - if (UTILS_UNLIKELY(mEngine.features.material.enable_fog_as_postprocess)) { - // if the fog as post-process feature is enabled, we need to proceed "as-if" the material - // didn't have the FOG variant bit. - if (getMaterialDomain() == MaterialDomain::SURFACE) { - BlendingMode const blendingMode = getBlendingMode(); - bool const hasScreenSpaceRefraction = getRefractionMode() == RefractionMode::SCREEN_SPACE; - bool const isBlendingCommand = !hasScreenSpaceRefraction && - (blendingMode != BlendingMode::OPAQUE && blendingMode != BlendingMode::MASKED); - if (!isBlendingCommand) { - variant.setFog(false); - } - } - } - -#if FILAMENT_ENABLE_MATDBG - updateActiveProgramsForMatdbg(variant); -#endif - return mPrograms.getProgram(variant); - } - // MaterialInstance::use() binds descriptor sets before drawing. For shared variants, // however, the material instance will call useShared() to bind the default material's sets // instead. @@ -305,6 +271,9 @@ public: } /** @}*/ + + // Called by getProgram() to update active program list for matdbg UI. + void updateActiveProgramsForMatdbg(Variant const variant) const noexcept; #endif private: @@ -320,8 +289,6 @@ private: bool mIsDefaultMaterial = false; bool mUseUboBatching = false; - bool mIsStereoSupported = false; - bool mIsParallelShaderCompileSupported = false; bool mDepthPrecacheDisabled = false; FMaterial const* mDefaultMaterial = nullptr; @@ -336,8 +303,6 @@ private: mutable utils::Mutex mPendingEditsLock; std::unique_ptr mPendingEdits; std::unique_ptr mEditedMaterialParser; - // Called by getProgram() to update active program list for matdbg UI. - void updateActiveProgramsForMatdbg(Variant const variant) const noexcept; void setPendingEdits(std::unique_ptr pendingEdits) noexcept; bool hasPendingEdits() const noexcept; void latchPendingEdits() noexcept; diff --git a/filament/src/details/MaterialInstance.cpp b/filament/src/details/MaterialInstance.cpp index b93ea25cc1..a2af631fe1 100644 --- a/filament/src/details/MaterialInstance.cpp +++ b/filament/src/details/MaterialInstance.cpp @@ -17,6 +17,7 @@ #include #include "RenderPass.h" +#include "MaterialParser.h" #include "ds/DescriptorSetLayout.h" @@ -131,6 +132,7 @@ FMaterialInstance::FMaterialInstance(FEngine& engine, mTextureParameters(other->mTextureParameters), mDescriptorSet(other->mDescriptorSet.duplicate( "MaterialInstance", mMaterial->getDescriptorSetLayout())), + mPrograms(other->mPrograms), mPolygonOffset(other->mPolygonOffset), mStencilState(other->mStencilState), mMaskThreshold(other->mMaskThreshold), @@ -207,6 +209,10 @@ void FMaterialInstance::terminate(FEngine& engine) { if (ubHandle){ driver.destroyBufferObject(*ubHandle); } + + if (mPrograms.isInitialized()) { + mPrograms.terminate(engine); + } } void FMaterialInstance::commit(FEngine& engine) const { @@ -259,6 +265,39 @@ void FMaterialInstance::commit(FEngine::DriverApi& driver, UboManager* uboManage // ------------------------------------------------------------------------------------------------ +template +void FMaterialInstance::setConstantImpl(std::string_view name, T value) { + auto const& constants = mMaterial->getDefinition().specializationConstantsNameToIndex; + auto it = constants.find(name); + FILAMENT_CHECK_PRECONDITION(it != constants.end()) << "Constant " << name << " does not exist"; + + if (UTILS_UNLIKELY(mPendingSpecializationConstants.empty())) { + mPendingSpecializationConstants = + FixedCapacityVector( + getPrograms().getSpecializationConstants()); + } + + uint32_t id = it->second + CONFIG_MAX_RESERVED_SPEC_CONSTANTS; + mPendingSpecializationConstants[id] = value; +} + +template +T FMaterialInstance::getConstantImpl(std::string_view name) const { + auto const& constants = mMaterial->getDefinition().specializationConstantsNameToIndex; + auto it = constants.find(name); + FILAMENT_CHECK_PRECONDITION(it != constants.end()) << "Constant " << name << " does not exist"; + + uint32_t id = it->second + CONFIG_MAX_RESERVED_SPEC_CONSTANTS; + + if (UTILS_UNLIKELY(!mPendingSpecializationConstants.empty())) { + return std::get(mPendingSpecializationConstants[id]); + } + + return getPrograms().getConstant(id); +} + +// ------------------------------------------------------------------------------------------------ + void FMaterialInstance::setParameter(std::string_view const name, Handle texture, SamplerParams const params) { auto const binding = mMaterial->getSamplerBinding(name); @@ -378,6 +417,15 @@ void FMaterialInstance::setTransparencyMode(TransparencyMode const mode) noexcep mTransparencyMode = mode; } +RasterState FMaterialInstance::getRasterState() const noexcept { + RasterState rs = mMaterial->getRasterState(); + rs.culling = mCulling; + rs.depthWrite = mDepthWrite; + rs.depthFunc = mDepthFunc; + rs.colorWrite = mColorWrite; + return rs; +} + void FMaterialInstance::setDepthCulling(bool const enable) noexcept { mDepthFunc = enable ? RasterState::DepthFunc::GE : RasterState::DepthFunc::A; } @@ -398,6 +446,53 @@ const char* FMaterialInstance::getName() const noexcept { // ------------------------------------------------------------------------------------------------ +void FMaterialInstance::compile(FEngine& engine, CompilerPriorityQueue const priority, + UserVariantFilterMask variantSpec, CallbackHandler* handler, + Invocable&& callback) noexcept { + + DriverApi& driver = engine.getDriverApi(); + MaterialDefinition const& definition = mMaterial->getDefinition(); + + bool const isStereoSupported = driver.isStereoSupported(); + + // Turn off the STE variant if stereo is not supported. + if (UTILS_LIKELY(!isStereoSupported)) { + variantSpec &= ~UserVariantFilterMask(UserVariantFilterBit::STE); + } + + UserVariantFilterMask const variantFilter = + ~variantSpec & UserVariantFilterMask(UserVariantFilterBit::ALL); + ShaderModel const shaderModel = engine.getShaderModel(); + + if (UTILS_LIKELY(driver.isParallelShaderCompileSupported())) { + for (auto const variant: definition.getVariants()) { + if (!variantFilter || variant == Variant::filterUserVariant(variant, variantFilter)) { + if (definition.hasVariant(variant, shaderModel, isStereoSupported)) { + prepareProgram(driver, variant, priority); + } + } + } + } + + if (callback) { + struct Callback { + Invocable f; + Material* m; + static void func(void* user) { + auto* const c = static_cast(user); + c->f(c->m); + delete c; + } + }; + // TODO(exv): fix this const cast + auto* const user = new (std::nothrow) Callback{ std::move(callback), + const_cast(static_cast(mMaterial)) }; + driver.compilePrograms(priority, handler, &Callback::func, user); + } else { + driver.compilePrograms(priority, nullptr, nullptr, nullptr); + } +} + void FMaterialInstance::use(FEngine::DriverApi& driver, Variant variant) const { assert_invariant(mDescriptorSet.getHandle()); assert_invariant(!isUsingUboBatching() || BufferAllocator::isValid(getAllocationId())); @@ -502,4 +597,34 @@ void FMaterialInstance::fixMissingSamplers() const { } } +LocalProgramCache const& FMaterialInstance::getPrograms() const noexcept { + return mPrograms.isInitialized() ? mPrograms : mMaterial->getPrograms(); +} + +void FMaterialInstance::flushSpecializationConstants() const noexcept { + assert_invariant(!mPendingSpecializationConstants.empty()); + + if (!mPrograms.isInitialized()) { + mPrograms.initializeForMaterialInstance(mMaterial->getEngine(), *mMaterial); + } + mPrograms.setConstants(std::move(mPendingSpecializationConstants)); + mPendingSpecializationConstants.clear(); +} + +#if FILAMENT_ENABLE_MATDBG + +void FMaterialInstance::updateActiveProgramsForMatdbg(Variant const variant) const noexcept { + mMaterial->updateActiveProgramsForMatdbg(variant); +} + +#endif // FILAMENT_ENABLE_MATDBG + +template void FMaterialInstance::setConstantImpl(std::string_view name, int32_t value); +template void FMaterialInstance::setConstantImpl(std::string_view name, float value); +template void FMaterialInstance::setConstantImpl(std::string_view name, bool value); + +template int32_t FMaterialInstance::getConstantImpl(std::string_view name) const; +template float FMaterialInstance::getConstantImpl(std::string_view name) const; +template bool FMaterialInstance::getConstantImpl(std::string_view name) const; + } // namespace filament diff --git a/filament/src/details/MaterialInstance.h b/filament/src/details/MaterialInstance.h index 4e99b38115..a47613f9fe 100644 --- a/filament/src/details/MaterialInstance.h +++ b/filament/src/details/MaterialInstance.h @@ -18,7 +18,7 @@ #define TNT_FILAMENT_DETAILS_MATERIALINSTANCE_H #include "downcast.h" - +#include "LocalProgramCache.h" #include "UniformBuffer.h" #include "ds/DescriptorSet.h" @@ -26,8 +26,6 @@ #include "details/BufferAllocator.h" #include "details/Engine.h" -#include "private/backend/DriverApi.h" - #include #include @@ -67,7 +65,7 @@ public: ~FMaterialInstance() noexcept; void terminate(FEngine& engine); - + void commit(FEngine& engine) const; void commit(FEngine::DriverApi& driver, UboManager* uboManager) const; @@ -86,6 +84,34 @@ public: UniformBuffer const& getUniformBuffer() const noexcept { return mUniforms; } + void compile(FEngine& engine, backend::CompilerPriorityQueue priority, + UserVariantFilterMask variantSpec, backend::CallbackHandler* handler, + utils::Invocable&& callback) noexcept; + + // prepareProgram creates the program for the material's given variant at the backend level. + // Must be called outside of backend render pass. + // Must be called before getProgram() below. + backend::Handle prepareProgram(backend::DriverApi& driver, + Variant const variant, + backend::CompilerPriorityQueue const priorityQueue) const noexcept { + if (UTILS_UNLIKELY(!mPendingSpecializationConstants.empty())) { + flushSpecializationConstants(); + } + return getPrograms().prepareProgram(driver, variant, priorityQueue); + } + + // getProgram returns the backend program for the material's given variant. + // Must be called after prepareProgram(). + // + // See also Material::getProgram(). + [[nodiscard]] + backend::Handle getProgram(Variant const variant) const noexcept { +#if FILAMENT_ENABLE_MATDBG + updateActiveProgramsForMatdbg(variant); +#endif + return getPrograms().getProgram(variant); + } + void setScissor(uint32_t const left, uint32_t const bottom, uint32_t const width, uint32_t const height) noexcept { constexpr uint32_t maxvalu = std::numeric_limits::max(); mScissorRect = { int32_t(left), int32_t(bottom), @@ -107,6 +133,8 @@ public: bool hasScissor() const noexcept { return mHasScissor; } + backend::RasterState getRasterState() const noexcept; + backend::CullingMode getCullingMode() const noexcept { return mCulling; } backend::CullingMode getShadowCullingMode() const noexcept { return mShadowCulling; } @@ -254,11 +282,15 @@ public: backend::Handle texture, backend::SamplerParams params); using MaterialInstance::setParameter; + using MaterialInstance::setConstant; private: friend class FMaterial; friend class MaterialInstance; + // Cannot inline since it inspects the FMaterial class. + LocalProgramCache const& getPrograms() const noexcept; + template void setParameterUntypedImpl(std::string_view name, const void* value); @@ -277,6 +309,19 @@ private: template T getParameterImpl(std::string_view name) const; + template + void setConstantImpl(std::string_view name, T value); + + template + T getConstantImpl(std::string_view name) const; + + void flushSpecializationConstants() const noexcept; + +#if FILAMENT_ENABLE_MATDBG + // Called by getProgram() to update active program list for matdbg UI. + void updateActiveProgramsForMatdbg(Variant const variant) const noexcept; +#endif + // keep these grouped, they're accessed together in the render-loop FMaterial const* mMaterial = nullptr; @@ -291,6 +336,11 @@ private: mutable DescriptorSet mDescriptorSet; UniformBuffer mUniforms; + // HACK: Mutable so that prepareProgram() can update specialization constants. + mutable LocalProgramCache mPrograms; + mutable utils::FixedCapacityVector + mPendingSpecializationConstants; + backend::PolygonOffset mPolygonOffset{}; backend::StencilState mStencilState{}; diff --git a/filament/test/filament_test_material.cpp b/filament/test/filament_test_material.cpp index d74b3e9bc3..72df47f546 100644 --- a/filament/test/filament_test_material.cpp +++ b/filament/test/filament_test_material.cpp @@ -19,6 +19,7 @@ #include #include +#include #include "filament_test_resources.h" @@ -167,3 +168,52 @@ TEST(Material, MaterialSettingInvalidApiLevelReturnsAnInvalidPackage) { Engine::destroy(engine); } + +TEST(MaterialInstanceTest, SetConstant) { + Engine* engine = Engine::create(Engine::Backend::NOOP); + + std::string shaderCode(R"( + void material(inout MaterialInputs material) { + prepareMaterial(material); + material.baseColor = vec4(1.0); + } + )"); + + filamat::MaterialBuilder builder; + builder.init(); + builder.name("MaterialInstanceTest"); + builder.material(shaderCode.c_str()); + builder.constant("myFloat", filamat::MaterialBuilder::ConstantType::FLOAT, 1.0f); + builder.constant("myInt", filamat::MaterialBuilder::ConstantType::INT, 2); + builder.constant("myBool", filamat::MaterialBuilder::ConstantType::BOOL, false); + + filamat::Package result = builder.build(engine->getJobSystem()); + ASSERT_TRUE(result.isValid()); + + Material* material = Material::Builder() + .package(result.getData(), result.getSize()) + .build(*engine); + ASSERT_NE(material, nullptr); + + MaterialInstance* instance = material->createInstance(); + ASSERT_NE(instance, nullptr); + + // Verify default values + EXPECT_EQ(instance->getConstant("myFloat"), 1.0f); + EXPECT_EQ(instance->getConstant("myInt"), 2); + EXPECT_EQ(instance->getConstant("myBool"), false); + + // Set new values + instance->setConstant("myFloat", 3.0f); + instance->setConstant("myInt", 4); + instance->setConstant("myBool", true); + + // Verify new values + EXPECT_EQ(instance->getConstant("myFloat"), 3.0f); + EXPECT_EQ(instance->getConstant("myInt"), 4); + EXPECT_EQ(instance->getConstant("myBool"), true); + + engine->destroy(instance); + engine->destroy(material); + Engine::destroy(engine); +}