diff --git a/NEW_RELEASE_NOTES.md b/NEW_RELEASE_NOTES.md index e5a2fb03f5..2e27f192b9 100644 --- a/NEW_RELEASE_NOTES.md +++ b/NEW_RELEASE_NOTES.md @@ -8,6 +8,7 @@ appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md). ## Release notes for next branch cut +- engine: Add support for _constant parameters_, which are constants that can be specialized after material compilation. - materials: improved size reduction of OpenGL/Metal shaders by ~65% when compiling materials with size optimizations (`matc -S`) [⚠️ **Recompile Materials**] - engine: fix potential crash on Metal devices with A8X GPU (iPad Air 2) diff --git a/docs/Materials.md.html b/docs/Materials.md.html index dc45ea39c4..8db63741d9 100644 --- a/docs/Materials.md.html +++ b/docs/Materials.md.html @@ -1076,6 +1076,63 @@ fragment { } ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +### General: constants + +Type +: array of constant objects + +Value +: Each entry is an object with the properties `name` and `type`, both of `string` type. The name + must be a valid GLSL identifier. Entries also have an optional `default`, which can either be a + `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 +[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. + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ JSON +material { + constants : [ + { + name : overrideAlpha, + type : bool + }, + { + name : customAlpha, + type : float, + default : 0.5 + } + ], + shadingModel : lit, + blending : transparent, +} + +fragment { + void material(inout MaterialInputs material) { + prepareMaterial(material); + if (materialConstants_overrideAlpha) { + material.baseColor.a = materialConstants_customAlpha; + material.baseColor.rgb *= material.baseColor.a; + } + } +} +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ### General: variantFilter Type diff --git a/filament/backend/include/backend/DriverEnums.h b/filament/backend/include/backend/DriverEnums.h index 8bec4b5bea..d66ce9f7c4 100644 --- a/filament/backend/include/backend/DriverEnums.h +++ b/filament/backend/include/backend/DriverEnums.h @@ -279,6 +279,15 @@ enum class UniformType : uint8_t { STRUCT }; +/** + * Supported constant parameter types + */ + enum class ConstantType : uint8_t { + INT, + FLOAT, + BOOL +}; + enum class Precision : uint8_t { LOW, MEDIUM, diff --git a/filament/include/filament/Material.h b/filament/include/filament/Material.h index 6533577383..602df4da56 100644 --- a/filament/include/filament/Material.h +++ b/filament/include/filament/Material.h @@ -105,6 +105,34 @@ public: */ Builder& package(const void* payload, size_t size); + template + using is_supported_constant_parameter_t = typename std::enable_if< + std::is_same::value || + std::is_same::value || + std::is_same::value>::type; + + /** + * Specialize a constant parameter specified in the material definition with a concrete + * value for this material. Once build() is called, this constant cannot be changed. + * Will throw an exception if the name does not match a constant specified in the + * material definition or if the type provided does not match. + * + * @tparam T The type of constant parameter, either int32_t, float, or bool. + * @param name The name of the constant parameter specified in the material definition, such + * as "myConstant". + * @param nameLength Length in `char` of the name parameter. + * @param value The value to use for the constant parameter, must match the type specified + * in the material definition. + */ + template> + Builder& constant(const char* name, size_t nameLength, T value); + + /** inline helper to provide the constant name as a null-terminated C string */ + template> + inline Builder& constant(const char* name, T value) { + return constant(name, strlen(name), value); + } + /** * Creates the Material object and returns a pointer to it. * diff --git a/filament/src/MaterialParser.cpp b/filament/src/MaterialParser.cpp index d2601ad993..ae87663068 100644 --- a/filament/src/MaterialParser.cpp +++ b/filament/src/MaterialParser.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include @@ -177,6 +178,13 @@ bool MaterialParser::getSamplerBlockBindings( pSamplerGroupInfoList, pSamplerBindingToNameMap); } +bool MaterialParser::getConstants(utils::FixedCapacityVector* value) const noexcept { + auto [start, end] = mImpl.mChunkContainer.getChunkRange(filamat::MaterialConstants); + if (start == end) return false; + Unflattener unflattener(start, end); + return ChunkMaterialConstants::unflatten(unflattener, value); +} + bool MaterialParser::getDepthWriteSet(bool* value) const noexcept { return mImpl.getFromSimpleChunk(ChunkType::MaterialDepthWriteSet, value); } @@ -546,4 +554,36 @@ bool ChunkSamplerBlockBindings::unflatten(Unflattener& unflattener, return true; } +bool ChunkMaterialConstants::unflatten(filaflat::Unflattener& unflattener, + utils::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; + uint8_t constantType = 0; + + if (!unflattener.read(&constantName)) { + return false; + } + + if (!unflattener.read(&constantType)) { + return false; + } + + (*materialConstants)[i].name = constantName; + (*materialConstants)[i].type = static_cast(constantType); + } + + return true; +} + } // namespace filament diff --git a/filament/src/MaterialParser.h b/filament/src/MaterialParser.h index 91b39cd9e7..154cb743f6 100644 --- a/filament/src/MaterialParser.h +++ b/filament/src/MaterialParser.h @@ -43,6 +43,7 @@ namespace filament { class BufferInterfaceBlock; class SamplerInterfaceBlock; struct SubpassInfo; +struct MaterialConstant; class MaterialParser { public: @@ -71,6 +72,7 @@ public: bool getUniformBlockBindings(utils::FixedCapacityVector>* value) const noexcept; bool getSamplerBlockBindings(SamplerGroupBindingInfoList* pSamplerGroupInfoList, SamplerBindingToNameMap* pSamplerBindingToNameMap) const noexcept; + bool getConstants(utils::FixedCapacityVector* value) const noexcept; bool getDepthWriteSet(bool* value) const noexcept; bool getDepthWrite(bool* value) const noexcept; @@ -170,6 +172,11 @@ struct ChunkSamplerBlockBindings { SamplerBindingToNameMap* pSamplerBindingToNameMap); }; +struct ChunkMaterialConstants { + static bool unflatten(filaflat::Unflattener& unflattener, + utils::FixedCapacityVector* materialConstants); +}; + } // namespace filament #endif // TNT_FILAMENT_MATERIALPARSER_H diff --git a/filament/src/details/Material.cpp b/filament/src/details/Material.cpp index 02d9d9d208..f6ef4c68e1 100644 --- a/filament/src/details/Material.cpp +++ b/filament/src/details/Material.cpp @@ -32,8 +32,11 @@ #include #include +#include #include +#include + namespace filament { using namespace backend; @@ -71,6 +74,7 @@ struct Material::BuilderDetails { size_t mSize = 0; MaterialParser* mMaterialParser = nullptr; bool mDefaultMaterial = false; + std::unordered_map> mConstantSpecializations; }; FMaterial::DefaultMaterialBuilder::DefaultMaterialBuilder() : Material::Builder() { @@ -91,6 +95,17 @@ Material::Builder& Material::Builder::package(const void* payload, size_t size) return *this; } +template +Material::Builder& Material::Builder::constant(const char* name, size_t nameLength, T value) { + ASSERT_PRECONDITION(name != nullptr, "name cannot be null"); + mImpl->mConstantSpecializations[{name, nameLength}] = value; + return *this; +} + +template Material::Builder& Material::Builder::constant(const char*, size_t, int32_t); +template Material::Builder& Material::Builder::constant(const char*, size_t, float); +template Material::Builder& Material::Builder::constant(const char*, size_t, bool); + Material* Material::Builder::build(Engine& engine) { std::unique_ptr materialParser{ createParser( downcast(engine).getBackend(), mImpl->mPayload, mImpl->mSize) }; @@ -179,6 +194,48 @@ FMaterial::FMaterial(FEngine& engine, const Material::Builder& builder) mSubpassInfo.isValid = false; } + utils::FixedCapacityVector constants; + // Older materials won't have a constants chunk, but that's okay. + parser->getConstants(&constants); + + // Verify that all the constant specializations exist in the material and that their types match. + // The first specialization constants are defined internally by Filament. + // The subsequent constants are user-defined in the material. + mSpecializationConstants.reserve(constants.size() + CONFIG_MAX_RESERVED_SPEC_CONSTANTS); + mSpecializationConstants.push_back({0, (int)mEngine.getSupportedFeatureLevel()}); + mSpecializationConstants.push_back({1, (int)CONFIG_MAX_INSTANCES}); + const bool staticTextureWorkaround = + mEngine.getDriverApi().isWorkaroundNeeded(Workaround::A8X_STATIC_TEXTURE_TARGET_ERROR); + mSpecializationConstants.push_back({2, (bool)staticTextureWorkaround}); + for (const auto& [name, value] : builder->mConstantSpecializations) { + auto found = std::find_if( + constants.begin(), constants.end(), [name = name](const auto& constant) { + return strncmp(constant.name.data(), name.data(), name.length()) == 0; + }); + ASSERT_PRECONDITION(found != constants.end(), + "The material %s does not have a constant parameter named %s.", mName.c_str_safe(), name.c_str()); + const char* const types[3] = {"an int", "a float", "a bool"}; + const char* const errorMessage = + "The constant parameter %s on material %s is of type %s, but %s was " + "provided."; + switch (found->type) { + case ConstantType::INT: + ASSERT_PRECONDITION(std::holds_alternative(value), errorMessage, + name.c_str(), mName.c_str_safe(), "int", types[value.index()]); + break; + case ConstantType::FLOAT: + ASSERT_PRECONDITION(std::holds_alternative(value), errorMessage, + name.c_str(), mName.c_str_safe(), "float", types[value.index()]); + break; + case ConstantType::BOOL: + ASSERT_PRECONDITION(std::holds_alternative(value), errorMessage, + name.c_str(), mName.c_str_safe(), "bool", types[value.index()]); + break; + } + uint32_t index = std::distance(constants.begin(), found) + CONFIG_MAX_RESERVED_SPEC_CONSTANTS; + mSpecializationConstants.push_back({index, value}); + } + parser->getShading(&mShading); parser->getMaterialProperties(&mMaterialProperties); parser->getBlendingMode(&mBlendingMode); @@ -462,14 +519,7 @@ Program FMaterial::getProgramWithVariants( } } - const bool staticTextureWorkaround = - mEngine.getDriverApi().isWorkaroundNeeded(Workaround::A8X_STATIC_TEXTURE_TARGET_ERROR); - - program.specializationConstants({ - { 0, (int)mEngine.getSupportedFeatureLevel() }, - { 1, (int)CONFIG_MAX_INSTANCES }, - { 2, (bool)staticTextureWorkaround } - }); + program.specializationConstants(mSpecializationConstants); return program; } diff --git a/filament/src/details/Material.h b/filament/src/details/Material.h index c32a4f2bc6..a9bc190bbb 100644 --- a/filament/src/details/Material.h +++ b/filament/src/details/Material.h @@ -27,6 +27,7 @@ #include #include #include +#include #include #include @@ -232,6 +233,7 @@ private: SamplerGroupBindingInfoList mSamplerGroupBindingInfoList; SamplerBindingToNameMap mSamplerBindingToNameMap; + utils::FixedCapacityVector mSpecializationConstants; #if FILAMENT_ENABLE_MATDBG matdbg::MaterialKey mDebuggerId; diff --git a/libs/filabridge/include/filament/MaterialChunkType.h b/libs/filabridge/include/filament/MaterialChunkType.h index eed5dea079..5d3e95ee3b 100644 --- a/libs/filabridge/include/filament/MaterialChunkType.h +++ b/libs/filabridge/include/filament/MaterialChunkType.h @@ -48,6 +48,7 @@ enum UTILS_PUBLIC ChunkType : uint64_t { MaterialSamplerBindings = charTo64bitNum("MAT_SAMP"), MaterialUniformBindings = charTo64bitNum("MAT_UNIF"), MaterialProperties = charTo64bitNum("MAT_PROP"), + MaterialConstants = charTo64bitNum("MAT_CONS"), MaterialName = charTo64bitNum("MAT_NAME"), MaterialVersion = charTo64bitNum("MAT_VERS"), diff --git a/libs/filabridge/include/private/filament/ConstantInfo.h b/libs/filabridge/include/private/filament/ConstantInfo.h new file mode 100644 index 0000000000..04ceaf15be --- /dev/null +++ b/libs/filabridge/include/private/filament/ConstantInfo.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2023 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. + */ + +#ifndef TNT_FILAMENT_CONSTANTINFO_H +#define TNT_FILAMENT_CONSTANTINFO_H + +#include + +#include + +namespace filament { + +struct MaterialConstant { + using ConstantType = backend::ConstantType; + + utils::CString name; + ConstantType type; + + MaterialConstant() = default; + MaterialConstant(const char* name, ConstantType type) : name(name), type(type) {} +}; + +} + +#endif // TNT_FILAMENT_CONSTANTINFO_H diff --git a/libs/filabridge/include/private/filament/EngineEnums.h b/libs/filabridge/include/private/filament/EngineEnums.h index f2113344f7..39558be756 100644 --- a/libs/filabridge/include/private/filament/EngineEnums.h +++ b/libs/filabridge/include/private/filament/EngineEnums.h @@ -60,6 +60,10 @@ enum class SamplerBindingPoints : uint8_t { constexpr size_t CONFIG_MAX_LIGHT_COUNT = 256; constexpr size_t CONFIG_MAX_LIGHT_INDEX = CONFIG_MAX_LIGHT_COUNT - 1; +// The number of specialization constants that Filament reserves for its own use. These are always +// the first constants (from 0 to CONFIG_MAX_RESERVED_SPEC_CONSTANTS - 1). +constexpr size_t CONFIG_MAX_RESERVED_SPEC_CONSTANTS = 8; + // The maximum number of shadowmaps. // There is currently a maximum limit of 128 shadowmaps. // Factors contributing to this limit: diff --git a/libs/filamat/include/filamat/Enums.h b/libs/filamat/include/filamat/Enums.h index ea626e814a..04b5ff8bbf 100644 --- a/libs/filamat/include/filamat/Enums.h +++ b/libs/filamat/include/filamat/Enums.h @@ -34,6 +34,7 @@ using ParameterPrecision = MaterialBuilder::ParameterPrecision; using OutputTarget = MaterialBuilder::OutputTarget; using OutputQualifier = MaterialBuilder::VariableQualifier; using OutputType = MaterialBuilder::OutputType; +using ConstantType = MaterialBuilder::ConstantType; // Convenience methods to convert std::string to Enum and also iterate over Enum values. class Enums { @@ -77,6 +78,7 @@ private: static std::unordered_map mStringToOutputTarget; static std::unordered_map mStringToOutputQualifier; static std::unordered_map mStringToOutputType; + static std::unordered_map mStringToConstantType; }; template diff --git a/libs/filamat/include/filamat/MaterialBuilder.h b/libs/filamat/include/filamat/MaterialBuilder.h index 8cbeba4728..3f668063c9 100644 --- a/libs/filamat/include/filamat/MaterialBuilder.h +++ b/libs/filamat/include/filamat/MaterialBuilder.h @@ -39,6 +39,7 @@ #include #include #include +#include #include #include @@ -238,6 +239,7 @@ public: using SpecularAmbientOcclusion = filament::SpecularAmbientOcclusion; using UniformType = filament::backend::UniformType; + using ConstantType = filament::backend::ConstantType; using SamplerType = filament::backend::SamplerType; using SubpassType = filament::backend::SubpassType; using SamplerFormat = filament::backend::SamplerFormat; @@ -290,6 +292,15 @@ public: MaterialBuilder& parameter(const char* name, size_t size, UniformType type, ParameterPrecision precision = ParameterPrecision::DEFAULT) noexcept; + //! Add a 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); + /** * Add a sampler parameter to this material. * @@ -652,6 +663,16 @@ public: int location; }; + struct Constant { + utils::CString name; + ConstantType type; + union { + int32_t i; + float f; + bool b; + } defaultValue; + }; + static constexpr size_t MATERIAL_PROPERTIES_COUNT = filament::MATERIAL_PROPERTIES_COUNT; using Property = filament::Property; @@ -679,6 +700,7 @@ public: using ParameterList = Parameter[MAX_PARAMETERS_COUNT]; using SubpassList = Parameter[MAX_SUBPASS_COUNT]; using BufferList = std::vector>; + using ConstantList = std::vector; // returns the number of parameters declared in this material uint8_t getParameterCount() const noexcept { return mParameterCount; } @@ -763,6 +785,7 @@ private: PropertyList mProperties; ParameterList mParameters; + ConstantList mConstants; SubpassList mSubpasses; VariableList mVariables; OutputList mOutputs; diff --git a/libs/filamat/src/Enums.cpp b/libs/filamat/src/Enums.cpp index 8049fdc9fe..01c4011c7e 100644 --- a/libs/filamat/src/Enums.cpp +++ b/libs/filamat/src/Enums.cpp @@ -160,4 +160,15 @@ std::unordered_map& Enums::getMap() n return mStringToSamplerFormat; }; +std::unordered_map Enums::mStringToConstantType = { + { "int", ConstantType::INT }, + { "float", ConstantType::FLOAT }, + { "bool", ConstantType::BOOL }, +}; + +template <> +std::unordered_map& Enums::getMap() noexcept { + return mStringToConstantType; +}; + } // namespace filamat diff --git a/libs/filamat/src/MaterialBuilder.cpp b/libs/filamat/src/MaterialBuilder.cpp index 35567278a4..08a6ebeaaf 100644 --- a/libs/filamat/src/MaterialBuilder.cpp +++ b/libs/filamat/src/MaterialBuilder.cpp @@ -45,6 +45,7 @@ #include #include #include +#include #include #include @@ -212,6 +213,53 @@ MaterialBuilder& MaterialBuilder::parameter(const char* name, SamplerType sample return parameter(name, samplerType, SamplerFormat::FLOAT, precision); } +template +MaterialBuilder& MaterialBuilder::constant(const char* name, ConstantType type, T defaultValue) { + auto result = std::find_if(mConstants.begin(), mConstants.end(), [name](const Constant& c) { + return c.name == utils::CString(name); + }); + ASSERT_POSTCONDITION(result == mConstants.end(), + "There is already a constant parameter present with the name %s.", name); + Constant constant { + .name = CString(name), + .type = type, + }; + auto toString = [](ConstantType t) { + switch (t) { + case ConstantType::INT: return "INT"; + case ConstantType::FLOAT: return "FLOAT"; + case ConstantType::BOOL: return "BOOL"; + } + }; + + const char* const errMessage = + "Constant %s was declared with type %s but given %s default value."; + if constexpr (std::is_same_v) { + ASSERT_POSTCONDITION( + type == ConstantType::INT, errMessage, name, toString(type), "an int"); + constant.defaultValue.i = defaultValue; + } else if constexpr (std::is_same_v) { + ASSERT_POSTCONDITION( + type == ConstantType::FLOAT, errMessage, name, toString(type), "a float"); + constant.defaultValue.f = defaultValue; + } else if constexpr (std::is_same_v) { + ASSERT_POSTCONDITION( + type == ConstantType::BOOL, errMessage, name, toString(type), "a bool"); + constant.defaultValue.b = defaultValue; + } else { + assert_invariant(false); + } + + mConstants.push_back(constant); + return *this; +} +template MaterialBuilder& MaterialBuilder::constant( + const char* name, ConstantType type, int32_t defaultValue); +template MaterialBuilder& MaterialBuilder::constant( + const char* name, ConstantType type, float defaultValue); +template MaterialBuilder& MaterialBuilder::constant( + const char* name, ConstantType type, bool defaultValue); + MaterialBuilder& MaterialBuilder::buffer(BufferInterfaceBlock bib) noexcept { ASSERT_POSTCONDITION(mBuffers.size() < MAX_BUFFERS_COUNT, "Too many buffers"); mBuffers.emplace_back(std::make_unique(std::move(bib))); @@ -699,10 +747,10 @@ bool MaterialBuilder::generateShaders(JobSystem& jobSystem, const std::vector(ChunkType::MaterialHasCustomDepthShader, needsStandardDepthProgram()); @@ -1163,7 +1211,7 @@ bool MaterialBuilder::needsStandardDepthProgram() const noexcept { std::string MaterialBuilder::peek(backend::ShaderStage stage, const CodeGenParams& params, const PropertyList& properties) noexcept { - ShaderGenerator sg(properties, mVariables, mOutputs, mDefines, + ShaderGenerator sg(properties, mVariables, mOutputs, mDefines, mConstants, mMaterialFragmentCode.getResolved(), mMaterialFragmentCode.getLineOffset(), mMaterialVertexCode.getResolved(), mMaterialVertexCode.getLineOffset(), mMaterialDomain); @@ -1222,6 +1270,12 @@ void MaterialBuilder::writeCommonChunks(ChunkContainer& container, MaterialInfo& // User Material SIB container.push(info.sib); + // User constant parameters + utils::FixedCapacityVector constantsEntry(mConstants.size()); + std::transform(mConstants.begin(), mConstants.end(), constantsEntry.begin(), + [](Constant const& c) { return MaterialConstant(c.name.c_str(), c.type); }); + container.push(std::move(constantsEntry)); + // TODO: should we write the SSBO info? this would only be needed if we wanted to provide // an interface to set [get?] values in the buffer. But we can do that easily // with a c-struct (what about kotlin/java?). tbd. diff --git a/libs/filamat/src/eiff/MaterialInterfaceBlockChunk.cpp b/libs/filamat/src/eiff/MaterialInterfaceBlockChunk.cpp index d3a23d9b3c..b3ebd9e0b5 100644 --- a/libs/filamat/src/eiff/MaterialInterfaceBlockChunk.cpp +++ b/libs/filamat/src/eiff/MaterialInterfaceBlockChunk.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include @@ -89,6 +90,20 @@ void MaterialSubpassInterfaceBlockChunk::flatten(Flattener& f) { // ------------------------------------------------------------------------------------------------ +MaterialConstantParametersChunk::MaterialConstantParametersChunk( + utils::FixedCapacityVector constants) + : Chunk(ChunkType::MaterialConstants), mConstants(std::move(constants)) {} + +void MaterialConstantParametersChunk::flatten(Flattener& f) { + f.writeUint64(mConstants.size()); + for (const auto& constant : mConstants) { + f.writeString(constant.name.c_str()); + f.writeUint8(static_cast(constant.type)); + } +} + +// ------------------------------------------------------------------------------------------------ + MaterialUniformBlockBindingsChunk::MaterialUniformBlockBindingsChunk( utils::FixedCapacityVector> list) : Chunk(ChunkType::MaterialUniformBindings), diff --git a/libs/filamat/src/eiff/MaterialInterfaceBlockChunk.h b/libs/filamat/src/eiff/MaterialInterfaceBlockChunk.h index df42347556..aa287096d4 100644 --- a/libs/filamat/src/eiff/MaterialInterfaceBlockChunk.h +++ b/libs/filamat/src/eiff/MaterialInterfaceBlockChunk.h @@ -28,6 +28,7 @@ class SamplerBindingMap; class SamplerInterfaceBlock; class BufferInterfaceBlock; struct SubpassInfo; +struct MaterialConstant; } // namespace filament namespace filamat { @@ -71,6 +72,20 @@ private: // ------------------------------------------------------------------------------------------------ +class MaterialConstantParametersChunk final : public Chunk { +public: + explicit MaterialConstantParametersChunk( + utils::FixedCapacityVector constants); + ~MaterialConstantParametersChunk() final = default; + +private: + void flatten(Flattener&) final; + + utils::FixedCapacityVector mConstants; +}; + +// ------------------------------------------------------------------------------------------------ + class MaterialUniformBlockBindingsChunk final : public Chunk { public: explicit MaterialUniformBlockBindingsChunk( diff --git a/libs/filamat/src/shaders/CodeGenerator.cpp b/libs/filamat/src/shaders/CodeGenerator.cpp index f3425cca55..bf6d385156 100644 --- a/libs/filamat/src/shaders/CodeGenerator.cpp +++ b/libs/filamat/src/shaders/CodeGenerator.cpp @@ -119,7 +119,7 @@ utils::io::sstream& CodeGenerator::generateProlog(utils::io::sstream& out, Shade out << "precision lowp sampler3D;\n"; } - // specification constants + // Filament-reserved specification constants (limited by CONFIG_MAX_RESERVED_SPEC_CONSTANTS) out << '\n'; generateSpecializationConstant(out, "BACKEND_FEATURE_LEVEL", 0, 1); diff --git a/libs/filamat/src/shaders/MaterialInfo.h b/libs/filamat/src/shaders/MaterialInfo.h index 22af34cb28..9536dcc704 100644 --- a/libs/filamat/src/shaders/MaterialInfo.h +++ b/libs/filamat/src/shaders/MaterialInfo.h @@ -28,6 +28,7 @@ #include #include +#include namespace filamat { diff --git a/libs/filamat/src/shaders/ShaderGenerator.cpp b/libs/filamat/src/shaders/ShaderGenerator.cpp index e7d6bf6f4f..406820f3f7 100644 --- a/libs/filamat/src/shaders/ShaderGenerator.cpp +++ b/libs/filamat/src/shaders/ShaderGenerator.cpp @@ -126,11 +126,36 @@ static void appendShader(io::sstream& ss, } } +static void generateUserSpecConstants( + const CodeGenerator& cg, io::sstream& fs, MaterialBuilder::ConstantList constants) { + // Constants 0 to CONFIG_MAX_RESERVED_SPEC_CONSTANTS - 1 are reserved by Filament. + size_t index = CONFIG_MAX_RESERVED_SPEC_CONSTANTS; + for (const auto& constant : constants) { + std::string fullName = std::string("materialConstants_") + constant.name.c_str(); + switch (constant.type) { + case ConstantType::INT: + cg.generateSpecializationConstant( + fs, fullName.c_str(), index++, constant.defaultValue.i); + break; + case ConstantType::FLOAT: + cg.generateSpecializationConstant( + fs, fullName.c_str(), index++, constant.defaultValue.f); + break; + case ConstantType::BOOL: + cg.generateSpecializationConstant( + fs, fullName.c_str(), index++, constant.defaultValue.b); + break; + } + } +} + + ShaderGenerator::ShaderGenerator( MaterialBuilder::PropertyList const& properties, MaterialBuilder::VariableList const& variables, MaterialBuilder::OutputList const& outputs, MaterialBuilder::PreprocessorDefineList const& defines, + MaterialBuilder::ConstantList const& constants, CString const& materialCode, size_t lineOffset, CString const& materialVertexCode, size_t vertexLineOffset, MaterialBuilder::MaterialDomain materialDomain) noexcept { @@ -151,6 +176,7 @@ ShaderGenerator::ShaderGenerator( mMaterialVertexLineOffset = vertexLineOffset; mMaterialDomain = materialDomain; mDefines = defines; + mConstants = constants; if (mMaterialFragmentCode.empty()) { if (mMaterialDomain == MaterialBuilder::MaterialDomain::SURFACE) { @@ -203,6 +229,8 @@ std::string ShaderGenerator::createVertexProgram(ShaderModel shaderModel, cg.generateProlog(vs, ShaderStage::VERTEX, material); + generateUserSpecConstants(cg, vs, mConstants); + cg.generateQualityDefine(vs, material.quality); CodeGenerator::generateDefine(vs, "FLIP_UV_ATTRIBUTE", material.flipUV); @@ -342,6 +370,8 @@ std::string ShaderGenerator::createFragmentProgram(ShaderModel shaderModel, io::sstream fs; cg.generateProlog(fs, ShaderStage::FRAGMENT, material); + generateUserSpecConstants(cg, fs, mConstants); + cg.generateQualityDefine(fs, material.quality); CodeGenerator::generateDefine(fs, "GEOMETRIC_SPECULAR_AA", material.specularAntiAliasing && lit); @@ -554,6 +584,8 @@ std::string ShaderGenerator::createComputeProgram(filament::backend::ShaderModel cg.generateProlog(s, ShaderStage::COMPUTE, material); + generateUserSpecConstants(cg, s, mConstants); + cg.generateQualityDefine(s, material.quality); cg.generateUniforms(s, ShaderStage::COMPUTE, diff --git a/libs/filamat/src/shaders/ShaderGenerator.h b/libs/filamat/src/shaders/ShaderGenerator.h index 2d7a63bd9e..d1e35a293c 100644 --- a/libs/filamat/src/shaders/ShaderGenerator.h +++ b/libs/filamat/src/shaders/ShaderGenerator.h @@ -42,6 +42,7 @@ public: MaterialBuilder::VariableList const& variables, MaterialBuilder::OutputList const& outputs, MaterialBuilder::PreprocessorDefineList const& defines, + MaterialBuilder::ConstantList const& constants, utils::CString const& materialCode, size_t lineOffset, utils::CString const& materialVertexCode, @@ -88,6 +89,7 @@ private: MaterialBuilder::OutputList mOutputs; MaterialBuilder::MaterialDomain mMaterialDomain; MaterialBuilder::PreprocessorDefineList mDefines; + MaterialBuilder::ConstantList mConstants; utils::CString mMaterialFragmentCode; // fragment or compute code utils::CString mMaterialVertexCode; size_t mMaterialLineOffset; diff --git a/libs/filamat/tests/test_filamat.cpp b/libs/filamat/tests/test_filamat.cpp index 95b25f25eb..6280ca3c42 100644 --- a/libs/filamat/tests/test_filamat.cpp +++ b/libs/filamat/tests/test_filamat.cpp @@ -802,6 +802,55 @@ TEST_F(MaterialCompiler, CustomSurfaceShadingHasFunction) { EXPECT_TRUE(result.isValid()); } +TEST_F(MaterialCompiler, ConstantParameter) { + std::string shaderCode(R"( + void material(inout MaterialInputs material) { + prepareMaterial(material); + if (materialConstants_myBoolConstant) { + material.baseColor.rgb = float3(materialConstants_myFloatConstant); + int anInt = materialConstants_myIntConstant; + } + } + )"); + std::string vertexCode(R"( + void materialVertex(inout MaterialVertexInputs material) { + int anInt = materialConstants_myIntConstant; + bool aBool = materialConstants_myBoolConstant; + float aFloat = materialConstants_myFloatConstant; + } + )"); + 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.shading(filament::Shading::LIT); + builder.material(shaderCode.c_str()); + builder.materialVertex(vertexCode.c_str()); + filamat::Package result = builder.build(*jobSystem); + EXPECT_TRUE(result.isValid()); +} + +TEST_F(MaterialCompiler, ConstantParameterSameName) { +#if !defined(NDEBUG) && defined(GTEST_HAS_DEATH_TEST) + EXPECT_THROW({ + filamat::MaterialBuilder builder; + builder.constant("myFloatConstant", ConstantType::FLOAT, 1.0f); + builder.constant("myFloatConstant", ConstantType::FLOAT, 1.0f); + }, utils::PostconditionPanic); +#endif +} + +TEST_F(MaterialCompiler, ConstantParameterWrongType) { +#if !defined(NDEBUG) && defined(GTEST_HAS_DEATH_TEST) + EXPECT_THROW({ + filamat::MaterialBuilder builder; + builder.constant("myFloatConstant", ConstantType::FLOAT, 10); + }, utils::PostconditionPanic); +#endif +} + int main(int argc, char** argv) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); diff --git a/libs/matdbg/src/CommonWriter.h b/libs/matdbg/src/CommonWriter.h index a711319708..6d2afc1ee4 100644 --- a/libs/matdbg/src/CommonWriter.h +++ b/libs/matdbg/src/CommonWriter.h @@ -236,6 +236,16 @@ const char* toString(backend::SamplerFormat format) noexcept { return "--"; } +inline +const char* toString(backend::ConstantType type) noexcept { + switch (type) { + case backend::ConstantType::FLOAT: return "float"; + case backend::ConstantType::INT: return "int"; + case backend::ConstantType::BOOL: return "bool"; + } + return "--"; +} + // Returns a human-readable variant description. // For example: DYN|DIR std::string formatVariantString(Variant variant, MaterialDomain domain) noexcept; diff --git a/libs/matdbg/src/TextWriter.cpp b/libs/matdbg/src/TextWriter.cpp index 737458ab0c..1d889cb186 100644 --- a/libs/matdbg/src/TextWriter.cpp +++ b/libs/matdbg/src/TextWriter.cpp @@ -259,6 +259,42 @@ static bool printParametersInfo(ostream& text, const ChunkContainer& container) return true; } +static bool printConstantInfo(ostream& text, const ChunkContainer& container) { + if (!container.hasChunk(ChunkType::MaterialConstants)) { + return true; + } + + auto [startConstants, endConstants] = container.getChunkRange(ChunkType::MaterialConstants); + Unflattener constants(startConstants, endConstants); + + uint64_t constantsCount; + constants.read(&constantsCount); + + text << "Constants:" << endl; + + for (uint64_t i = 0; i < constantsCount; i++) { + CString fieldName; + uint8_t fieldType; + + if (!constants.read(&fieldName)) { + return false; + } + + if (!constants.read(&fieldType)) { + return false; + } + + text << " " + << setw(alignment) << fieldName.c_str() + << setw(shortAlignment) << toString(ConstantType(fieldType)) + << endl; + } + + text << endl; + + return true; +} + static bool printSubpassesInfo(ostream& text, const ChunkContainer& container) { // Subpasses are optional. @@ -406,6 +442,9 @@ bool TextWriter::writeMaterialInfo(const filaflat::ChunkContainer& container) { if (!printParametersInfo(text, container)) { return false; } + if (!printConstantInfo(text, container)) { + return false; + } if (!printSubpassesInfo(text, container)) { return false; } diff --git a/tools/matc/src/matc/ParametersProcessor.cpp b/tools/matc/src/matc/ParametersProcessor.cpp index f1745e4b2c..8a5c823926 100644 --- a/tools/matc/src/matc/ParametersProcessor.cpp +++ b/tools/matc/src/matc/ParametersProcessor.cpp @@ -246,6 +246,98 @@ static bool processParameters(MaterialBuilder& builder, const JsonishValue& v) { return ok; } +static bool processConstant(MaterialBuilder& builder, const JsonishObject& jsonObject) noexcept { + const JsonishValue* typeValue = jsonObject.getValue("type"); + if (!typeValue) { + std::cerr << "constants: entry without key 'type'." << std::endl; + return false; + } + if (typeValue->getType() != JsonishValue::STRING) { + std::cerr << "constants: type value must be STRING." << std::endl; + return false; + } + + const JsonishValue* nameValue = jsonObject.getValue("name"); + if (!nameValue) { + std::cerr << "constants: entry without 'name' key." << std::endl; + return false; + } + if (nameValue->getType() != JsonishValue::STRING) { + std::cerr << "constants: name value must be STRING." << std::endl; + return false; + } + + auto typeString = typeValue->toJsonString()->getString(); + auto nameString = nameValue->toJsonString()->getString(); + const JsonishValue* defaultValue = jsonObject.getValue("default"); + + if (Enums::isValid(typeString)) { + auto type = Enums::toEnum(typeString); + switch (type) { + case ConstantType::INT: { + int32_t intDefault = 0; + if (defaultValue) { + if (defaultValue->getType() != JsonishValue::NUMBER) { + std::cerr << "constants: INT constants must have NUMBER default value" + << std::endl; + return false; + } + // FIXME: Jsonish doesn't distinguish between integers and floats. + intDefault = (int32_t)defaultValue->toJsonNumber()->getFloat(); + } + builder.constant(nameString.c_str(), type, intDefault); + break; + } + case ConstantType::FLOAT: { + float floatDefault = 0.0f; + if (defaultValue) { + if (defaultValue->getType() != JsonishValue::NUMBER) { + std::cerr << "constants: FLOAT constants must have NUMBER default value" + << std::endl; + return false; + } + floatDefault = defaultValue->toJsonNumber()->getFloat(); + } + builder.constant(nameString.c_str(), type, floatDefault); + break; + } + case ConstantType::BOOL: + bool boolDefault = false; + if (defaultValue) { + if (defaultValue->getType() != JsonishValue::BOOL) { + std::cerr << "constants: BOOL constants must have BOOL default value" + << std::endl; + return false; + } + boolDefault = defaultValue->toJsonBool()->getBool(); + } + builder.constant(nameString.c_str(), type, boolDefault); + break; + } + } else { + std::cerr << "constants: the type '" << typeString + << "' for constant with name '" << nameString << "' is not a valid constant " + << "parameter type." << std::endl; + return false; + } + + return true; +} + +static bool processConstants(MaterialBuilder& builder, const JsonishValue& v) { + auto jsonArray = v.toJsonArray(); + + bool ok = true; + for (auto value : jsonArray->getElements()) { + if (value->getType() == JsonishValue::Type::OBJECT) { + ok &= processConstant(builder, *value->toJsonObject()); + continue; + } + std::cerr << "constants must be an array of OBJECTs." << std::endl; + return false; + } + return ok; +} static bool processBufferField(filament::BufferInterfaceBlock::Builder& builder, const JsonishObject& jsonObject) noexcept { @@ -1059,6 +1151,7 @@ ParametersProcessor::ParametersProcessor() { mParameters["name"] = { &processName, Type::STRING }; mParameters["interpolation"] = { &processInterpolation, Type::STRING }; mParameters["parameters"] = { &processParameters, Type::ARRAY }; + mParameters["constants"] = { &processConstants, Type::ARRAY }; mParameters["buffers"] = { &processBuffers, Type::ARRAY }; mParameters["subpasses"] = { &processSubpasses, Type::ARRAY }; mParameters["variables"] = { &processVariables, Type::ARRAY };