From 8a1a0b0fd28b3713b91157ebd4e4def93fa36a03 Mon Sep 17 00:00:00 2001 From: Eliza <4576666+elizagamedev@users.noreply.github.com> Date: Fri, 6 Jun 2025 12:52:20 +0900 Subject: [PATCH] materials: introduce mutable spec constants (#8795) * materials: introduce mutable spec constants Rationale & design of this feature has been discussed internally. The current implementation uses a `FixedCapacityVector` to store the new program handles, but I wouldn't object to replacing it with a hasmap as discussed offline. I have compiled but not tested this yet on Android, so I'm not certain that the API bindings are correctly wired up. * materials: mutable spec constant feedback * materials: address mutable spec constant comments --- NEW_RELEASE_NOTES.md | 4 +- .../src/main/cpp/MaterialInstance.cpp | 15 ++ .../android/filament/MaterialInstance.java | 13 ++ docs_src/markdeep/Materials.md.html | 55 +++-- filament/backend/include/backend/Program.h | 5 +- filament/backend/src/Program.cpp | 12 +- filament/include/filament/MaterialInstance.h | 56 ++++- filament/src/MaterialInstance.cpp | 40 ++++ filament/src/MaterialParser.cpp | 37 +++ filament/src/MaterialParser.h | 10 + filament/src/PostProcessManager.cpp | 10 +- filament/src/RenderPass.cpp | 6 +- filament/src/details/Material.cpp | 132 ++++++++--- filament/src/details/Material.h | 60 +++-- filament/src/details/MaterialInstance.cpp | 2 + filament/src/details/MaterialInstance.h | 14 +- .../include/filament/MaterialChunkType.h | 1 + .../include/private/filament/ConstantInfo.h | 14 +- .../filamat/include/filamat/MaterialBuilder.h | 6 +- libs/filamat/src/MaterialBuilder.cpp | 41 +++- .../src/eiff/MaterialInterfaceBlockChunk.cpp | 14 ++ .../src/eiff/MaterialInterfaceBlockChunk.h | 15 ++ libs/filamat/src/shaders/ShaderGenerator.cpp | 25 +- libs/filamat/src/shaders/ShaderGenerator.h | 9 +- libs/filamat/tests/test_filamat.cpp | 14 +- samples/CMakeLists.txt | 2 + .../bakedColorWithMutableConstants.mat | 44 ++++ samples/mutable_constants.cpp | 214 ++++++++++++++++++ tools/matc/src/matc/ParametersProcessor.cpp | 21 +- 29 files changed, 775 insertions(+), 116 deletions(-) create mode 100644 samples/materials/bakedColorWithMutableConstants.mat create mode 100644 samples/mutable_constants.cpp diff --git a/NEW_RELEASE_NOTES.md b/NEW_RELEASE_NOTES.md index 3cb132818b..0477a387a3 100644 --- a/NEW_RELEASE_NOTES.md +++ b/NEW_RELEASE_NOTES.md @@ -8,4 +8,6 @@ appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md). ## Release notes for next branch cut -- samples: samples now have a CLI to select backend api \ No newline at end of file +- samples: samples now have a CLI to select backend api +- materials: sampler now export their type in the material binary [⚠️ **New Material Version**] +- materials: new mutable specialization constants feature. See the [materials documentation](https://google.github.io/filament/Materials.html) for details. [⚠️ **New Material Version**] diff --git a/android/filament-android/src/main/cpp/MaterialInstance.cpp b/android/filament-android/src/main/cpp/MaterialInstance.cpp index 5f91d88162..1e583228ba 100644 --- a/android/filament-android/src/main/cpp/MaterialInstance.cpp +++ b/android/filament-android/src/main/cpp/MaterialInstance.cpp @@ -60,6 +60,14 @@ static void setParameter(JNIEnv* env, jlong nativeMaterialInstance, jstring name env->ReleaseStringUTFChars(name_, name); } +template +static void setConstant(JNIEnv* env, jlong nativeMaterialInstance, jstring name_, T v) { + MaterialInstance* instance = (MaterialInstance*) nativeMaterialInstance; + const char *name = env->GetStringUTFChars(name_, 0); + instance->setConstant(name, v); + env->ReleaseStringUTFChars(name_, name); +} + extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_MaterialInstance_nSetParameterBool(JNIEnv *env, jclass, @@ -264,6 +272,13 @@ Java_com_google_android_filament_MaterialInstance_nSetParameterTexture( env->ReleaseStringUTFChars(name_, name); } +extern "C" +JNIEXPORT void JNICALL +Java_com_google_android_filament_MaterialInstance_nSetConstantBool(JNIEnv *env, jclass, + jlong nativeMaterialInstance, jstring name_, jboolean x) { + setConstant(env, nativeMaterialInstance, name_, bool(x)); +} + extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_MaterialInstance_nSetScissor( 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 8efd7f841d..a1b5e6abfe 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,16 @@ public class MaterialInstance { nSetParameterFloat4(getNativeObject(), name, color[0], color[1], color[2], color[3]); } + /** + * Sets the value of a bool constant. + * + * @param name the name of the material constant + * @param x the value of the material constant + */ + public void setConstant(@NonNull String name, boolean x) { + nSetConstantBool(getNativeObject(), name, x); + } + /** * Set-up a custom scissor rectangle; by default it is disabled. * @@ -921,6 +931,9 @@ 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 void nSetConstantBool(long nativeMaterialInstance, + @NonNull String name, boolean x); + private static native void nSetParameterTexture(long nativeMaterialInstance, @NonNull String name, long nativeTexture, long sampler); diff --git a/docs_src/markdeep/Materials.md.html b/docs_src/markdeep/Materials.md.html index e99ed6555b..353ad97e58 100644 --- a/docs_src/markdeep/Materials.md.html +++ b/docs_src/markdeep/Materials.md.html @@ -211,7 +211,7 @@ when using textures. This property can dramatically change the appearance of a surface. Non-metallic surfaces have chromatic diffuse reflection and achromatic specular reflection (reflected light does not change color). Metallic surfaces do not have any diffuse reflection and chromatic specular reflection -(reflected light takes on the color of the surfaced as defined by `baseColor`). +(reflected light takes on the color of the surfaced as defined by `baseColor`). The effect of `metallic` is shown in figure [metallicProperty] (click on the image to see a larger version). @@ -247,7 +247,7 @@ The effect of `roughness` on metallic surfaces is shown in figure [roughnessCond When refraction through an object is enabled (using a `refractonType` of `thin` or `solid`), the `roughness` property will also affect the refractions, as shown in figure [roughnessRefractionProperty] (click on the image to see a larger version). - + ![Figure [roughnessRefractionProperty]: Refractive sphere with `roughness` varying from 0.0 (left) to 1.0 (right)](images/materials/refraction_roughness.png) @@ -306,7 +306,7 @@ The sheen color controls the color appearance and strength of an optional sheen base layer described by the properties above. The sheen layer always sits below the clear coat layer if such a layer is present. -The sheen layer can be used to represent cloth and fabric materials. Please refer to +The sheen layer can be used to represent cloth and fabric materials. Please refer to section [Cloth model] for more information about cloth and fabric materials. The effect of `sheenColor` is shown in figure [materialSheenColor] @@ -519,14 +519,14 @@ light to bend further away from the initial path. Table [commonMatIOR] describes acceptable refractive indices for various types of materials. -Material | IOR +Material | IOR --------------------------:|:----------------- Air | 1.0 -Water | 1.33 -Common liquids | 1.33 to 1.5 -Common gemstones | 1.58 to 2.33 -Plastics, glass | 1.5 to 1.58 -Other dielectric materials | 1.33 to 1.58 +Water | 1.33 +Common liquids | 1.33 to 1.5 +Common gemstones | 1.58 to 2.33 +Plastics, glass | 1.5 to 1.58 +Other dielectric materials | 1.33 to 1.58 [Table [commonMatIOR]: Index of refraction of common materials] The appearance of a refractive material will greatly depend on the `refractionType` and @@ -1091,30 +1091,37 @@ Value `bool` or `number`, depending on the `type` of the constant. The type must be one of the types described in table [materialConstantsTypes]. - Type | Description | Default -:----------------------|:-----------------------------------------|:------------------ -int | A signed, 32 bit GLSL int | 0 -float | A single-precision GLSL float | 0.0 -bool | A GLSL bool | false + Constants may also be specified as mutable by setting the `mutable` property to `true`. Only + `bool` spec constants may be specified as mutable. + +| Type | Description | Default | May be mutable? | +|:------|:------------------------------|:--------|-----------------| +| int | A signed, 32 bit GLSL int | | no | +| float | A single-precision GLSL float | 0.0 | no | +| bool | A GLSL bool | false | yes | [Table [materialConstantsTypes]: Material constants types] Description : Lists the constant parameters accepted by your material. These constants can be set, or "specialized", at runtime when loading a material package. Multiple materials can be loaded from - the same material package with differing constant parameter specializations. Once a material is - loaded from a material package, its constant parameters cannot be changed. Compared to regular - parameters, constant parameters allow the compiler to generate more efficient code. Access - constant parameters from the shader by prefixing the name with `materialConstant_`. For example, - a constant parameter named `myConstant` is accessed in the shader as - `materialConstant_myConstant`. If a constant parameter is not set at runtime, the default is - used. + the same material package with differing constant parameter specializations. If a constant + parameter is specialized as mutable, it may be changed at any time on a per-instance basis via + `MaterialInstance::setConstant(name, value)`. Otherwise, once a material is loaded from a + material package, its constant parameters cannot be changed. + + Compared to regular parameters, constant parameters allow the compiler to generate more + efficient code. Access constant parameters from the shader by prefixing the name with + `materialConstant_`. For example, a constant parameter named `myConstant` is accessed in the + shader as `materialConstant_myConstant`. If a constant parameter is not set at runtime, the + default is used. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ JSON material { constants : [ { name : overrideAlpha, - type : bool + type : bool, + mutable : true, }, { name : customAlpha, @@ -2553,7 +2560,7 @@ standard skybox material. It produces a list of 2 parameters, named `showSun` an respectively a boolean and a cubemap texture. ```text -$ matc --reflect parameters filament/src/materials/skybox.mat +$ matc --reflect parameters filament/src/materials/skybox.mat { "parameters": [ { @@ -2569,7 +2576,7 @@ $ matc --reflect parameters filament/src/materials/skybox.mat } ] } -``` +``` ### --variant-filter diff --git a/filament/backend/include/backend/Program.h b/filament/backend/include/backend/Program.h index a2464a0382..b5ac3b26b3 100644 --- a/filament/backend/include/backend/Program.h +++ b/filament/backend/include/backend/Program.h @@ -20,6 +20,7 @@ #include #include #include +#include #include @@ -66,6 +67,7 @@ public: using DescriptorBindingsInfo = utils::FixedCapacityVector; using DescriptorSetInfo = std::array; using SpecializationConstantsInfo = utils::FixedCapacityVector; + using MutableSpecConstantsInfo = utils::bitset8; using ShaderBlob = utils::FixedCapacityVector; using ShaderSource = std::array; @@ -102,7 +104,8 @@ public: Program& descriptorBindings(backend::descriptor_set_t set, DescriptorBindingsInfo descriptorBindings) noexcept; - Program& specializationConstants(SpecializationConstantsInfo specConstants) noexcept; + Program& specializationConstants(SpecializationConstantsInfo specConstants, + uint32_t firstMutableId, MutableSpecConstantsInfo mutableSpecConstants) noexcept; struct PushConstant { utils::CString name; diff --git a/filament/backend/src/Program.cpp b/filament/backend/src/Program.cpp index bb4249f82a..4417bfdcab 100644 --- a/filament/backend/src/Program.cpp +++ b/filament/backend/src/Program.cpp @@ -80,8 +80,18 @@ Program& Program::attributes(AttributesInfo attributes) noexcept { return *this; } -Program& Program::specializationConstants(SpecializationConstantsInfo specConstants) noexcept { +Program& Program::specializationConstants(SpecializationConstantsInfo specConstants, + uint32_t firstMutableId, MutableSpecConstantsInfo mutableSpecConstants) noexcept { + // String the two lists together. mSpecializationConstants = std::move(specConstants); + uint32_t firstMutableIndex = specConstants.size(); + mSpecializationConstants.reserve(specConstants.size() + mutableSpecConstants.size()); + for (uint32_t i = 0; i < mutableSpecConstants.size(); i++) { + mSpecializationConstants[i + firstMutableIndex] = SpecializationConstant { + .id = i + firstMutableId, + .value = mutableSpecConstants[i], + }; + } return *this; } diff --git a/filament/include/filament/MaterialInstance.h b/filament/include/filament/MaterialInstance.h index edb565759d..37cbb0faa2 100644 --- a/filament/include/filament/MaterialInstance.h +++ b/filament/include/filament/MaterialInstance.h @@ -86,6 +86,9 @@ public: std::is_same_v >; + template + using is_supported_constant_parameter_t = std::enable_if_t>; + /** * 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 @@ -237,13 +240,13 @@ public: /** * Gets the value of a parameter by name. - * + * * Note: Only supports non-texture parameters such as numeric and math types. - * + * * @param name Name of the parameter as defined by Material. Cannot be nullptr. * @param nameLength Length in `char` of the name parameter. * @throws utils::PreConditionPanic if name doesn't exist or no-op if exceptions are disabled. - * + * * @see Material::hasParameter */ template @@ -261,6 +264,53 @@ public: return getParameter(name, strlen(name)); } + /** + * Set a mutable constant parameter by name. + * + * @param name Name of the parameter as defined by Material. Cannot be nullptr. + * @param nameLength Length in `char` of the name parameter. + * @param value Value of the parameter to set. + * @throws utils::PreConditionPanic if name doesn't exist or no-op if exceptions are disabled. + */ + template> + void setConstant(const char* UTILS_NONNULL name, size_t nameLength, T const& value); + + /** inline helper to provide the name as a null-terminated string literal */ + template> + void setConstant(StringLiteral const name, T const& 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 const& value) { + setConstant(name, strlen(name), value); + } + + /** + * Gets the value of a mutable constant parameter by name. + * + * @param name Name of the parameter as defined by Material. Cannot be nullptr. + * @param nameLength Length in `char` of the name parameter. + * @throws utils::PreConditionPanic if name doesn't exist or no-op if exceptions are disabled. + * + * @see Material::hasConstant + */ + 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/MaterialInstance.cpp b/filament/src/MaterialInstance.cpp index 27c70eb745..711b17e27a 100644 --- a/filament/src/MaterialInstance.cpp +++ b/filament/src/MaterialInstance.cpp @@ -232,6 +232,46 @@ template UTILS_PUBLIC mat3f MaterialInstance::getParameter (const ch // ------------------------------------------------------------------------------------------------ +template<> +inline void FMaterialInstance::setConstantImpl(std::string_view const name, bool const& value) { + std::optional id = mMaterial->getMutableConstantId(name); + FILAMENT_CHECK_PRECONDITION(id.has_value()) << "No mutable constant with name " << name; + mConstants.set(*id, value); +} + +template +void MaterialInstance::setConstant(const char* name, size_t nameLength, T const& value) { + downcast(this)->setConstantImpl({ name, nameLength }, value); +} + +// Explicit template instantiation of our supported types. +// +// Mutable spec constants will probably only ever allow bools, but it's nice to keep the API +// forwards-compatible. +template UTILS_PUBLIC void MaterialInstance::setConstant(const char* name, size_t nameLength, bool const& v); + +// ------------------------------------------------------------------------------------------------ + +template<> +inline bool FMaterialInstance::getConstantImpl(std::string_view const name) const { + std::optional id = mMaterial->getMutableConstantId(name); + FILAMENT_CHECK_PRECONDITION(id.has_value()) << "No mutable constant with name " << name; + return mConstants[*id]; +} + +template +T MaterialInstance::getConstant(const char* name, size_t nameLength) const { + return downcast(this)->getConstantImpl({ name, nameLength }); +} + +// Explicit template instantiation of our supported types. +// +// Mutable spec constants will probably only ever allow bools, but it's nice to keep the API +// forwards-compatible. +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/MaterialParser.cpp b/filament/src/MaterialParser.cpp index e3800804c7..e13fac7567 100644 --- a/filament/src/MaterialParser.cpp +++ b/filament/src/MaterialParser.cpp @@ -229,6 +229,11 @@ bool MaterialParser::getConstants(FixedCapacityVector* contain return get(container); } +bool MaterialParser::getMutableConstants( + FixedCapacityVector* container) const noexcept { + return get(container); +} + bool MaterialParser::getPushConstants(CString* structVarName, FixedCapacityVector* value) const noexcept { auto [start, end] = mImpl.mChunkContainer.getChunkRange(MaterialPushConstants); @@ -751,6 +756,38 @@ bool ChunkMaterialConstants::unflatten(Unflattener& unflattener, return true; } +bool ChunkMaterialMutableConstants::unflatten(Unflattener& unflattener, + FixedCapacityVector* materialConstants) { + assert_invariant(materialConstants); + + // Read number of constants. + uint64_t numConstants = 0; + if (!unflattener.read(&numConstants)) { + return false; + } + + materialConstants->reserve(numConstants); + materialConstants->resize(numConstants); + + for (uint64_t i = 0; i < numConstants; i++) { + CString constantName; + bool defaultValue; + + if (!unflattener.read(&constantName)) { + return false; + } + + if (!unflattener.read(&defaultValue)) { + return false; + } + + (*materialConstants)[i].name = std::move(constantName); + (*materialConstants)[i].defaultValue = defaultValue; + } + + return true; +} + bool ChunkMaterialPushConstants::unflatten(Unflattener& unflattener, CString* structVarName, FixedCapacityVector* materialPushConstants) { diff --git a/filament/src/MaterialParser.h b/filament/src/MaterialParser.h index 9787d64ab5..8e6d52fec7 100644 --- a/filament/src/MaterialParser.h +++ b/filament/src/MaterialParser.h @@ -49,6 +49,7 @@ class BufferInterfaceBlock; class SamplerInterfaceBlock; struct SubpassInfo; struct MaterialConstant; +struct MaterialMutableConstant; struct MaterialPushConstant; class MaterialParser { @@ -79,6 +80,8 @@ public: bool getShaderModels(uint32_t* value) const noexcept; bool getMaterialProperties(uint64_t* value) const noexcept; bool getConstants(utils::FixedCapacityVector* value) const noexcept; + bool getMutableConstants(utils::FixedCapacityVector* value) + const noexcept; bool getPushConstants(utils::CString* structVarName, utils::FixedCapacityVector* value) const noexcept; @@ -237,6 +240,13 @@ struct ChunkMaterialConstants { static filamat::ChunkType const tag = filamat::MaterialConstants; }; +struct ChunkMaterialMutableConstants { + static bool unflatten(filaflat::Unflattener& unflattener, + utils::FixedCapacityVector* materialConstants); + using Container = utils::FixedCapacityVector; + static filamat::ChunkType const tag = filamat::MaterialMutableConstants; +}; + struct ChunkMaterialPushConstants { static bool unflatten(filaflat::Unflattener& unflattener, utils::CString* structVarName, utils::FixedCapacityVector* materialPushConstants); diff --git a/filament/src/PostProcessManager.cpp b/filament/src/PostProcessManager.cpp index 08a2699b76..c62427e7a9 100644 --- a/filament/src/PostProcessManager.cpp +++ b/filament/src/PostProcessManager.cpp @@ -187,7 +187,10 @@ FMaterial* PostProcessManager::PostProcessMaterial::getMaterial(FEngine& engine, if (UTILS_UNLIKELY(mSize)) { loadMaterial(engine); } - mMaterial->prepareProgram(Variant{ Variant::type_t(variant) }); + mMaterial->prepareProgram(Variant{ Variant::type_t(variant) }, + // TODO(exv): allow post process materials to use mutable spec constants? + Program::MutableSpecConstantsInfo(0) + ); return mMaterial; } @@ -423,7 +426,10 @@ UTILS_NOINLINE PipelineState PostProcessManager::getPipelineState( FMaterial const* const ma, PostProcessVariant variant) const noexcept { return { - .program = ma->getProgram(Variant{ Variant::type_t(variant) }), + .program = ma->getProgram(Variant{ Variant::type_t(variant) }, + // TODO(exv): allow post process materials to use mutable spec constants? + Program::MutableSpecConstantsInfo(0) + ), .vertexBufferInfo = mFullScreenQuadVbih, .pipelineLayout = { .setLayout = { diff --git a/filament/src/RenderPass.cpp b/filament/src/RenderPass.cpp index 085772e506..cdd946d42c 100644 --- a/filament/src/RenderPass.cpp +++ b/filament/src/RenderPass.cpp @@ -246,7 +246,8 @@ void RenderPass::appendCommands(FEngine const& engine, 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(first->info.materialVariant); + ma->prepareProgram(first->info.materialVariant, + first->info.mi->getMutableSpecConstants()); } } } @@ -1098,7 +1099,8 @@ void RenderPass::Executor::execute(FEngine const& engine, DriverApi& driver, } assert_invariant(ma); - pipeline.program = ma->getProgram(info.materialVariant); + pipeline.program = ma->getProgram(info.materialVariant, + info.mi->getMutableSpecConstants()); 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 4042fee664..b385bd4d35 100644 --- a/filament/src/details/Material.cpp +++ b/filament/src/details/Material.cpp @@ -338,8 +338,24 @@ FMaterial::FMaterial(FEngine& engine, const Builder& builder, processBlendingMode(parser); processSpecializationConstants(engine, builder, parser); + processMutableSpecializationConstants(engine, builder, parser); processPushConstants(engine, parser); processDescriptorSets(engine, parser); + + switch (mMaterialDomain) { + case filament::MaterialDomain::SURFACE: + mVariantBits = VARIANT_BITS; + break; + case filament::MaterialDomain::POST_PROCESS: + mVariantBits = POST_PROCESS_VARIANT_BITS; + break; + case filament::MaterialDomain::COMPUTE: + mVariantBits = 0; + break; + } + mCachedPrograms = FixedCapacityVector>( + 1 << (mVariantBits + mMaterialMutableConstants.size())); + precacheDepthVariants(engine); #if FILAMENT_ENABLE_MATDBG @@ -442,13 +458,17 @@ void FMaterial::compile(CompilerPriorityQueue const priority, UserVariantFilterMask const variantFilter = ~variantSpec & UserVariantFilterMask(UserVariantFilterBit::ALL); + int mutableConstantVariations = 1 << mMaterialMutableConstants.size(); + if (UTILS_LIKELY(mEngine.getDriverApi().isParallelShaderCompileSupported())) { auto const& variants = isVariantLit() ? VariantUtils::getLitVariants() : VariantUtils::getUnlitVariants(); for (auto const variant: variants) { if (!variantFilter || variant == Variant::filterUserVariant(variant, variantFilter)) { if (hasVariant(variant)) { - prepareProgram(variant, priority); + for (int spec = 0; spec < mutableConstantVariations; spec++) { + prepareProgram(variant, Program::MutableSpecConstantsInfo(spec), priority); + } } } } @@ -489,6 +509,11 @@ FMaterialInstance* FMaterial::getDefaultInstance() noexcept { return mDefaultMaterialInstance; } +bool FMaterial::isCached(Variant const variant, + const backend::Program::MutableSpecConstantsInfo mutableSpecConstants) const noexcept { + return bool(mCachedPrograms[getCachedProgramIndex(variant, mutableSpecConstants)]); +} + bool FMaterial::hasParameter(const char* name) const noexcept { return mUniformInterfaceBlock.hasField(name) || mSamplerInterfaceBlock.hasSampler(name) || @@ -529,14 +554,15 @@ bool FMaterial::hasVariant(Variant const variant) const noexcept { } void FMaterial::prepareProgramSlow(Variant const variant, + const backend::Program::MutableSpecConstantsInfo mutableSpecConstants, backend::CompilerPriorityQueue const priorityQueue) const noexcept { assert_invariant(mEngine.hasFeatureLevel(mFeatureLevel)); switch (getMaterialDomain()) { case MaterialDomain::SURFACE: - getSurfaceProgramSlow(variant, priorityQueue); + getSurfaceProgramSlow(variant, mutableSpecConstants, priorityQueue); break; case MaterialDomain::POST_PROCESS: - getPostProcessProgramSlow(variant, priorityQueue); + getPostProcessProgramSlow(variant, mutableSpecConstants, priorityQueue); break; case MaterialDomain::COMPUTE: // TODO: implement MaterialDomain::COMPUTE @@ -545,6 +571,7 @@ void FMaterial::prepareProgramSlow(Variant const variant, } void FMaterial::getSurfaceProgramSlow(Variant const variant, + const backend::Program::MutableSpecConstantsInfo mutableSpecConstants, CompilerPriorityQueue const priorityQueue) const noexcept { // filterVariant() has already been applied in generateCommands(), shouldn't be needed here // if we're unlit, we don't have any bits that correspond to lit materials @@ -555,25 +582,25 @@ void FMaterial::getSurfaceProgramSlow(Variant const variant, Variant const vertexVariant = Variant::filterVariantVertex(variant); Variant const fragmentVariant = Variant::filterVariantFragment(variant); - Program pb{ getProgramWithVariants(variant, vertexVariant, fragmentVariant) }; + Program pb{ getProgramWithVariants(variant, vertexVariant, fragmentVariant, mutableSpecConstants) }; pb.priorityQueue(priorityQueue); pb.multiview( mEngine.getConfig().stereoscopicType == StereoscopicType::MULTIVIEW && Variant::isStereoVariant(variant)); - createAndCacheProgram(std::move(pb), variant); + createAndCacheProgram(std::move(pb), variant, mutableSpecConstants); } void FMaterial::getPostProcessProgramSlow(Variant const variant, + const backend::Program::MutableSpecConstantsInfo mutableSpecConstants, CompilerPriorityQueue const priorityQueue) const noexcept { - Program pb{ getProgramWithVariants(variant, variant, variant) }; + Program pb{ getProgramWithVariants(variant, variant, variant, mutableSpecConstants) }; pb.priorityQueue(priorityQueue); - createAndCacheProgram(std::move(pb), variant); + createAndCacheProgram(std::move(pb), variant, mutableSpecConstants); } -Program FMaterial::getProgramWithVariants( - Variant variant, - Variant vertexVariant, - Variant fragmentVariant) const { +Program FMaterial::getProgramWithVariants(Variant variant, + Variant vertexVariant, Variant fragmentVariant, + const backend::Program::MutableSpecConstantsInfo mutableSpecConstants) const { FEngine const& engine = mEngine; const ShaderModel sm = engine.getShaderModel(); const bool isNoop = engine.getBackend() == Backend::NOOP; @@ -634,29 +661,34 @@ Program FMaterial::getProgramWithVariants( mProgramDescriptorBindings[+DescriptorSetBindingPoints::PER_RENDERABLE]); program.descriptorBindings(+DescriptorSetBindingPoints::PER_MATERIAL, mProgramDescriptorBindings[+DescriptorSetBindingPoints::PER_MATERIAL]); - program.specializationConstants(mSpecializationConstants); + program.specializationConstants(mSpecializationConstants, + CONFIG_MAX_RESERVED_SPEC_CONSTANTS + mMaterialConstants.size(), mutableSpecConstants); program.pushConstants(ShaderStage::VERTEX, mPushConstants[uint8_t(ShaderStage::VERTEX)]); program.pushConstants(ShaderStage::FRAGMENT, mPushConstants[uint8_t(ShaderStage::FRAGMENT)]); - program.cacheId(hash::combine(size_t(mCacheId), variant.key)); + program.cacheId( + hash::combine(size_t(mCacheId), + hash::combine(variant.key, mutableSpecConstants.getValue()))); return program; } -void FMaterial::createAndCacheProgram(Program&& p, Variant const variant) const noexcept { +void FMaterial::createAndCacheProgram(Program&& p, Variant const variant, + Program::MutableSpecConstantsInfo const mutableSpecConstants) const noexcept { FEngine const& engine = mEngine; DriverApi& driverApi = mEngine.getDriverApi(); bool const isShared = isSharedVariant(variant); + size_t const cacheIndex = getCachedProgramIndex(variant, mutableSpecConstants); // Check if the default material has this program cached if (isShared) { FMaterial const* const pDefaultMaterial = engine.getDefaultMaterial(); if (pDefaultMaterial) { - auto const program = pDefaultMaterial->mCachedPrograms[variant.key]; + auto const program = pDefaultMaterial->mCachedPrograms[cacheIndex]; if (program) { - mCachedPrograms[variant.key] = program; + mCachedPrograms[cacheIndex] = program; return; } } @@ -665,17 +697,17 @@ void FMaterial::createAndCacheProgram(Program&& p, Variant const variant) const auto const program = driverApi.createProgram(std::move(p)); driverApi.setDebugTag(program.getId(), mName); assert_invariant(program); - mCachedPrograms[variant.key] = program; + mCachedPrograms[cacheIndex] = program; // If the default material doesn't already have this program cached, and all caching conditions // are met (Surface Domain and no custom depth shader), cache it now. // New Materials will inherit these program automatically. if (isShared) { FMaterial const* const pDefaultMaterial = engine.getDefaultMaterial(); - if (pDefaultMaterial && !pDefaultMaterial->mCachedPrograms[variant.key]) { + if (pDefaultMaterial && !pDefaultMaterial->mCachedPrograms[cacheIndex]) { // set the tag to the default material name driverApi.setDebugTag(program.getId(), mName); - pDefaultMaterial->mCachedPrograms[variant.key] = program; + pDefaultMaterial->mCachedPrograms[cacheIndex] = program; } } } @@ -723,6 +755,14 @@ size_t FMaterial::getParameters(ParameterInfo* parameters, size_t count) const n return count; } +std::optional FMaterial::getMutableConstantId(std::string_view name) const noexcept { + auto const pos = mMutableSpecializationConstantsNameToIndex.find(name); + if (pos != mMutableSpecializationConstantsNameToIndex.end()) { + return pos->second; + } + return std::nullopt; +} + #if FILAMENT_ENABLE_MATDBG // Swaps in an edited version of the original package that was used to create the material. The @@ -781,7 +821,9 @@ void FMaterial::onQueryCallback(void* userdata, VariantList* pActiveVariants) { #endif // FILAMENT_ENABLE_MATDBG -[[nodiscard]] Handle FMaterial::getProgramWithMATDBG(Variant const variant) const noexcept { +[[nodiscard]] Handle FMaterial::getProgramWithMATDBG(Variant const variant, + Program::MutableSpecConstantsInfo const mutableSpecConstants) const noexcept { + size_t cacheIndex = getCachedProgramIndex(variant, mutableSpecConstants); #if FILAMENT_ENABLE_MATDBG assert_invariant((size_t)variant.key < VARIANT_COUNT); std::unique_lock lock(mActiveProgramsLock); @@ -796,13 +838,13 @@ void FMaterial::onQueryCallback(void* userdata, VariantList* pActiveVariants) { lock.unlock(); if (isSharedVariant(variant)) { FMaterial const* const pDefaultMaterial = mEngine.getDefaultMaterial(); - if (pDefaultMaterial && pDefaultMaterial->mCachedPrograms[variant.key]) { - return pDefaultMaterial->getProgram(variant); + if (pDefaultMaterial && pDefaultMaterial->mCachedPrograms[cacheIndex]) { + return pDefaultMaterial->getProgram(variant, mutableSpecConstants); } } #endif - assert_invariant(mCachedPrograms[variant.key]); - return mCachedPrograms[variant.key]; + assert_invariant(mCachedPrograms[cacheIndex]); + return mCachedPrograms[cacheIndex]; } void FMaterial::destroyPrograms(FEngine& engine, @@ -815,7 +857,7 @@ void FMaterial::destroyPrograms(FEngine& engine, case MaterialDomain::SURFACE: { if (mIsDefaultMaterial || mHasCustomDepthShader) { // default material, or we have custom depth shaders, we destroy all variants - for (size_t k = 0, n = VARIANT_COUNT; k < n; ++k) { + for (size_t k = 0, n = cachedPrograms.size(); k < n; ++k) { if ((k & variantMask) == variantValue) { // Only destroy if the handle is valid. Not strictly needed, but we have a lot // of variants, and this generates traffic in the command queue. @@ -833,7 +875,7 @@ void FMaterial::destroyPrograms(FEngine& engine, UTILS_UNUSED_IN_RELEASE auto const UTILS_NULLABLE pDefaultMaterial = engine.getDefaultMaterial(); - for (size_t k = 0, n = VARIANT_COUNT; k < n; ++k) { + for (size_t k = 0, n = cachedPrograms.size(); k < n; ++k) { if ((k & variantMask) == variantValue) { // Only destroy if the handle is valid. Not strictly needed, but we have a lot // of variant, and this generates traffic in the command queue. @@ -845,7 +887,7 @@ void FMaterial::destroyPrograms(FEngine& engine, // During Engine::shutdown, auto-cleanup destroys the // default material first assert_invariant(!pDefaultMaterial || - pDefaultMaterial->mCachedPrograms[k]); + pDefaultMaterial->mCachedPrograms[k & (VARIANT_COUNT - 1)]); // we don't own this variant, skip, but clear the entry. cachedPrograms[k].clear(); continue; @@ -859,7 +901,7 @@ void FMaterial::destroyPrograms(FEngine& engine, break; } case MaterialDomain::POST_PROCESS: { - for (size_t k = 0, n = POST_PROCESS_VARIANT_COUNT; k < n; ++k) { + for (size_t k = 0, n = cachedPrograms.size(); k < n; ++k) { if ((k & variantMask) == variantValue) { // Only destroy if the handle is valid. Not strictly needed, but we have a lot // of variant, and this generates traffic in the command queue. @@ -871,8 +913,13 @@ void FMaterial::destroyPrograms(FEngine& engine, break; } case MaterialDomain::COMPUTE: { - // Compute programs don't have variants - driverApi.destroyProgram(std::move(cachedPrograms[0])); + for (size_t k = 0, n = cachedPrograms.size(); k < n; ++k) { + // Only destroy if the handle is valid. Not strictly needed, but we have a lot + // of variant, and this generates traffic in the command queue. + if (cachedPrograms[k]) { + driverApi.destroyProgram(std::move(cachedPrograms[k])); + } + } break; } } @@ -1107,6 +1154,19 @@ void FMaterial::processSpecializationConstants(FEngine& engine, Builder const& b } } +void FMaterial::processMutableSpecializationConstants(FEngine& engine, Builder const& builder, + MaterialParser const* const parser) { + // Older materials won't have a mutable constants chunk, but that's okay. + parser->getMutableConstants(&mMaterialMutableConstants); + for (size_t i = 0, c = mMaterialMutableConstants.size(); i < c; i++) { + auto& item = mMaterialMutableConstants[i]; + // the key can be a string_view because mMaterialMutableConstants owns the CString + std::string_view const key{ item.name.data(), item.name.size() }; + mMutableSpecializationConstantsNameToIndex[key] = i; + mDefaultMutableSpecializationConstants.set(i, item.defaultValue); + } +} + void FMaterial::processPushConstants(FEngine&, MaterialParser const* parser) { FixedCapacityVector& vertexConstants = mPushConstants[uint8_t(ShaderStage::VERTEX)]; @@ -1162,7 +1222,8 @@ void FMaterial::precacheDepthVariants(FEngine& engine) { } assert_invariant(Variant::isValidDepthVariant(variant)); if (hasVariant(variant)) { - prepareProgram(variant); + // The default material shouldn't ever have mutable spec constants. + prepareProgram(variant, Program::MutableSpecConstantsInfo(0)); } } return; @@ -1175,9 +1236,16 @@ void FMaterial::precacheDepthVariants(FEngine& engine) { FMaterial const* const pDefaultMaterial = engine.getDefaultMaterial(); assert_invariant(pDefaultMaterial); auto const allDepthVariants = VariantUtils::getDepthVariants(); + int mutableConstantVariations = 1 << mMaterialMutableConstants.size(); for (auto const variant: allDepthVariants) { assert_invariant(Variant::isValidDepthVariant(variant)); - mCachedPrograms[variant.key] = pDefaultMaterial->mCachedPrograms[variant.key]; + size_t defaultCacheIndex = pDefaultMaterial->getCachedProgramIndex(variant, + Program::MutableSpecConstantsInfo(0)); + for (int spec = 0; spec < mutableConstantVariations; spec++) { + size_t cacheIndex = getCachedProgramIndex(variant, + Program::MutableSpecConstantsInfo(spec)); + mCachedPrograms[cacheIndex] = pDefaultMaterial->mCachedPrograms[defaultCacheIndex]; + } } } } diff --git a/filament/src/details/Material.h b/filament/src/details/Material.h index ac6f0e76af..563437e794 100644 --- a/filament/src/details/Material.h +++ b/filament/src/details/Material.h @@ -122,9 +122,8 @@ public: FEngine& getEngine() const noexcept { return mEngine; } - bool isCached(Variant const variant) const noexcept { - return bool(mCachedPrograms[variant.key]); - } + bool isCached(Variant const variant, + const backend::Program::MutableSpecConstantsInfo mutableSpecConstants) const noexcept; void invalidate(Variant::type_t variantMask = 0, Variant::type_t variantValue = 0) noexcept; @@ -132,26 +131,31 @@ public: // Must be called outside of backend render pass. // Must be called before getProgram() below. void prepareProgram(Variant const variant, + backend::Program::MutableSpecConstantsInfo const mutableSpecConstants, backend::CompilerPriorityQueue const priorityQueue = CompilerPriorityQueue::HIGH) const noexcept { // prepareProgram() is called for each RenderPrimitive in the scene, so it must be efficient. - if (UTILS_UNLIKELY(!isCached(variant))) { - prepareProgramSlow(variant, priorityQueue); + if (UTILS_UNLIKELY(!isCached(variant, mutableSpecConstants))) { + prepareProgramSlow(variant, mutableSpecConstants, priorityQueue); } } - // getProgram returns the backend program for the material's given variant. + // getProgram returns the backend program for the material's given variant and set of mutable + // spec constants. // Must be called after prepareProgram(). [[nodiscard]] - backend::Handle getProgram(Variant const variant) const noexcept { + backend::Handle getProgram(Variant const variant, + backend::Program::MutableSpecConstantsInfo const mutableSpecConstants) const noexcept { #if FILAMENT_ENABLE_MATDBG - return getProgramWithMATDBG(variant); + return getProgramWithMATDBG(variant, mutableSpecConstants); #endif - assert_invariant(mCachedPrograms[variant.key]); - return mCachedPrograms[variant.key]; + size_t index = getCachedProgramIndex(variant, mutableSpecConstants); + assert_invariant(mCachedPrograms[index]); + return mCachedPrograms[index]; } [[nodiscard]] - backend::Handle getProgramWithMATDBG(Variant variant) const noexcept; + backend::Handle getProgramWithMATDBG(Variant variant, + backend::Program::MutableSpecConstantsInfo const mutableSpecConstants) const noexcept; bool isVariantLit() const noexcept { return mIsVariantLit; } @@ -208,6 +212,12 @@ public: } size_t getParameters(ParameterInfo* parameters, size_t count) const noexcept; + uint32_t getMutableConstantCount() const noexcept { return mMaterialMutableConstants.size(); } + std::optional getMutableConstantId(std::string_view name) const noexcept; + backend::Program::MutableSpecConstantsInfo getDefaultMutableConstants() const noexcept { + return mDefaultMutableSpecializationConstants; + } + uint32_t generateMaterialInstanceId() const noexcept { return mMaterialInstanceId++; } void destroyPrograms(FEngine& engine, @@ -261,34 +271,48 @@ public: private: bool hasVariant(Variant variant) const noexcept; void prepareProgramSlow(Variant variant, + backend::Program::MutableSpecConstantsInfo mutableSpecConstants, CompilerPriorityQueue priorityQueue) const noexcept; void getSurfaceProgramSlow(Variant variant, + backend::Program::MutableSpecConstantsInfo mutableSpecConstants, CompilerPriorityQueue priorityQueue) const noexcept; void getPostProcessProgramSlow(Variant variant, + backend::Program::MutableSpecConstantsInfo mutableSpecConstants, CompilerPriorityQueue priorityQueue) const noexcept; backend::Program getProgramWithVariants(Variant variant, - Variant vertexVariant, Variant fragmentVariant) const; + Variant vertexVariant, Variant fragmentVariant, + backend::Program::MutableSpecConstantsInfo mutableSpecConstants) const; void processBlendingMode(MaterialParser const* parser); void processSpecializationConstants(FEngine& engine, Builder const& builder, MaterialParser const* parser); + void processMutableSpecializationConstants(FEngine& engine, Builder const& builder, + MaterialParser const* parser); + void processPushConstants(FEngine& engine, MaterialParser const* parser); void precacheDepthVariants(FEngine& engine); void processDescriptorSets(FEngine& engine, MaterialParser const* parser); - void createAndCacheProgram(backend::Program&& p, Variant variant) const noexcept; + void createAndCacheProgram(backend::Program&& p, Variant variant, + backend::Program::MutableSpecConstantsInfo mutableSpecConstants) const noexcept; inline bool isSharedVariant(Variant const variant) const { return (mMaterialDomain == MaterialDomain::SURFACE) && !mIsDefaultMaterial && !mHasCustomDepthShader && Variant::isValidDepthVariant(variant); } + inline size_t getCachedProgramIndex(const Variant variant, + const backend::Program::MutableSpecConstantsInfo mutableSpecConstants) const noexcept { + return static_cast(variant.key) | + (static_cast(mutableSpecConstants.getValue()) << mVariantBits); + } + // try to order by frequency of use - mutable std::array, VARIANT_COUNT> mCachedPrograms; + mutable utils::FixedCapacityVector> mCachedPrograms; DescriptorSetLayout mPerViewDescriptorSetLayout; DescriptorSetLayout mPerViewDescriptorSetLayoutVsm; DescriptorSetLayout mDescriptorSetLayout; @@ -324,6 +348,7 @@ private: bool mHasCustomDepthShader = false; bool mIsDefaultMaterial = false; bool mSpecularAntiAliasing = false; + uint8_t mVariantBits; // reserve some space to construct the default material instance mutable FMaterialInstance* mDefaultMaterialInstance = nullptr; @@ -348,6 +373,13 @@ private: // current specialization constants for the HwProgram utils::FixedCapacityVector mSpecializationConstants; + // Mutable constants defined by this Material. + utils::FixedCapacityVector mMaterialMutableConstants; + // A map from the Constant name to the mMutableMaterialConstant index + std::unordered_map mMutableSpecializationConstantsNameToIndex; + // Default values for the mutable constants. + backend::Program::MutableSpecConstantsInfo mDefaultMutableSpecializationConstants; + // current push constants for the HwProgram std::array, backend::Program::SHADER_TYPE_COUNT> diff --git a/filament/src/details/MaterialInstance.cpp b/filament/src/details/MaterialInstance.cpp index 69e87c4eb1..b4cfaabb47 100644 --- a/filament/src/details/MaterialInstance.cpp +++ b/filament/src/details/MaterialInstance.cpp @@ -63,6 +63,7 @@ FMaterialInstance::FMaterialInstance(FEngine& engine, FMaterial const* material, const char* name) noexcept : mMaterial(material), mDescriptorSet("MaterialInstance", material->getDescriptorSetLayout()), + mConstants(material->getDefaultMutableConstants()), mCulling(CullingMode::BACK), mShadowCulling(CullingMode::BACK), mDepthFunc(RasterState::DepthFunc::LE), @@ -128,6 +129,7 @@ FMaterialInstance::FMaterialInstance(FEngine& engine, mTextureParameters(other->mTextureParameters), mDescriptorSet(other->mDescriptorSet.duplicate( "MaterialInstance", mMaterial->getDescriptorSetLayout())), + mConstants(other->mConstants), mPolygonOffset(other->mPolygonOffset), mStencilState(other->mStencilState), mMaskThreshold(other->mMaskThreshold), diff --git a/filament/src/details/MaterialInstance.h b/filament/src/details/MaterialInstance.h index 72f59d3760..0134254e5a 100644 --- a/filament/src/details/MaterialInstance.h +++ b/filament/src/details/MaterialInstance.h @@ -31,6 +31,7 @@ #include #include +#include #include #include @@ -66,7 +67,7 @@ public: void terminate(FEngine& engine); void commitStreamUniformAssociations(FEngine::DriverApi& driver); - + void commit(FEngine& engine) const; void commit(FEngine::DriverApi& driver) const; @@ -242,6 +243,10 @@ public: using MaterialInstance::setParameter; + const backend::Program::MutableSpecConstantsInfo getMutableSpecConstants() const noexcept { + return mConstants; + } + private: friend class FMaterial; friend class MaterialInstance; @@ -264,6 +269,12 @@ private: template T getParameterImpl(std::string_view name) const; + template + void setConstantImpl(std::string_view name, T const& value); + + template + T getConstantImpl(std::string_view name) const; + // keep these grouped, they're accessed together in the render-loop FMaterial const* mMaterial = nullptr; @@ -277,6 +288,7 @@ private: mutable DescriptorSet mDescriptorSet; UniformBuffer mUniforms; bool mHasStreamUniformAssociations = false; + backend::Program::MutableSpecConstantsInfo mConstants; backend::PolygonOffset mPolygonOffset{}; backend::StencilState mStencilState{}; diff --git a/libs/filabridge/include/filament/MaterialChunkType.h b/libs/filabridge/include/filament/MaterialChunkType.h index 4ca08f4c68..2ecc410d17 100644 --- a/libs/filabridge/include/filament/MaterialChunkType.h +++ b/libs/filabridge/include/filament/MaterialChunkType.h @@ -54,6 +54,7 @@ enum UTILS_PUBLIC ChunkType : uint64_t { MaterialDescriptorSetLayoutInfo = charTo64bitNum("MAT_DSLI"), MaterialProperties = charTo64bitNum("MAT_PROP"), MaterialConstants = charTo64bitNum("MAT_CONS"), + MaterialMutableConstants = charTo64bitNum("MAT_MCNS"), MaterialPushConstants = charTo64bitNum("MAT_PCON"), MaterialName = charTo64bitNum("MAT_NAME"), diff --git a/libs/filabridge/include/private/filament/ConstantInfo.h b/libs/filabridge/include/private/filament/ConstantInfo.h index 04ceaf15be..4da401d3e6 100644 --- a/libs/filabridge/include/private/filament/ConstantInfo.h +++ b/libs/filabridge/include/private/filament/ConstantInfo.h @@ -30,7 +30,19 @@ struct MaterialConstant { ConstantType type; MaterialConstant() = default; - MaterialConstant(const char* name, ConstantType type) : name(name), type(type) {} + MaterialConstant(utils::CString name, ConstantType type) : name(std::move(name)), type(type) {} +}; + +struct MaterialMutableConstant { + utils::CString name; + // The Filament engine stores mutable spec constants as a bitfield, meaning that must override + // all of the default values built into in the shader souces. Therefore, unlike the immutable + // constants above, we have to store the default values in the material metadata. + bool defaultValue; + + MaterialMutableConstant() = default; + MaterialMutableConstant(utils::CString name, bool defaultValue) + : name(std::move(name)), defaultValue(defaultValue) {} }; } diff --git a/libs/filamat/include/filamat/MaterialBuilder.h b/libs/filamat/include/filamat/MaterialBuilder.h index 2255d90058..271bc7b505 100644 --- a/libs/filamat/include/filamat/MaterialBuilder.h +++ b/libs/filamat/include/filamat/MaterialBuilder.h @@ -307,14 +307,15 @@ public: MaterialBuilder& parameter(const char* name, size_t size, UniformType type, ParameterPrecision precision = ParameterPrecision::DEFAULT); - //! Add a constant parameter to this material. + //! Add a (mutable) constant parameter to this material. template using is_supported_constant_parameter_t = typename std::enable_if< std::is_same::value || std::is_same::value || std::is_same::value>::type; template> - MaterialBuilder& constant(const char *name, ConstantType type, T defaultValue = 0); + MaterialBuilder& constant(const char* name, ConstantType type, bool isMutable, + T defaultValue = 0); /** * Add a sampler parameter to this material. @@ -871,6 +872,7 @@ private: PropertyList mProperties; ParameterList mParameters; ConstantList mConstants; + ConstantList mMutableConstants; PushConstantList mPushConstants; SubpassList mSubpasses; VariableList mVariables; diff --git a/libs/filamat/src/MaterialBuilder.cpp b/libs/filamat/src/MaterialBuilder.cpp index de0344f1df..d88d42e282 100644 --- a/libs/filamat/src/MaterialBuilder.cpp +++ b/libs/filamat/src/MaterialBuilder.cpp @@ -317,12 +317,19 @@ MaterialBuilder& MaterialBuilder::parameter(const char* name, SamplerType sample } template -MaterialBuilder& MaterialBuilder::constant(const char* name, ConstantType const type, T defaultValue) { - auto result = std::find_if(mConstants.begin(), mConstants.end(), [name](const Constant& c) { - return c.name == CString(name); - }); - FILAMENT_CHECK_POSTCONDITION(result == mConstants.end()) +MaterialBuilder& MaterialBuilder::constant(const char* name, ConstantType const type, + bool isMutable, T defaultValue) { + ConstantList& constants = isMutable ? mMutableConstants : mConstants; + const ConstantList& otherConstants = isMutable ? mConstants : mMutableConstants; + auto result = std::find_if(constants.begin(), constants.end(), + [name](const Constant& c) { return c.name == CString(name); }); + FILAMENT_CHECK_POSTCONDITION(result == constants.end()) << "There is already a constant parameter present with the name " << name << "."; + auto otherResult = std::find_if(otherConstants.begin(), otherConstants.end(), + [name](const Constant& c) { return c.name == CString(name); }); + FILAMENT_CHECK_POSTCONDITION(otherResult == otherConstants.end()) + << "There is already a constant parameter present with the name " << name << "."; + Constant constant { .name = CString(name), .type = type, @@ -354,15 +361,20 @@ MaterialBuilder& MaterialBuilder::constant(const char* name, ConstantType const assert_invariant(false); } - mConstants.push_back(constant); + if (isMutable) { + FILAMENT_CHECK_POSTCONDITION(type == ConstantType::BOOL) + << "Mutable spec constants must be bool type."; + } + + constants.push_back(constant); return *this; } template MaterialBuilder& MaterialBuilder::constant( - const char* name, ConstantType type, int32_t defaultValue); + const char* name, ConstantType type, bool isMutable, int32_t defaultValue); template MaterialBuilder& MaterialBuilder::constant( - const char* name, ConstantType type, float defaultValue); + const char* name, ConstantType type, bool isMutable, float defaultValue); template MaterialBuilder& MaterialBuilder::constant( - const char* name, ConstantType type, bool defaultValue); + const char* name, ConstantType type, bool isMutable, bool defaultValue); MaterialBuilder& MaterialBuilder::buffer(BufferInterfaceBlock bib) { FILAMENT_CHECK_POSTCONDITION(mBuffers.size() < MAX_BUFFERS_COUNT) << "Too many buffers"; @@ -907,7 +919,7 @@ bool MaterialBuilder::generateShaders(JobSystem& jobSystem, const std::vector constantsEntry(mConstants.size()); std::transform(mConstants.begin(), mConstants.end(), constantsEntry.begin(), - [](Constant const& c) { return MaterialConstant(c.name.c_str(), c.type); }); + [](Constant const& c) { return MaterialConstant(c.name, c.type); }); container.push(std::move(constantsEntry)); + FixedCapacityVector mutableConstantsEntry(mMutableConstants.size()); + std::transform(mMutableConstants.begin(), mMutableConstants.end(), mutableConstantsEntry.begin(), + [](Constant const& c) { return MaterialMutableConstant(c.name, c.defaultValue.b); }); + container.push(std::move(mutableConstantsEntry)); + FixedCapacityVector pushConstantsEntry(mPushConstants.size()); std::transform(mPushConstants.begin(), mPushConstants.end(), pushConstantsEntry.begin(), [](PushConstant const& c) { diff --git a/libs/filamat/src/eiff/MaterialInterfaceBlockChunk.cpp b/libs/filamat/src/eiff/MaterialInterfaceBlockChunk.cpp index 5b1556cd9a..552ac9efc9 100644 --- a/libs/filamat/src/eiff/MaterialInterfaceBlockChunk.cpp +++ b/libs/filamat/src/eiff/MaterialInterfaceBlockChunk.cpp @@ -117,6 +117,20 @@ void MaterialConstantParametersChunk::flatten(Flattener& f) { // ------------------------------------------------------------------------------------------------ +MaterialMutableConstantParametersChunk::MaterialMutableConstantParametersChunk( + FixedCapacityVector constants) + : Chunk(MaterialMutableConstants), mConstants(std::move(constants)) {} + +void MaterialMutableConstantParametersChunk::flatten(Flattener& f) { + f.writeUint64(mConstants.size()); + for (const auto& constant : mConstants) { + f.writeString(constant.name.c_str()); + f.writeBool(constant.defaultValue); + } +} + +// ------------------------------------------------------------------------------------------------ + MaterialPushConstantParametersChunk::MaterialPushConstantParametersChunk( CString const& structVarName, FixedCapacityVector constants) : Chunk(MaterialPushConstants), diff --git a/libs/filamat/src/eiff/MaterialInterfaceBlockChunk.h b/libs/filamat/src/eiff/MaterialInterfaceBlockChunk.h index 6d428400ab..7f43ee7180 100644 --- a/libs/filamat/src/eiff/MaterialInterfaceBlockChunk.h +++ b/libs/filamat/src/eiff/MaterialInterfaceBlockChunk.h @@ -18,6 +18,7 @@ #define TNT_FILAMAT_MAT_INTEFFACE_BLOCK_CHUNK_H #include "Chunk.h" +#include "private/filament/ConstantInfo.h" #include @@ -91,6 +92,20 @@ private: // ------------------------------------------------------------------------------------------------ +class MaterialMutableConstantParametersChunk final : public Chunk { +public: + explicit MaterialMutableConstantParametersChunk( + FixedCapacityVector constants); + ~MaterialMutableConstantParametersChunk() override = default; + +private: + void flatten(Flattener&) override; + + FixedCapacityVector mConstants; +}; + +// ------------------------------------------------------------------------------------------------ + class MaterialPushConstantParametersChunk final : public Chunk { public: explicit MaterialPushConstantParametersChunk(CString const& structVarName, diff --git a/libs/filamat/src/shaders/ShaderGenerator.cpp b/libs/filamat/src/shaders/ShaderGenerator.cpp index 6b305f5747..8bcdd1459b 100644 --- a/libs/filamat/src/shaders/ShaderGenerator.cpp +++ b/libs/filamat/src/shaders/ShaderGenerator.cpp @@ -283,10 +283,17 @@ void ShaderGenerator::appendShader(io::sstream& ss, } } -void ShaderGenerator::generateUserSpecConstants( - const CodeGenerator& cg, io::sstream& fs, MaterialBuilder::ConstantList const& constants) { +void ShaderGenerator::generateAllUserSpecConstants(const CodeGenerator& cg, io::sstream& fs, + MaterialBuilder::ConstantList const& constants, + MaterialBuilder::ConstantList const& mutableConstants) { + generateUserSpecConstants(cg, fs, constants, 0); + generateUserSpecConstants(cg, fs, mutableConstants, constants.size()); +} + +void ShaderGenerator::generateUserSpecConstants(const CodeGenerator& cg, io::sstream& fs, + MaterialBuilder::ConstantList const& constants, size_t offset) { // Constants 0 to CONFIG_MAX_RESERVED_SPEC_CONSTANTS - 1 are reserved by Filament. - size_t index = CONFIG_MAX_RESERVED_SPEC_CONSTANTS; + size_t index = CONFIG_MAX_RESERVED_SPEC_CONSTANTS + offset; for (const auto& constant : constants) { std::string const fullName = std::string("materialConstants_") + constant.name.c_str(); switch (constant.type) { @@ -314,6 +321,7 @@ ShaderGenerator::ShaderGenerator( MaterialBuilder::OutputList const& outputs, MaterialBuilder::PreprocessorDefineList const& defines, MaterialBuilder::ConstantList const& constants, + MaterialBuilder::ConstantList const& mutableConstants, MaterialBuilder::PushConstantList const& pushConstants, CString const& materialCode, size_t const lineOffset, CString const& materialVertexCode, size_t const vertexLineOffset, @@ -336,6 +344,7 @@ ShaderGenerator::ShaderGenerator( mMaterialDomain = materialDomain; mDefines = defines; mConstants = constants; + mMutableConstants = mutableConstants; mPushConstants = pushConstants; if (mMaterialFragmentCode.empty()) { @@ -392,7 +401,7 @@ std::string ShaderGenerator::createSurfaceVertexProgram(ShaderModel const shader cg.generateCommonProlog(vs, ShaderStage::VERTEX, material, variant); - generateUserSpecConstants(cg, vs, mConstants); + generateAllUserSpecConstants(cg, vs, mConstants, mMutableConstants); // note: even if the user vertex shader is empty, we can't use the "optimized" version if // we're in masked mode because fragment shader needs the color varyings @@ -525,7 +534,7 @@ std::string ShaderGenerator::createSurfaceFragmentProgram(ShaderModel const shad io::sstream fs; cg.generateCommonProlog(fs, ShaderStage::FRAGMENT, material, variant); - generateUserSpecConstants(cg, fs, mConstants); + generateAllUserSpecConstants(cg, fs, mConstants, mMutableConstants); generateSurfaceMaterialVariantDefines( fs, ShaderStage::FRAGMENT, featureLevel, material, variant); @@ -681,7 +690,7 @@ std::string ShaderGenerator::createSurfaceComputeProgram(ShaderModel const shade cg.generateCommonProlog(s, ShaderStage::COMPUTE, material, {}); - generateUserSpecConstants(cg, s, mConstants); + generateAllUserSpecConstants(cg, s, mConstants, mMutableConstants); CodeGenerator::generateSurfaceTypes(s, ShaderStage::COMPUTE); @@ -721,7 +730,7 @@ std::string ShaderGenerator::createPostProcessVertexProgram(ShaderModel const sm io::sstream vs; cg.generateCommonProlog(vs, ShaderStage::VERTEX, material, {}); - generateUserSpecConstants(cg, vs, mConstants); + generateAllUserSpecConstants(cg, vs, mConstants, mMutableConstants); CodeGenerator::generateDefine(vs, "LOCATION_POSITION", uint32_t(POSITION)); @@ -765,7 +774,7 @@ std::string ShaderGenerator::createPostProcessFragmentProgram(ShaderModel const io::sstream fs; cg.generateCommonProlog(fs, ShaderStage::FRAGMENT, material, {}); - generateUserSpecConstants(cg, fs, mConstants); + generateAllUserSpecConstants(cg, fs, mConstants, mMutableConstants); generatePostProcessMaterialVariantDefines(fs, PostProcessVariant(variant)); diff --git a/libs/filamat/src/shaders/ShaderGenerator.h b/libs/filamat/src/shaders/ShaderGenerator.h index 9d9b470a91..c04aa310b9 100644 --- a/libs/filamat/src/shaders/ShaderGenerator.h +++ b/libs/filamat/src/shaders/ShaderGenerator.h @@ -51,6 +51,7 @@ public: MaterialBuilder::OutputList const& outputs, MaterialBuilder::PreprocessorDefineList const& defines, MaterialBuilder::ConstantList const& constants, + MaterialBuilder::ConstantList const& mutableConstants, MaterialBuilder::PushConstantList const& pushConstants, utils::CString const& materialCode, size_t lineOffset, @@ -104,9 +105,14 @@ private: static void generatePostProcessMaterialVariantDefines(utils::io::sstream& out, filament::PostProcessVariant variant) noexcept; + static void generateAllUserSpecConstants(const CodeGenerator& cg, utils::io::sstream& fs, + MaterialBuilder::ConstantList const& constants, + MaterialBuilder::ConstantList const& mutableConstants); + static void generateUserSpecConstants( const CodeGenerator& cg, utils::io::sstream& fs, - MaterialBuilder::ConstantList const& constants); + MaterialBuilder::ConstantList const& constants, + size_t offset); std::string createPostProcessVertexProgram(filament::backend::ShaderModel sm, MaterialBuilder::TargetApi targetApi, MaterialBuilder::TargetLanguage targetLanguage, @@ -135,6 +141,7 @@ private: MaterialBuilder::MaterialDomain mMaterialDomain; MaterialBuilder::PreprocessorDefineList mDefines; MaterialBuilder::ConstantList mConstants; + MaterialBuilder::ConstantList mMutableConstants; MaterialBuilder::PushConstantList mPushConstants; utils::CString mMaterialFragmentCode; // fragment or compute code utils::CString mMaterialVertexCode; diff --git a/libs/filamat/tests/test_filamat.cpp b/libs/filamat/tests/test_filamat.cpp index 057438c241..3fdd854eb9 100644 --- a/libs/filamat/tests/test_filamat.cpp +++ b/libs/filamat/tests/test_filamat.cpp @@ -888,10 +888,10 @@ TEST_F(MaterialCompiler, ConstantParameter) { } )"); filamat::MaterialBuilder builder; - builder.constant("myFloatConstant", ConstantType::FLOAT, 1.0f); - builder.constant("myIntConstant", ConstantType::INT, 123); - builder.constant("myBoolConstant", ConstantType::BOOL, true); - builder.constant("myOtherBoolConstant", ConstantType::BOOL); + builder.constant("myFloatConstant", ConstantType::FLOAT, /*isMutable=*/false, 1.0f); + builder.constant("myIntConstant", ConstantType::INT, /*isMutable=*/false, 123); + builder.constant("myBoolConstant", ConstantType::BOOL, /*isMutable=*/false, true); + builder.constant("myOtherBoolConstant", ConstantType::BOOL, /*isMutable=*/false); builder.shading(filament::Shading::LIT); builder.material(shaderCode.c_str()); @@ -904,8 +904,8 @@ TEST_F(MaterialCompiler, ConstantParameterSameName) { #ifdef __EXCEPTIONS EXPECT_THROW({ filamat::MaterialBuilder builder; - builder.constant("myFloatConstant", ConstantType::FLOAT, 1.0f); - builder.constant("myFloatConstant", ConstantType::FLOAT, 1.0f); + builder.constant("myFloatConstant", ConstantType::FLOAT, /*isMutable=*/false, 1.0f); + builder.constant("myFloatConstant", ConstantType::FLOAT, /*isMutable=*/false, 1.0f); }, utils::PostconditionPanic); #endif } @@ -914,7 +914,7 @@ TEST_F(MaterialCompiler, ConstantParameterWrongType) { #ifdef __EXCEPTIONS EXPECT_THROW({ filamat::MaterialBuilder builder; - builder.constant("myFloatConstant", ConstantType::FLOAT, 10); + builder.constant("myFloatConstant", ConstantType::FLOAT, /*isMutable=*/false, 10); }, utils::PostconditionPanic); #endif } diff --git a/samples/CMakeLists.txt b/samples/CMakeLists.txt index df9aefe810..c569d393fe 100644 --- a/samples/CMakeLists.txt +++ b/samples/CMakeLists.txt @@ -19,6 +19,7 @@ set(RESOURCE_BINS) set(MATERIAL_SRCS materials/aiDefaultMat.mat materials/bakedColor.mat + materials/bakedColorWithMutableConstants.mat materials/bakedTexture.mat materials/aoPreview.mat materials/arrayTexture.mat @@ -278,6 +279,7 @@ if (NOT ANDROID) add_demo(lightbulb) add_demo(material_sandbox) add_demo(multiple_windows) + add_demo(mutable_constants) # Tint does not support setting gl_PointSize, disable the relevant sample if using WebGPU # https://github.com/gpuweb/gpuweb/issues/1190 if (NOT FILAMENT_SUPPORTS_WEBGPU) diff --git a/samples/materials/bakedColorWithMutableConstants.mat b/samples/materials/bakedColorWithMutableConstants.mat new file mode 100644 index 0000000000..19d122f7e9 --- /dev/null +++ b/samples/materials/bakedColorWithMutableConstants.mat @@ -0,0 +1,44 @@ +material { + name : BakedColorWithMutableConstants, + requires : [ + color + ], + constants : [ + { + name: swizzle, + type: bool, + mutable: true, + default: true, + }, + { + name: darken, + type: bool, + mutable: true, + }, + { + // In generated shader sources, we first specify immutable + // specialization constants, then mutable specialization constants. + // By including this dummy constant, we offset the ID of the first + // mutable constant by one. This is done here purely for testing + // purposes. + name: dummy, + type: bool, + } + ], + shadingModel : unlit, + culling : none, +} + +fragment { + void material(inout MaterialInputs material) { + prepareMaterial(material); + if (materialConstants_swizzle) { + material.baseColor = getColor().gbra; + } else { + material.baseColor = getColor(); + } + if (materialConstants_darken) { + material.baseColor.rgb *= 0.5; + } + } +} diff --git a/samples/mutable_constants.cpp b/samples/mutable_constants.cpp new file mode 100644 index 0000000000..9a4ccafa88 --- /dev/null +++ b/samples/mutable_constants.cpp @@ -0,0 +1,214 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include + +#include + +#include +#include + + +#include "generated/resources/resources.h" + +using namespace filament; +using utils::Entity; +using utils::EntityManager; + +struct App { + Config config; + VertexBuffer* vb; + IndexBuffer* ib; + Material* mat; + Camera* cam; + Entity camera; + Skybox* skybox; + Entity renderable; +}; + +struct Vertex { + filament::math::float2 position; + uint32_t color; +}; + +static const Vertex TRIANGLE_VERTICES[3] = { + {{1, 0}, 0xffff0000u}, + {{cos(M_PI * 2 / 3), sin(M_PI * 2 / 3)}, 0xff00ff00u}, + {{cos(M_PI * 4 / 3), sin(M_PI * 4 / 3)}, 0xff0000ffu}, +}; + +static constexpr uint16_t TRIANGLE_INDICES[3] = { 0, 1, 2 }; + +static void printUsage(char* name) { + std::string exec_name(utils::Path(name).getName()); + std::string usage( + "MUTABLE_CONSTANTS renders a spinning colored triangle\n" + "Usage:\n" + " MUTABLE_CONSTANTS [options]\n" + "Options:\n" + " --help, -h\n" + " Prints this message\n\n" + " --api, -a\n" + " Specify the backend API: opengl, vulkan, metal, or webgpu\n" + " (note: webgpu is a no-op for now, printing backend\n" + " component info if FILAMENT_BACKEND_DEBUG_FLAG, \n" + " set at build time, includes the \n" + " FWGPU_PRINT_SYSTEM bit flag 0x2)\n" + ); + const std::string from("MUTABLE_CONSTANTS"); + for (size_t pos = usage.find(from); pos != std::string::npos; pos = usage.find(from, pos)) { + usage.replace(pos, from.length(), exec_name); + } + std::cout << usage; +} + +static int handleCommandLineArguments(int argc, char* argv[], App* app) { + static constexpr const char* OPTSTR = "ha:"; + static const struct option OPTIONS[] = { + { "help", no_argument, nullptr, 'h' }, + { "api", required_argument, nullptr, 'a' }, + { nullptr, 0, nullptr, 0 } + }; + int opt; + int option_index = 0; + while ((opt = getopt_long(argc, argv, OPTSTR, OPTIONS, &option_index)) >= 0) { + std::string arg(optarg ? optarg : ""); + switch (opt) { + default: + case 'h': + printUsage(argv[0]); + exit(0); + case 'a': + if (arg == "opengl") { + app->config.backend = Engine::Backend::OPENGL; + } else if (arg == "vulkan") { + app->config.backend = Engine::Backend::VULKAN; + } else if (arg == "metal") { + app->config.backend = Engine::Backend::METAL; + } else if (arg == "webgpu") { + app->config.backend = Engine::Backend::WEBGPU; + } else { + std::cerr << "Unrecognized backend. Must be " + "'opengl'|'vulkan'|'metal'|'webgpu'.\n"; + exit(1); + } + break; + } + } + return optind; +} + +int main(int argc, char** argv) { + App app{}; + app.config.title = "mutable_constants"; + handleCommandLineArguments(argc, argv, &app); + + auto setup = [&app](Engine* engine, View* view, Scene* scene) { + app.skybox = Skybox::Builder().color({0.1, 0.125, 0.25, 1.0}).build(*engine); + scene->setSkybox(app.skybox); + view->setPostProcessingEnabled(false); + static_assert(sizeof(Vertex) == 12, "Strange vertex size."); + app.vb = VertexBuffer::Builder() + .vertexCount(3) + .bufferCount(1) + .attribute(VertexAttribute::POSITION, 0, VertexBuffer::AttributeType::FLOAT2, 0, 12) + .attribute(VertexAttribute::COLOR, 0, VertexBuffer::AttributeType::UBYTE4, 8, 12) + .normalized(VertexAttribute::COLOR) + .build(*engine); + app.vb->setBufferAt(*engine, 0, + VertexBuffer::BufferDescriptor(TRIANGLE_VERTICES, 36, nullptr)); + app.ib = IndexBuffer::Builder() + .indexCount(3) + .bufferType(IndexBuffer::IndexType::USHORT) + .build(*engine); + app.ib->setBuffer(*engine, + IndexBuffer::BufferDescriptor(TRIANGLE_INDICES, 6, nullptr)); + app.mat = Material::Builder().package(RESOURCES_BAKEDCOLORWITHMUTABLECONSTANTS_DATA, + RESOURCES_BAKEDCOLORWITHMUTABLECONSTANTS_SIZE) + .build(*engine); + app.renderable = EntityManager::get().create(); + RenderableManager::Builder(1) + .boundingBox({{ -1, -1, -1 }, { 1, 1, 1 }}) + .material(0, app.mat->getDefaultInstance()) + .geometry(0, RenderableManager::PrimitiveType::TRIANGLES, app.vb, app.ib, 0, 3) + .culling(false) + .receiveShadows(false) + .castShadows(false) + .build(*engine, app.renderable); + scene->addEntity(app.renderable); + app.camera = utils::EntityManager::get().create(); + app.cam = engine->createCamera(app.camera); + view->setCamera(app.cam); + }; + + auto cleanup = [&app](Engine* engine, View*, Scene*) { + engine->destroy(app.skybox); + engine->destroy(app.renderable); + engine->destroy(app.mat); + engine->destroy(app.vb); + engine->destroy(app.ib); + engine->destroyCameraComponent(app.camera); + utils::EntityManager::get().destroy(app.camera); + }; + + auto gui = [&app](Engine* engine, View* view) { + MaterialInstance* mi = app.mat->getDefaultInstance(); + bool swizzle = mi->getConstant("swizzle"); + bool darken = mi->getConstant("darken"); + ImGui::Checkbox("Swizzle", &swizzle); + ImGui::Checkbox("Darken", &darken); + if (swizzle != mi->getConstant("swizzle")) { + mi->setConstant("swizzle", swizzle); + } + if (darken != mi->getConstant("darken")) { + mi->setConstant("darken", darken); + } + }; + + FilamentApp::get().animate([&app](Engine* engine, View* view, double now) { + constexpr float ZOOM = 1.5f; + const uint32_t w = view->getViewport().width; + const uint32_t h = view->getViewport().height; + const float aspect = (float) w / h; + app.cam->setProjection(Camera::Projection::ORTHO, + -aspect * ZOOM, aspect * ZOOM, + -ZOOM, ZOOM, 0, 1); + auto& tcm = engine->getTransformManager(); + tcm.setTransform(tcm.getInstance(app.renderable), + filament::math::mat4f::rotation(now, filament::math::float3{ 0, 0, 1 })); + }); + + FilamentApp::get().run(app.config, setup, cleanup, gui); + + return 0; +} diff --git a/tools/matc/src/matc/ParametersProcessor.cpp b/tools/matc/src/matc/ParametersProcessor.cpp index 8b3350eeb9..e797b9aa99 100644 --- a/tools/matc/src/matc/ParametersProcessor.cpp +++ b/tools/matc/src/matc/ParametersProcessor.cpp @@ -154,7 +154,7 @@ static bool processParameter(MaterialBuilder& builder, const JsonishObject& json std::cerr << "parameters: name value must be STRING." << std::endl; return false; } - + const JsonishValue* transformNameValue = jsonObject.getValue("transformName"); if (transformNameValue && transformNameValue->getType() != JsonishValue::STRING) { std::cerr << "parameters: transformName value must be STRING." << std::endl; @@ -328,6 +328,19 @@ static bool processConstant(MaterialBuilder& builder, const JsonishObject& jsonO return false; } + const JsonishValue* mutableValue = jsonObject.getValue("mutable"); + bool isMutable; + if (mutableValue) { + const JsonishBool* mutableBool = mutableValue->toJsonBool(); + if (!mutableBool) { + std::cerr << "constants: mutable value must be BOOL." << std::endl; + return false; + } + isMutable = mutableBool->getBool(); + } else { + isMutable = false; + } + auto typeString = typeValue->toJsonString()->getString(); auto nameString = nameValue->toJsonString()->getString(); const JsonishValue* defaultValue = jsonObject.getValue("default"); @@ -346,7 +359,7 @@ static bool processConstant(MaterialBuilder& builder, const JsonishObject& jsonO // FIXME: Jsonish doesn't distinguish between integers and floats. intDefault = (int32_t)defaultValue->toJsonNumber()->getFloat(); } - builder.constant(nameString.c_str(), type, intDefault); + builder.constant(nameString.c_str(), type, isMutable, intDefault); break; } case ConstantType::FLOAT: { @@ -359,7 +372,7 @@ static bool processConstant(MaterialBuilder& builder, const JsonishObject& jsonO } floatDefault = defaultValue->toJsonNumber()->getFloat(); } - builder.constant(nameString.c_str(), type, floatDefault); + builder.constant(nameString.c_str(), type, isMutable, floatDefault); break; } case ConstantType::BOOL: @@ -372,7 +385,7 @@ static bool processConstant(MaterialBuilder& builder, const JsonishObject& jsonO } boolDefault = defaultValue->toJsonBool()->getBool(); } - builder.constant(nameString.c_str(), type, boolDefault); + builder.constant(nameString.c_str(), type, isMutable, boolDefault); break; } } else {