Compare commits

...

2 Commits

Author SHA1 Message Date
Eliza Velasquez
84cecd334b materials: fix mutable spec constants nvidia bug
It turns out that I didn't understand how `FixedCapacityVector` works.
2025-09-03 18:13:09 -07:00
Eliza Velasquez
bb6ebd3aea materials: reintroduce mutable spec constants
This change apparently breaks nvidia, still working on it.
2025-08-27 13:27:39 -07:00
29 changed files with 772 additions and 113 deletions

View File

@@ -7,3 +7,6 @@ We are chaning the way Vulkan buffers are handled. We need to switch over to a m
appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md). appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md).
## Release notes for next branch cut ## Release notes for next branch cut
- Rename `sampler` parameter `unfilterable` to `filterable` [⚠️ **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**]

View File

@@ -60,6 +60,14 @@ static void setParameter(JNIEnv* env, jlong nativeMaterialInstance, jstring name
env->ReleaseStringUTFChars(name_, name); env->ReleaseStringUTFChars(name_, name);
} }
template<typename T>
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" extern "C"
JNIEXPORT void JNICALL JNIEXPORT void JNICALL
Java_com_google_android_filament_MaterialInstance_nSetParameterBool(JNIEnv *env, jclass, 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); 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" extern "C"
JNIEXPORT void JNICALL JNIEXPORT void JNICALL
Java_com_google_android_filament_MaterialInstance_nSetScissor( Java_com_google_android_filament_MaterialInstance_nSetScissor(

View File

@@ -402,6 +402,16 @@ public class MaterialInstance {
nSetParameterFloat4(getNativeObject(), name, color[0], color[1], color[2], color[3]); 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. * 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, @NonNull String name, int element, @NonNull @Size(min = 1) float[] v,
@IntRange(from = 0) int offset, @IntRange(from = 1) int count); @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, private static native void nSetParameterTexture(long nativeMaterialInstance,
@NonNull String name, long nativeTexture, long sampler); @NonNull String name, long nativeTexture, long sampler);

View File

@@ -212,7 +212,7 @@ when using textures.
This property can dramatically change the appearance of a surface. Non-metallic surfaces have 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 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 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 The effect of `metallic` is shown in figure [metallicProperty] (click on the image to see a
larger version). larger version).
@@ -248,7 +248,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 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 `roughness` property will also affect the refractions, as shown in figure
[roughnessRefractionProperty] (click on the image to see a larger version). [roughnessRefractionProperty] (click on the image to see a larger version).
![Figure [roughnessRefractionProperty]: Refractive sphere with `roughness` varying from 0.0 ![Figure [roughnessRefractionProperty]: Refractive sphere with `roughness` varying from 0.0
(left) to 1.0 (right)](images/materials/refraction_roughness.png) (left) to 1.0 (right)](images/materials/refraction_roughness.png)
@@ -307,7 +307,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 base layer described by the properties above. The sheen layer always sits below the clear coat layer
if such a layer is present. 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. section [Cloth model] for more information about cloth and fabric materials.
The effect of `sheenColor` is shown in figure [materialSheenColor] The effect of `sheenColor` is shown in figure [materialSheenColor]
@@ -520,14 +520,14 @@ light to bend further away from the initial path.
Table [commonMatIOR] describes acceptable refractive indices for various types of materials. Table [commonMatIOR] describes acceptable refractive indices for various types of materials.
Material | IOR Material | IOR
--------------------------:|:----------------- --------------------------:|:-----------------
Air | 1.0 Air | 1.0
Water | 1.33 Water | 1.33
Common liquids | 1.33 to 1.5 Common liquids | 1.33 to 1.5
Common gemstones | 1.58 to 2.33 Common gemstones | 1.58 to 2.33
Plastics, glass | 1.5 to 1.58 Plastics, glass | 1.5 to 1.58
Other dielectric materials | 1.33 to 1.58 Other dielectric materials | 1.33 to 1.58
[Table [commonMatIOR]: Index of refraction of common materials] [Table [commonMatIOR]: Index of refraction of common materials]
The appearance of a refractive material will greatly depend on the `refractionType` and The appearance of a refractive material will greatly depend on the `refractionType` and
@@ -1096,30 +1096,37 @@ Value
`bool` or `number`, depending on the `type` of the constant. The type must be one of the types `bool` or `number`, depending on the `type` of the constant. The type must be one of the types
described in table [materialConstantsTypes]. described in table [materialConstantsTypes].
Type | Description | Default Constants may also be specified as mutable by setting the `mutable` property to `true`. Only
:----------------------|:-----------------------------------------|:------------------ `bool` spec constants may be specified as mutable.
int | A signed, 32 bit GLSL int | 0
float | A single-precision GLSL float | 0.0 | Type | Description | Default | May be mutable? |
bool | A GLSL bool | false |:------|:------------------------------|:--------|-----------------|
| 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] [Table [materialConstantsTypes]: Material constants types]
Description Description
: Lists the constant parameters accepted by your material. These constants can be set, or : 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 "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 the same material package with differing constant parameter specializations. If a constant
loaded from a material package, its constant parameters cannot be changed. Compared to regular parameter is specialized as mutable, it may be changed at any time on a per-instance basis via
parameters, constant parameters allow the compiler to generate more efficient code. Access `MaterialInstance::setConstant(name, value)`. Otherwise, once a material is loaded from a
constant parameters from the shader by prefixing the name with `materialConstant_`. For example, material package, its constant parameters cannot be changed.
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 Compared to regular parameters, constant parameters allow the compiler to generate more
used. 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 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ JSON
material { material {
constants : [ constants : [
{ {
name : overrideAlpha, name : overrideAlpha,
type : bool type : bool,
mutable : true,
}, },
{ {
name : customAlpha, name : customAlpha,
@@ -2639,7 +2646,7 @@ standard skybox material. It produces a list of 2 parameters, named `showSun` an
respectively a boolean and a cubemap texture. respectively a boolean and a cubemap texture.
```text ```text
$ matc --reflect parameters filament/src/materials/skybox.mat $ matc --reflect parameters filament/src/materials/skybox.mat
{ {
"parameters": [ "parameters": [
{ {
@@ -2655,7 +2662,7 @@ $ matc --reflect parameters filament/src/materials/skybox.mat
} }
] ]
} }
``` ```
### --variant-filter ### --variant-filter

View File

@@ -20,6 +20,7 @@
#include <utils/CString.h> #include <utils/CString.h>
#include <utils/FixedCapacityVector.h> #include <utils/FixedCapacityVector.h>
#include <utils/Invocable.h> #include <utils/Invocable.h>
#include <utils/bitset.h>
#include <backend/DriverEnums.h> #include <backend/DriverEnums.h>
@@ -66,6 +67,7 @@ public:
using DescriptorBindingsInfo = utils::FixedCapacityVector<Descriptor>; using DescriptorBindingsInfo = utils::FixedCapacityVector<Descriptor>;
using DescriptorSetInfo = std::array<DescriptorBindingsInfo, MAX_DESCRIPTOR_SET_COUNT>; using DescriptorSetInfo = std::array<DescriptorBindingsInfo, MAX_DESCRIPTOR_SET_COUNT>;
using SpecializationConstantsInfo = utils::FixedCapacityVector<SpecializationConstant>; using SpecializationConstantsInfo = utils::FixedCapacityVector<SpecializationConstant>;
using MutableSpecConstantsInfo = utils::bitset8;
using ShaderBlob = utils::FixedCapacityVector<uint8_t>; using ShaderBlob = utils::FixedCapacityVector<uint8_t>;
using ShaderSource = std::array<ShaderBlob, SHADER_TYPE_COUNT>; using ShaderSource = std::array<ShaderBlob, SHADER_TYPE_COUNT>;
@@ -103,7 +105,8 @@ public:
Program& descriptorBindings(backend::descriptor_set_t set, Program& descriptorBindings(backend::descriptor_set_t set,
DescriptorBindingsInfo descriptorBindings) noexcept; DescriptorBindingsInfo descriptorBindings) noexcept;
Program& specializationConstants(SpecializationConstantsInfo specConstants) noexcept; Program& specializationConstants(SpecializationConstantsInfo specConstants,
uint32_t firstMutableId, MutableSpecConstantsInfo mutableSpecConstants) noexcept;
struct PushConstant { struct PushConstant {
utils::CString name; utils::CString name;

View File

@@ -82,8 +82,19 @@ Program& Program::attributes(AttributesInfo attributes) noexcept {
return *this; return *this;
} }
Program& Program::specializationConstants(SpecializationConstantsInfo specConstants) noexcept { Program& Program::specializationConstants(SpecializationConstantsInfo specConstants,
mSpecializationConstants = std::move(specConstants); uint32_t firstMutableId, MutableSpecConstantsInfo mutableSpecConstants) noexcept {
// String the two lists together.
mSpecializationConstants = SpecializationConstantsInfo(
specConstants.size() + mutableSpecConstants.size());
std::uninitialized_move(specConstants.begin(), specConstants.end(),
mSpecializationConstants.begin());
for (uint32_t i = 0; i < mutableSpecConstants.size(); i++) {
mSpecializationConstants[i + specConstants.size()] = SpecializationConstant {
.id = i + firstMutableId,
.value = mutableSpecConstants[i],
};
}
return *this; return *this;
} }

View File

@@ -87,6 +87,9 @@ public:
std::is_same_v<math::mat3f, T> std::is_same_v<math::mat3f, T>
>; >;
template<typename T>
using is_supported_constant_parameter_t = std::enable_if_t<std::is_same_v<bool, T>>;
/** /**
* Creates a new MaterialInstance using another MaterialInstance as a template for initialization. * 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 * The new MaterialInstance is an instance of the same Material of the template instance and
@@ -238,13 +241,13 @@ public:
/** /**
* Gets the value of a parameter by name. * Gets the value of a parameter by name.
* *
* Note: Only supports non-texture parameters such as numeric and math types. * 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 name Name of the parameter as defined by Material. Cannot be nullptr.
* @param nameLength Length in `char` of the name parameter. * @param nameLength Length in `char` of the name parameter.
* @throws utils::PreConditionPanic if name doesn't exist or no-op if exceptions are disabled. * @throws utils::PreConditionPanic if name doesn't exist or no-op if exceptions are disabled.
* *
* @see Material::hasParameter * @see Material::hasParameter
*/ */
template<typename T> template<typename T>
@@ -262,6 +265,53 @@ public:
return getParameter<T>(name, strlen(name)); return getParameter<T>(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<typename T, typename = is_supported_constant_parameter_t<T>>
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<typename T, typename = is_supported_constant_parameter_t<T>>
void setConstant(StringLiteral const name, T const& value) {
setConstant<T>(name.data, name.size, value);
}
/** inline helper to provide the name as a null-terminated C string */
template<typename T, typename = is_supported_constant_parameter_t<T>>
void setConstant(const char* UTILS_NONNULL name, T const& value) {
setConstant<T>(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<typename T, typename = is_supported_constant_parameter_t<T>>
T getConstant(const char* UTILS_NONNULL name, size_t nameLength) const;
/** inline helper to provide the name as a null-terminated C string */
template<typename T, typename = is_supported_constant_parameter_t<T>>
T getConstant(StringLiteral const name) const {
return getConstant<T>(name.data, name.size);
}
/** inline helper to provide the name as a null-terminated C string */
template<typename T, typename = is_supported_constant_parameter_t<T>>
T getConstant(const char* UTILS_NONNULL name) const {
return getConstant<T>(name, strlen(name));
}
/** /**
* Set-up a custom scissor rectangle; by default it is disabled. * Set-up a custom scissor rectangle; by default it is disabled.
* *

View File

@@ -233,6 +233,46 @@ template UTILS_PUBLIC mat3f MaterialInstance::getParameter<mat3f> (const ch
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
template<>
inline void FMaterialInstance::setConstantImpl<bool>(std::string_view const name, bool const& value) {
std::optional<uint32_t> id = mMaterial->getMutableConstantId(name);
FILAMENT_CHECK_PRECONDITION(id.has_value()) << "No mutable constant with name " << name;
mConstants.set(*id, value);
}
template<typename T, typename>
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<bool>(const char* name, size_t nameLength, bool const& v);
// ------------------------------------------------------------------------------------------------
template<>
inline bool FMaterialInstance::getConstantImpl<bool>(std::string_view const name) const {
std::optional<uint32_t> id = mMaterial->getMutableConstantId(name);
FILAMENT_CHECK_PRECONDITION(id.has_value()) << "No mutable constant with name " << name;
return mConstants[*id];
}
template<typename T, typename>
T MaterialInstance::getConstant(const char* name, size_t nameLength) const {
return downcast(this)->getConstantImpl<T>({ 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<bool>(const char* name, size_t nameLength) const;
// ------------------------------------------------------------------------------------------------
Material const* MaterialInstance::getMaterial() const noexcept { Material const* MaterialInstance::getMaterial() const noexcept {
return downcast(this)->getMaterial(); return downcast(this)->getMaterial();
} }

View File

@@ -229,6 +229,11 @@ bool MaterialParser::getConstants(FixedCapacityVector<MaterialConstant>* contain
return get<ChunkMaterialConstants>(container); return get<ChunkMaterialConstants>(container);
} }
bool MaterialParser::getMutableConstants(
FixedCapacityVector<MaterialMutableConstant>* container) const noexcept {
return get<ChunkMaterialMutableConstants>(container);
}
bool MaterialParser::getPushConstants(CString* structVarName, bool MaterialParser::getPushConstants(CString* structVarName,
FixedCapacityVector<MaterialPushConstant>* value) const noexcept { FixedCapacityVector<MaterialPushConstant>* value) const noexcept {
auto [start, end] = mImpl.mChunkContainer.getChunkRange(MaterialPushConstants); auto [start, end] = mImpl.mChunkContainer.getChunkRange(MaterialPushConstants);
@@ -761,6 +766,38 @@ bool ChunkMaterialConstants::unflatten(Unflattener& unflattener,
return true; return true;
} }
bool ChunkMaterialMutableConstants::unflatten(Unflattener& unflattener,
FixedCapacityVector<MaterialMutableConstant>* 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, bool ChunkMaterialPushConstants::unflatten(Unflattener& unflattener,
CString* structVarName, CString* structVarName,
FixedCapacityVector<MaterialPushConstant>* materialPushConstants) { FixedCapacityVector<MaterialPushConstant>* materialPushConstants) {

View File

@@ -49,6 +49,7 @@ class BufferInterfaceBlock;
class SamplerInterfaceBlock; class SamplerInterfaceBlock;
struct SubpassInfo; struct SubpassInfo;
struct MaterialConstant; struct MaterialConstant;
struct MaterialMutableConstant;
struct MaterialPushConstant; struct MaterialPushConstant;
class MaterialParser { class MaterialParser {
@@ -79,6 +80,8 @@ public:
bool getShaderModels(uint32_t* value) const noexcept; bool getShaderModels(uint32_t* value) const noexcept;
bool getMaterialProperties(uint64_t* value) const noexcept; bool getMaterialProperties(uint64_t* value) const noexcept;
bool getConstants(utils::FixedCapacityVector<MaterialConstant>* value) const noexcept; bool getConstants(utils::FixedCapacityVector<MaterialConstant>* value) const noexcept;
bool getMutableConstants(utils::FixedCapacityVector<MaterialMutableConstant>* value)
const noexcept;
bool getPushConstants(utils::CString* structVarName, bool getPushConstants(utils::CString* structVarName,
utils::FixedCapacityVector<MaterialPushConstant>* value) const noexcept; utils::FixedCapacityVector<MaterialPushConstant>* value) const noexcept;
@@ -238,6 +241,13 @@ struct ChunkMaterialConstants {
static filamat::ChunkType const tag = filamat::MaterialConstants; static filamat::ChunkType const tag = filamat::MaterialConstants;
}; };
struct ChunkMaterialMutableConstants {
static bool unflatten(filaflat::Unflattener& unflattener,
utils::FixedCapacityVector<MaterialMutableConstant>* materialConstants);
using Container = utils::FixedCapacityVector<MaterialMutableConstant>;
static filamat::ChunkType const tag = filamat::MaterialMutableConstants;
};
struct ChunkMaterialPushConstants { struct ChunkMaterialPushConstants {
static bool unflatten(filaflat::Unflattener& unflattener, utils::CString* structVarName, static bool unflatten(filaflat::Unflattener& unflattener, utils::CString* structVarName,
utils::FixedCapacityVector<MaterialPushConstant>* materialPushConstants); utils::FixedCapacityVector<MaterialPushConstant>* materialPushConstants);

View File

@@ -199,6 +199,8 @@ FMaterial* PostProcessManager::PostProcessMaterial::getMaterial(FEngine& engine,
loadMaterial(engine); 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),
CompilerPriorityQueue::CRITICAL); CompilerPriorityQueue::CRITICAL);
return mMaterial; return mMaterial;
} }
@@ -465,7 +467,10 @@ UTILS_NOINLINE
PipelineState PostProcessManager::getPipelineState( PipelineState PostProcessManager::getPipelineState(
FMaterial const* const ma, PostProcessVariant variant) const noexcept { FMaterial const* const ma, PostProcessVariant variant) const noexcept {
return { 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, .vertexBufferInfo = mFullScreenQuadVbih,
.pipelineLayout = { .pipelineLayout = {
.setLayout = { .setLayout = {

View File

@@ -243,7 +243,9 @@ void RenderPass::appendCommands(FEngine const& engine,
for (Command const* first = curr, *last = curr + commandCount ; first != last ; ++first) { for (Command const* first = curr, *last = curr + commandCount ; first != last ; ++first) {
if (UTILS_LIKELY((first->key & CUSTOM_MASK) == uint64_t(CustomCommand::PASS))) { if (UTILS_LIKELY((first->key & CUSTOM_MASK) == uint64_t(CustomCommand::PASS))) {
auto ma = first->info.mi->getMaterial(); auto ma = first->info.mi->getMaterial();
ma->prepareProgram(first->info.materialVariant, CompilerPriorityQueue::CRITICAL); ma->prepareProgram(first->info.materialVariant,
first->info.mi->getMutableSpecConstants(),
CompilerPriorityQueue::CRITICAL);
} }
} }
} }
@@ -1096,7 +1098,8 @@ void RenderPass::Executor::execute(FEngine const& engine, DriverApi& driver,
} }
assert_invariant(ma); assert_invariant(ma);
pipeline.program = ma->getProgram(info.materialVariant); pipeline.program = ma->getProgram(info.materialVariant,
info.mi->getMutableSpecConstants());
if (UTILS_UNLIKELY(memcmp(&pipeline, &currentPipeline, sizeof(PipelineState)) != 0)) { if (UTILS_UNLIKELY(memcmp(&pipeline, &currentPipeline, sizeof(PipelineState)) != 0)) {
currentPipeline = pipeline; currentPipeline = pipeline;

View File

@@ -364,8 +364,24 @@ FMaterial::FMaterial(FEngine& engine, const Builder& builder,
processBlendingMode(parser); processBlendingMode(parser);
processSpecializationConstants(engine, builder, parser); processSpecializationConstants(engine, builder, parser);
processMutableSpecializationConstants(engine, builder, parser);
processPushConstants(engine, parser); processPushConstants(engine, parser);
processDescriptorSets(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<backend::Handle<backend::HwProgram>>(
1 << (mVariantBits + mMaterialMutableConstants.size()));
precacheDepthVariants(engine); precacheDepthVariants(engine);
#if FILAMENT_ENABLE_MATDBG #if FILAMENT_ENABLE_MATDBG
@@ -466,13 +482,17 @@ void FMaterial::compile(CompilerPriorityQueue const priority,
UserVariantFilterMask const variantFilter = UserVariantFilterMask const variantFilter =
~variantSpec & UserVariantFilterMask(UserVariantFilterBit::ALL); ~variantSpec & UserVariantFilterMask(UserVariantFilterBit::ALL);
int mutableConstantVariations = 1 << mMaterialMutableConstants.size();
if (UTILS_LIKELY(mEngine.getDriverApi().isParallelShaderCompileSupported())) { if (UTILS_LIKELY(mEngine.getDriverApi().isParallelShaderCompileSupported())) {
auto const& variants = isVariantLit() ? auto const& variants = isVariantLit() ?
VariantUtils::getLitVariants() : VariantUtils::getUnlitVariants(); VariantUtils::getLitVariants() : VariantUtils::getUnlitVariants();
for (auto const variant: variants) { for (auto const variant: variants) {
if (!variantFilter || variant == Variant::filterUserVariant(variant, variantFilter)) { if (!variantFilter || variant == Variant::filterUserVariant(variant, variantFilter)) {
if (hasVariant(variant)) { if (hasVariant(variant)) {
prepareProgram(variant, priority); for (int spec = 0; spec < mutableConstantVariations; spec++) {
prepareProgram(variant, Program::MutableSpecConstantsInfo(spec), priority);
}
} }
} }
} }
@@ -553,14 +573,15 @@ bool FMaterial::hasVariant(Variant const variant) const noexcept {
} }
void FMaterial::prepareProgramSlow(Variant const variant, void FMaterial::prepareProgramSlow(Variant const variant,
const backend::Program::MutableSpecConstantsInfo mutableSpecConstants,
backend::CompilerPriorityQueue const priorityQueue) const noexcept { backend::CompilerPriorityQueue const priorityQueue) const noexcept {
assert_invariant(mEngine.hasFeatureLevel(mFeatureLevel)); assert_invariant(mEngine.hasFeatureLevel(mFeatureLevel));
switch (getMaterialDomain()) { switch (getMaterialDomain()) {
case MaterialDomain::SURFACE: case MaterialDomain::SURFACE:
getSurfaceProgramSlow(variant, priorityQueue); getSurfaceProgramSlow(variant, mutableSpecConstants, priorityQueue);
break; break;
case MaterialDomain::POST_PROCESS: case MaterialDomain::POST_PROCESS:
getPostProcessProgramSlow(variant, priorityQueue); getPostProcessProgramSlow(variant, mutableSpecConstants, priorityQueue);
break; break;
case MaterialDomain::COMPUTE: case MaterialDomain::COMPUTE:
// TODO: implement MaterialDomain::COMPUTE // TODO: implement MaterialDomain::COMPUTE
@@ -569,6 +590,7 @@ void FMaterial::prepareProgramSlow(Variant const variant,
} }
void FMaterial::getSurfaceProgramSlow(Variant const variant, void FMaterial::getSurfaceProgramSlow(Variant const variant,
const backend::Program::MutableSpecConstantsInfo mutableSpecConstants,
CompilerPriorityQueue const priorityQueue) const noexcept { CompilerPriorityQueue const priorityQueue) const noexcept {
// filterVariant() has already been applied in generateCommands(), shouldn't be needed here // 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 // if we're unlit, we don't have any bits that correspond to lit materials
@@ -579,25 +601,25 @@ void FMaterial::getSurfaceProgramSlow(Variant const variant,
Variant const vertexVariant = Variant::filterVariantVertex(variant); Variant const vertexVariant = Variant::filterVariantVertex(variant);
Variant const fragmentVariant = Variant::filterVariantFragment(variant); Variant const fragmentVariant = Variant::filterVariantFragment(variant);
Program pb{ getProgramWithVariants(variant, vertexVariant, fragmentVariant) }; Program pb{ getProgramWithVariants(variant, vertexVariant, fragmentVariant, mutableSpecConstants) };
pb.priorityQueue(priorityQueue); pb.priorityQueue(priorityQueue);
pb.multiview( pb.multiview(
mEngine.getConfig().stereoscopicType == StereoscopicType::MULTIVIEW && mEngine.getConfig().stereoscopicType == StereoscopicType::MULTIVIEW &&
Variant::isStereoVariant(variant)); Variant::isStereoVariant(variant));
createAndCacheProgram(std::move(pb), variant); createAndCacheProgram(std::move(pb), variant, mutableSpecConstants);
} }
void FMaterial::getPostProcessProgramSlow(Variant const variant, void FMaterial::getPostProcessProgramSlow(Variant const variant,
const backend::Program::MutableSpecConstantsInfo mutableSpecConstants,
CompilerPriorityQueue const priorityQueue) const noexcept { CompilerPriorityQueue const priorityQueue) const noexcept {
Program pb{ getProgramWithVariants(variant, variant, variant) }; Program pb{ getProgramWithVariants(variant, variant, variant, mutableSpecConstants) };
pb.priorityQueue(priorityQueue); pb.priorityQueue(priorityQueue);
createAndCacheProgram(std::move(pb), variant); createAndCacheProgram(std::move(pb), variant, mutableSpecConstants);
} }
Program FMaterial::getProgramWithVariants( Program FMaterial::getProgramWithVariants(Variant variant,
Variant variant, Variant vertexVariant, Variant fragmentVariant,
Variant vertexVariant, const backend::Program::MutableSpecConstantsInfo mutableSpecConstants) const {
Variant fragmentVariant) const {
FEngine const& engine = mEngine; FEngine const& engine = mEngine;
const ShaderModel sm = engine.getShaderModel(); const ShaderModel sm = engine.getShaderModel();
const bool isNoop = engine.getBackend() == Backend::NOOP; const bool isNoop = engine.getBackend() == Backend::NOOP;
@@ -658,29 +680,34 @@ Program FMaterial::getProgramWithVariants(
mProgramDescriptorBindings[+DescriptorSetBindingPoints::PER_RENDERABLE]); mProgramDescriptorBindings[+DescriptorSetBindingPoints::PER_RENDERABLE]);
program.descriptorBindings(+DescriptorSetBindingPoints::PER_MATERIAL, program.descriptorBindings(+DescriptorSetBindingPoints::PER_MATERIAL,
mProgramDescriptorBindings[+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::VERTEX, mPushConstants[uint8_t(ShaderStage::VERTEX)]);
program.pushConstants(ShaderStage::FRAGMENT, mPushConstants[uint8_t(ShaderStage::FRAGMENT)]); 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; 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; FEngine const& engine = mEngine;
DriverApi& driverApi = mEngine.getDriverApi(); DriverApi& driverApi = mEngine.getDriverApi();
bool const isShared = isSharedVariant(variant); bool const isShared = isSharedVariant(variant);
size_t const cacheIndex = getCachedProgramIndex(variant, mutableSpecConstants);
// Check if the default material has this program cached // Check if the default material has this program cached
if (isShared) { if (isShared) {
FMaterial const* const pDefaultMaterial = engine.getDefaultMaterial(); FMaterial const* const pDefaultMaterial = engine.getDefaultMaterial();
if (pDefaultMaterial) { if (pDefaultMaterial) {
auto const program = pDefaultMaterial->mCachedPrograms[variant.key]; auto const program = pDefaultMaterial->mCachedPrograms[cacheIndex];
if (program) { if (program) {
mCachedPrograms[variant.key] = program; mCachedPrograms[cacheIndex] = program;
return; return;
} }
} }
@@ -689,17 +716,17 @@ void FMaterial::createAndCacheProgram(Program&& p, Variant const variant) const
auto const program = driverApi.createProgram(std::move(p)); auto const program = driverApi.createProgram(std::move(p));
driverApi.setDebugTag(program.getId(), mName); driverApi.setDebugTag(program.getId(), mName);
assert_invariant(program); 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 // 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. // are met (Surface Domain and no custom depth shader), cache it now.
// New Materials will inherit these program automatically. // New Materials will inherit these program automatically.
if (isShared) { if (isShared) {
FMaterial const* const pDefaultMaterial = engine.getDefaultMaterial(); 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 // set the tag to the default material name
driverApi.setDebugTag(program.getId(), mName); driverApi.setDebugTag(program.getId(), mName);
pDefaultMaterial->mCachedPrograms[variant.key] = program; pDefaultMaterial->mCachedPrograms[cacheIndex] = program;
} }
} }
} }
@@ -747,6 +774,14 @@ size_t FMaterial::getParameters(ParameterInfo* parameters, size_t count) const n
return count; return count;
} }
std::optional<uint32_t> 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 #if FILAMENT_ENABLE_MATDBG
// Swaps in an edited version of the original package that was used to create the material. The // Swaps in an edited version of the original package that was used to create the material. The
@@ -805,7 +840,9 @@ void FMaterial::onQueryCallback(void* userdata, VariantList* pActiveVariants) {
#endif // FILAMENT_ENABLE_MATDBG #endif // FILAMENT_ENABLE_MATDBG
[[nodiscard]] Handle<HwProgram> FMaterial::getProgramWithMATDBG(Variant const variant) const noexcept { [[nodiscard]] Handle<HwProgram> FMaterial::getProgramWithMATDBG(Variant const variant,
Program::MutableSpecConstantsInfo const mutableSpecConstants) const noexcept {
size_t cacheIndex = getCachedProgramIndex(variant, mutableSpecConstants);
#if FILAMENT_ENABLE_MATDBG #if FILAMENT_ENABLE_MATDBG
assert_invariant((size_t)variant.key < VARIANT_COUNT); assert_invariant((size_t)variant.key < VARIANT_COUNT);
std::unique_lock lock(mActiveProgramsLock); std::unique_lock lock(mActiveProgramsLock);
@@ -820,13 +857,13 @@ void FMaterial::onQueryCallback(void* userdata, VariantList* pActiveVariants) {
lock.unlock(); lock.unlock();
if (isSharedVariant(variant)) { if (isSharedVariant(variant)) {
FMaterial const* const pDefaultMaterial = mEngine.getDefaultMaterial(); FMaterial const* const pDefaultMaterial = mEngine.getDefaultMaterial();
if (pDefaultMaterial && pDefaultMaterial->mCachedPrograms[variant.key]) { if (pDefaultMaterial && pDefaultMaterial->mCachedPrograms[cacheIndex]) {
return pDefaultMaterial->getProgram(variant); return pDefaultMaterial->getProgram(variant, mutableSpecConstants);
} }
} }
#endif #endif
assert_invariant(mCachedPrograms[variant.key]); assert_invariant(mCachedPrograms[cacheIndex]);
return mCachedPrograms[variant.key]; return mCachedPrograms[cacheIndex];
} }
void FMaterial::destroyPrograms(FEngine& engine, void FMaterial::destroyPrograms(FEngine& engine,
@@ -839,7 +876,7 @@ void FMaterial::destroyPrograms(FEngine& engine,
case MaterialDomain::SURFACE: { case MaterialDomain::SURFACE: {
if (mIsDefaultMaterial || mHasCustomDepthShader) { if (mIsDefaultMaterial || mHasCustomDepthShader) {
// default material, or we have custom depth shaders, we destroy all variants // 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) { if ((k & variantMask) == variantValue) {
// Only destroy if the handle is valid. Not strictly needed, but we have a lot // 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. // of variants, and this generates traffic in the command queue.
@@ -857,7 +894,7 @@ void FMaterial::destroyPrograms(FEngine& engine,
UTILS_UNUSED_IN_RELEASE UTILS_UNUSED_IN_RELEASE
auto const UTILS_NULLABLE pDefaultMaterial = engine.getDefaultMaterial(); 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) { if ((k & variantMask) == variantValue) {
// Only destroy if the handle is valid. Not strictly needed, but we have a lot // 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. // of variant, and this generates traffic in the command queue.
@@ -869,7 +906,7 @@ void FMaterial::destroyPrograms(FEngine& engine,
// During Engine::shutdown, auto-cleanup destroys the // During Engine::shutdown, auto-cleanup destroys the
// default material first // default material first
assert_invariant(!pDefaultMaterial || assert_invariant(!pDefaultMaterial ||
pDefaultMaterial->mCachedPrograms[k]); pDefaultMaterial->mCachedPrograms[k & (VARIANT_COUNT - 1)]);
// we don't own this variant, skip, but clear the entry. // we don't own this variant, skip, but clear the entry.
cachedPrograms[k].clear(); cachedPrograms[k].clear();
continue; continue;
@@ -883,7 +920,7 @@ void FMaterial::destroyPrograms(FEngine& engine,
break; break;
} }
case MaterialDomain::POST_PROCESS: { 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) { if ((k & variantMask) == variantValue) {
// Only destroy if the handle is valid. Not strictly needed, but we have a lot // 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. // of variant, and this generates traffic in the command queue.
@@ -895,8 +932,13 @@ void FMaterial::destroyPrograms(FEngine& engine,
break; break;
} }
case MaterialDomain::COMPUTE: { case MaterialDomain::COMPUTE: {
// Compute programs don't have variants for (size_t k = 0, n = cachedPrograms.size(); k < n; ++k) {
driverApi.destroyProgram(std::move(cachedPrograms[0])); // 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; break;
} }
} }
@@ -1131,6 +1173,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) { void FMaterial::processPushConstants(FEngine&, MaterialParser const* parser) {
FixedCapacityVector<Program::PushConstant>& vertexConstants = FixedCapacityVector<Program::PushConstant>& vertexConstants =
mPushConstants[uint8_t(ShaderStage::VERTEX)]; mPushConstants[uint8_t(ShaderStage::VERTEX)];
@@ -1186,7 +1241,9 @@ void FMaterial::precacheDepthVariants(FEngine& engine) {
} }
assert_invariant(Variant::isValidDepthVariant(variant)); assert_invariant(Variant::isValidDepthVariant(variant));
if (hasVariant(variant)) { if (hasVariant(variant)) {
prepareProgram(variant, CompilerPriorityQueue::HIGH); // The default material shouldn't ever have mutable spec constants.
prepareProgram(variant, Program::MutableSpecConstantsInfo(0),
CompilerPriorityQueue::HIGH);
} }
} }
return; return;
@@ -1199,9 +1256,16 @@ void FMaterial::precacheDepthVariants(FEngine& engine) {
FMaterial const* const pDefaultMaterial = engine.getDefaultMaterial(); FMaterial const* const pDefaultMaterial = engine.getDefaultMaterial();
assert_invariant(pDefaultMaterial); assert_invariant(pDefaultMaterial);
auto const allDepthVariants = VariantUtils::getDepthVariants(); auto const allDepthVariants = VariantUtils::getDepthVariants();
int mutableConstantVariations = 1 << mMaterialMutableConstants.size();
for (auto const variant: allDepthVariants) { for (auto const variant: allDepthVariants) {
assert_invariant(Variant::isValidDepthVariant(variant)); 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];
}
} }
} }
} }

View File

@@ -132,8 +132,9 @@ public:
FEngine& getEngine() const noexcept { return mEngine; } FEngine& getEngine() const noexcept { return mEngine; }
bool isCached(Variant const variant) const noexcept { bool isCached(Variant const variant,
return bool(mCachedPrograms[variant.key]); const backend::Program::MutableSpecConstantsInfo mutableSpecConstants) const noexcept {
return bool(mCachedPrograms[getCachedProgramIndex(variant, mutableSpecConstants)]);
} }
void invalidate(Variant::type_t variantMask = 0, Variant::type_t variantValue = 0) noexcept; void invalidate(Variant::type_t variantMask = 0, Variant::type_t variantValue = 0) noexcept;
@@ -142,22 +143,26 @@ public:
// Must be called outside of backend render pass. // Must be called outside of backend render pass.
// Must be called before getProgram() below. // Must be called before getProgram() below.
void prepareProgram(Variant const variant, void prepareProgram(Variant const variant,
backend::Program::MutableSpecConstantsInfo const mutableSpecConstants,
backend::CompilerPriorityQueue const priorityQueue) const noexcept { backend::CompilerPriorityQueue const priorityQueue) const noexcept {
// prepareProgram() is called for each RenderPrimitive in the scene, so it must be efficient. // prepareProgram() is called for each RenderPrimitive in the scene, so it must be efficient.
if (UTILS_UNLIKELY(!isCached(variant))) { if (UTILS_UNLIKELY(!isCached(variant, mutableSpecConstants))) {
prepareProgramSlow(variant, priorityQueue); 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(). // Must be called after prepareProgram().
[[nodiscard]] [[nodiscard]]
backend::Handle<backend::HwProgram> getProgram(Variant const variant) const noexcept { backend::Handle<backend::HwProgram> getProgram(Variant const variant,
backend::Program::MutableSpecConstantsInfo const mutableSpecConstants) const noexcept {
#if FILAMENT_ENABLE_MATDBG #if FILAMENT_ENABLE_MATDBG
return getProgramWithMATDBG(variant); return getProgramWithMATDBG(variant, mutableSpecConstants);
#endif #endif
assert_invariant(mCachedPrograms[variant.key]); size_t index = getCachedProgramIndex(variant, mutableSpecConstants);
return mCachedPrograms[variant.key]; assert_invariant(mCachedPrograms[index]);
return mCachedPrograms[index];
} }
// MaterialInstance::use() binds descriptor sets before drawing. For shared variants, // MaterialInstance::use() binds descriptor sets before drawing. For shared variants,
@@ -178,7 +183,8 @@ public:
} }
[[nodiscard]] [[nodiscard]]
backend::Handle<backend::HwProgram> getProgramWithMATDBG(Variant variant) const noexcept; backend::Handle<backend::HwProgram> getProgramWithMATDBG(Variant variant,
backend::Program::MutableSpecConstantsInfo const mutableSpecConstants) const noexcept;
bool isVariantLit() const noexcept { return mIsVariantLit; } bool isVariantLit() const noexcept { return mIsVariantLit; }
@@ -235,6 +241,12 @@ public:
} }
size_t getParameters(ParameterInfo* parameters, size_t count) const noexcept; size_t getParameters(ParameterInfo* parameters, size_t count) const noexcept;
uint32_t getMutableConstantCount() const noexcept { return mMaterialMutableConstants.size(); }
std::optional<uint32_t> getMutableConstantId(std::string_view name) const noexcept;
backend::Program::MutableSpecConstantsInfo getDefaultMutableConstants() const noexcept {
return mDefaultMutableSpecializationConstants;
}
uint32_t generateMaterialInstanceId() const noexcept { return mMaterialInstanceId++; } uint32_t generateMaterialInstanceId() const noexcept { return mMaterialInstanceId++; }
void destroyPrograms(FEngine& engine, void destroyPrograms(FEngine& engine,
@@ -288,34 +300,48 @@ public:
private: private:
bool hasVariant(Variant variant) const noexcept; bool hasVariant(Variant variant) const noexcept;
void prepareProgramSlow(Variant variant, void prepareProgramSlow(Variant variant,
backend::Program::MutableSpecConstantsInfo mutableSpecConstants,
CompilerPriorityQueue priorityQueue) const noexcept; CompilerPriorityQueue priorityQueue) const noexcept;
void getSurfaceProgramSlow(Variant variant, void getSurfaceProgramSlow(Variant variant,
backend::Program::MutableSpecConstantsInfo mutableSpecConstants,
CompilerPriorityQueue priorityQueue) const noexcept; CompilerPriorityQueue priorityQueue) const noexcept;
void getPostProcessProgramSlow(Variant variant, void getPostProcessProgramSlow(Variant variant,
backend::Program::MutableSpecConstantsInfo mutableSpecConstants,
CompilerPriorityQueue priorityQueue) const noexcept; CompilerPriorityQueue priorityQueue) const noexcept;
backend::Program getProgramWithVariants(Variant variant, 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 processBlendingMode(MaterialParser const* parser);
void processSpecializationConstants(FEngine& engine, Builder const& builder, void processSpecializationConstants(FEngine& engine, Builder const& builder,
MaterialParser const* parser); MaterialParser const* parser);
void processMutableSpecializationConstants(FEngine& engine, Builder const& builder,
MaterialParser const* parser);
void processPushConstants(FEngine& engine, MaterialParser const* parser); void processPushConstants(FEngine& engine, MaterialParser const* parser);
void precacheDepthVariants(FEngine& engine); void precacheDepthVariants(FEngine& engine);
void processDescriptorSets(FEngine& engine, MaterialParser const* parser); 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 { inline bool isSharedVariant(Variant const variant) const {
return (mMaterialDomain == MaterialDomain::SURFACE) && !mIsDefaultMaterial && return (mMaterialDomain == MaterialDomain::SURFACE) && !mIsDefaultMaterial &&
!mHasCustomDepthShader && Variant::isValidDepthVariant(variant); !mHasCustomDepthShader && Variant::isValidDepthVariant(variant);
} }
inline size_t getCachedProgramIndex(const Variant variant,
const backend::Program::MutableSpecConstantsInfo mutableSpecConstants) const noexcept {
return static_cast<size_t>(variant.key) |
(static_cast<size_t>(mutableSpecConstants.getValue()) << mVariantBits);
}
// try to order by frequency of use // try to order by frequency of use
mutable std::array<backend::Handle<backend::HwProgram>, VARIANT_COUNT> mCachedPrograms; mutable utils::FixedCapacityVector<backend::Handle<backend::HwProgram>> mCachedPrograms;
DescriptorSetLayout mPerViewDescriptorSetLayout; DescriptorSetLayout mPerViewDescriptorSetLayout;
DescriptorSetLayout mPerViewDescriptorSetLayoutVsm; DescriptorSetLayout mPerViewDescriptorSetLayoutVsm;
DescriptorSetLayout mDescriptorSetLayout; DescriptorSetLayout mDescriptorSetLayout;
@@ -351,6 +377,7 @@ private:
bool mHasCustomDepthShader = false; bool mHasCustomDepthShader = false;
bool mIsDefaultMaterial = false; bool mIsDefaultMaterial = false;
bool mSpecularAntiAliasing = false; bool mSpecularAntiAliasing = false;
uint8_t mVariantBits;
// reserve some space to construct the default material instance // reserve some space to construct the default material instance
mutable FMaterialInstance* mDefaultMaterialInstance = nullptr; mutable FMaterialInstance* mDefaultMaterialInstance = nullptr;
@@ -375,6 +402,13 @@ private:
// current specialization constants for the HwProgram // current specialization constants for the HwProgram
utils::FixedCapacityVector<backend::Program::SpecializationConstant> mSpecializationConstants; utils::FixedCapacityVector<backend::Program::SpecializationConstant> mSpecializationConstants;
// Mutable constants defined by this Material.
utils::FixedCapacityVector<MaterialMutableConstant> mMaterialMutableConstants;
// A map from the Constant name to the mMutableMaterialConstant index
std::unordered_map<std::string_view, uint32_t> mMutableSpecializationConstantsNameToIndex;
// Default values for the mutable constants.
backend::Program::MutableSpecConstantsInfo mDefaultMutableSpecializationConstants;
// current push constants for the HwProgram // current push constants for the HwProgram
std::array<utils::FixedCapacityVector<backend::Program::PushConstant>, std::array<utils::FixedCapacityVector<backend::Program::PushConstant>,
backend::Program::SHADER_TYPE_COUNT> backend::Program::SHADER_TYPE_COUNT>

View File

@@ -63,6 +63,7 @@ FMaterialInstance::FMaterialInstance(FEngine& engine, FMaterial const* material,
const char* name) noexcept const char* name) noexcept
: mMaterial(material), : mMaterial(material),
mDescriptorSet("MaterialInstance", material->getDescriptorSetLayout()), mDescriptorSet("MaterialInstance", material->getDescriptorSetLayout()),
mConstants(material->getDefaultMutableConstants()),
mCulling(CullingMode::BACK), mCulling(CullingMode::BACK),
mShadowCulling(CullingMode::BACK), mShadowCulling(CullingMode::BACK),
mDepthFunc(RasterState::DepthFunc::LE), mDepthFunc(RasterState::DepthFunc::LE),
@@ -128,6 +129,7 @@ FMaterialInstance::FMaterialInstance(FEngine& engine,
mTextureParameters(other->mTextureParameters), mTextureParameters(other->mTextureParameters),
mDescriptorSet(other->mDescriptorSet.duplicate( mDescriptorSet(other->mDescriptorSet.duplicate(
"MaterialInstance", mMaterial->getDescriptorSetLayout())), "MaterialInstance", mMaterial->getDescriptorSetLayout())),
mConstants(other->mConstants),
mPolygonOffset(other->mPolygonOffset), mPolygonOffset(other->mPolygonOffset),
mStencilState(other->mStencilState), mStencilState(other->mStencilState),
mMaskThreshold(other->mMaskThreshold), mMaskThreshold(other->mMaskThreshold),

View File

@@ -33,6 +33,7 @@
#include <backend/DriverEnums.h> #include <backend/DriverEnums.h>
#include <backend/Handle.h> #include <backend/Handle.h>
#include <backend/Program.h>
#include <utils/BitmaskEnum.h> #include <utils/BitmaskEnum.h>
#include <utils/bitset.h> #include <utils/bitset.h>
@@ -68,7 +69,7 @@ public:
void terminate(FEngine& engine); void terminate(FEngine& engine);
void commitStreamUniformAssociations(FEngine::DriverApi& driver); void commitStreamUniformAssociations(FEngine::DriverApi& driver);
void commit(FEngine& engine) const; void commit(FEngine& engine) const;
void commit(FEngine::DriverApi& driver) const; void commit(FEngine::DriverApi& driver) const;
@@ -244,6 +245,10 @@ public:
using MaterialInstance::setParameter; using MaterialInstance::setParameter;
const backend::Program::MutableSpecConstantsInfo getMutableSpecConstants() const noexcept {
return mConstants;
}
private: private:
friend class FMaterial; friend class FMaterial;
friend class MaterialInstance; friend class MaterialInstance;
@@ -266,6 +271,12 @@ private:
template<typename T> template<typename T>
T getParameterImpl(std::string_view name) const; T getParameterImpl(std::string_view name) const;
template<typename T>
void setConstantImpl(std::string_view name, T const& value);
template<typename T>
T getConstantImpl(std::string_view name) const;
// keep these grouped, they're accessed together in the render-loop // keep these grouped, they're accessed together in the render-loop
FMaterial const* mMaterial = nullptr; FMaterial const* mMaterial = nullptr;
@@ -279,6 +290,7 @@ private:
mutable DescriptorSet mDescriptorSet; mutable DescriptorSet mDescriptorSet;
UniformBuffer mUniforms; UniformBuffer mUniforms;
bool mHasStreamUniformAssociations = false; bool mHasStreamUniformAssociations = false;
backend::Program::MutableSpecConstantsInfo mConstants;
backend::PolygonOffset mPolygonOffset{}; backend::PolygonOffset mPolygonOffset{};
backend::StencilState mStencilState{}; backend::StencilState mStencilState{};

View File

@@ -54,6 +54,7 @@ enum UTILS_PUBLIC ChunkType : uint64_t {
MaterialDescriptorSetLayoutInfo = charTo64bitNum("MAT_DSLI"), MaterialDescriptorSetLayoutInfo = charTo64bitNum("MAT_DSLI"),
MaterialProperties = charTo64bitNum("MAT_PROP"), MaterialProperties = charTo64bitNum("MAT_PROP"),
MaterialConstants = charTo64bitNum("MAT_CONS"), MaterialConstants = charTo64bitNum("MAT_CONS"),
MaterialMutableConstants = charTo64bitNum("MAT_MCNS"),
MaterialPushConstants = charTo64bitNum("MAT_PCON"), MaterialPushConstants = charTo64bitNum("MAT_PCON"),
MaterialName = charTo64bitNum("MAT_NAME"), MaterialName = charTo64bitNum("MAT_NAME"),

View File

@@ -30,7 +30,19 @@ struct MaterialConstant {
ConstantType type; ConstantType type;
MaterialConstant() = default; 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) {}
}; };
} }

View File

@@ -319,14 +319,15 @@ public:
MaterialBuilder& parameter(const char* name, size_t size, UniformType type, MaterialBuilder& parameter(const char* name, size_t size, UniformType type,
ParameterPrecision precision = ParameterPrecision::DEFAULT); ParameterPrecision precision = ParameterPrecision::DEFAULT);
//! Add a constant parameter to this material. //! Add a (mutable) constant parameter to this material.
template<typename T> template<typename T>
using is_supported_constant_parameter_t = typename std::enable_if< using is_supported_constant_parameter_t = typename std::enable_if<
std::is_same<int32_t, T>::value || std::is_same<int32_t, T>::value ||
std::is_same<float, T>::value || std::is_same<float, T>::value ||
std::is_same<bool, T>::value>::type; std::is_same<bool, T>::value>::type;
template<typename T, typename = is_supported_constant_parameter_t<T>> template<typename T, typename = is_supported_constant_parameter_t<T>>
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. * Add a sampler parameter to this material.
@@ -927,6 +928,7 @@ private:
PropertyList mProperties; PropertyList mProperties;
ParameterList mParameters; ParameterList mParameters;
ConstantList mConstants; ConstantList mConstants;
ConstantList mMutableConstants;
PushConstantList mPushConstants; PushConstantList mPushConstants;
SubpassList mSubpasses; SubpassList mSubpasses;
VariableList mVariables; VariableList mVariables;

View File

@@ -322,12 +322,19 @@ MaterialBuilder& MaterialBuilder::parameter(const char* name, SamplerType sample
} }
template<typename T, typename> template<typename T, typename>
MaterialBuilder& MaterialBuilder::constant(const char* name, ConstantType const type, T defaultValue) { MaterialBuilder& MaterialBuilder::constant(const char* name, ConstantType const type,
auto result = std::find_if(mConstants.begin(), mConstants.end(), [name](const Constant& c) { bool isMutable, T defaultValue) {
return c.name == CString(name); ConstantList& constants = isMutable ? mMutableConstants : mConstants;
}); const ConstantList& otherConstants = isMutable ? mConstants : mMutableConstants;
FILAMENT_CHECK_POSTCONDITION(result == mConstants.end()) 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 << "."; << "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 { Constant constant {
.name = CString(name), .name = CString(name),
.type = type, .type = type,
@@ -359,15 +366,20 @@ MaterialBuilder& MaterialBuilder::constant(const char* name, ConstantType const
assert_invariant(false); 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; return *this;
} }
template MaterialBuilder& MaterialBuilder::constant<int32_t>( template MaterialBuilder& MaterialBuilder::constant<int32_t>(
const char* name, ConstantType type, int32_t defaultValue); const char* name, ConstantType type, bool isMutable, int32_t defaultValue);
template MaterialBuilder& MaterialBuilder::constant<float>( template MaterialBuilder& MaterialBuilder::constant<float>(
const char* name, ConstantType type, float defaultValue); const char* name, ConstantType type, bool isMutable, float defaultValue);
template MaterialBuilder& MaterialBuilder::constant<bool>( template MaterialBuilder& MaterialBuilder::constant<bool>(
const char* name, ConstantType type, bool defaultValue); const char* name, ConstantType type, bool isMutable, bool defaultValue);
MaterialBuilder& MaterialBuilder::buffer(BufferInterfaceBlock bib) { MaterialBuilder& MaterialBuilder::buffer(BufferInterfaceBlock bib) {
FILAMENT_CHECK_POSTCONDITION(mBuffers.size() < MAX_BUFFERS_COUNT) << "Too many buffers"; FILAMENT_CHECK_POSTCONDITION(mBuffers.size() < MAX_BUFFERS_COUNT) << "Too many buffers";
@@ -946,7 +958,7 @@ bool MaterialBuilder::generateShaders(JobSystem& jobSystem, const std::vector<Va
BlobDictionary spirvDictionary; BlobDictionary spirvDictionary;
// End: must be protected by lock // End: must be protected by lock
ShaderGenerator sg(mProperties, mVariables, mOutputs, mDefines, mConstants, mPushConstants, ShaderGenerator sg(mProperties, mVariables, mOutputs, mDefines, mConstants, mMutableConstants, mPushConstants,
mMaterialFragmentCode.getResolved(), mMaterialFragmentCode.getLineOffset(), mMaterialFragmentCode.getResolved(), mMaterialFragmentCode.getLineOffset(),
mMaterialVertexCode.getResolved(), mMaterialVertexCode.getLineOffset(), mMaterialVertexCode.getResolved(), mMaterialVertexCode.getLineOffset(),
mMaterialDomain); mMaterialDomain);
@@ -1548,7 +1560,7 @@ bool MaterialBuilder::needsStandardDepthProgram() const noexcept {
std::string MaterialBuilder::peek(backend::ShaderStage const stage, std::string MaterialBuilder::peek(backend::ShaderStage const stage,
const CodeGenParams& params, const PropertyList& properties) noexcept { const CodeGenParams& params, const PropertyList& properties) noexcept {
ShaderGenerator const sg(properties, mVariables, mOutputs, mDefines, mConstants, mPushConstants, ShaderGenerator const sg(properties, mVariables, mOutputs, mDefines, mConstants, mMutableConstants, mPushConstants,
mMaterialFragmentCode.getResolved(), mMaterialFragmentCode.getLineOffset(), mMaterialFragmentCode.getResolved(), mMaterialFragmentCode.getLineOffset(),
mMaterialVertexCode.getResolved(), mMaterialVertexCode.getLineOffset(), mMaterialVertexCode.getResolved(), mMaterialVertexCode.getLineOffset(),
mMaterialDomain); mMaterialDomain);
@@ -1676,9 +1688,14 @@ void MaterialBuilder::writeCommonChunks(ChunkContainer& container, MaterialInfo&
// User constant parameters // User constant parameters
FixedCapacityVector<MaterialConstant> constantsEntry(mConstants.size()); FixedCapacityVector<MaterialConstant> constantsEntry(mConstants.size());
std::transform(mConstants.begin(), mConstants.end(), constantsEntry.begin(), 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<MaterialConstantParametersChunk>(std::move(constantsEntry)); container.push<MaterialConstantParametersChunk>(std::move(constantsEntry));
FixedCapacityVector<MaterialMutableConstant> mutableConstantsEntry(mMutableConstants.size());
std::transform(mMutableConstants.begin(), mMutableConstants.end(), mutableConstantsEntry.begin(),
[](Constant const& c) { return MaterialMutableConstant(c.name, c.defaultValue.b); });
container.push<MaterialMutableConstantParametersChunk>(std::move(mutableConstantsEntry));
FixedCapacityVector<MaterialPushConstant> pushConstantsEntry(mPushConstants.size()); FixedCapacityVector<MaterialPushConstant> pushConstantsEntry(mPushConstants.size());
std::transform(mPushConstants.begin(), mPushConstants.end(), pushConstantsEntry.begin(), std::transform(mPushConstants.begin(), mPushConstants.end(), pushConstantsEntry.begin(),
[](PushConstant const& c) { [](PushConstant const& c) {

View File

@@ -118,6 +118,20 @@ void MaterialConstantParametersChunk::flatten(Flattener& f) {
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
MaterialMutableConstantParametersChunk::MaterialMutableConstantParametersChunk(
FixedCapacityVector<MaterialMutableConstant> 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( MaterialPushConstantParametersChunk::MaterialPushConstantParametersChunk(
CString const& structVarName, FixedCapacityVector<MaterialPushConstant> constants) CString const& structVarName, FixedCapacityVector<MaterialPushConstant> constants)
: Chunk(MaterialPushConstants), : Chunk(MaterialPushConstants),

View File

@@ -18,6 +18,7 @@
#define TNT_FILAMAT_MAT_INTEFFACE_BLOCK_CHUNK_H #define TNT_FILAMAT_MAT_INTEFFACE_BLOCK_CHUNK_H
#include "Chunk.h" #include "Chunk.h"
#include "private/filament/ConstantInfo.h"
#include <backend/Program.h> #include <backend/Program.h>
@@ -91,6 +92,20 @@ private:
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
class MaterialMutableConstantParametersChunk final : public Chunk {
public:
explicit MaterialMutableConstantParametersChunk(
FixedCapacityVector<filament::MaterialMutableConstant> constants);
~MaterialMutableConstantParametersChunk() override = default;
private:
void flatten(Flattener&) override;
FixedCapacityVector<filament::MaterialMutableConstant> mConstants;
};
// ------------------------------------------------------------------------------------------------
class MaterialPushConstantParametersChunk final : public Chunk { class MaterialPushConstantParametersChunk final : public Chunk {
public: public:
explicit MaterialPushConstantParametersChunk(CString const& structVarName, explicit MaterialPushConstantParametersChunk(CString const& structVarName,

View File

@@ -285,10 +285,17 @@ void ShaderGenerator::appendShader(io::sstream& ss,
} }
} }
void ShaderGenerator::generateUserSpecConstants( void ShaderGenerator::generateAllUserSpecConstants(const CodeGenerator& cg, io::sstream& fs,
const CodeGenerator& cg, io::sstream& fs, MaterialBuilder::ConstantList const& constants) { 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. // 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) { for (const auto& constant : constants) {
std::string const fullName = std::string("materialConstants_") + constant.name.c_str(); std::string const fullName = std::string("materialConstants_") + constant.name.c_str();
switch (constant.type) { switch (constant.type) {
@@ -316,6 +323,7 @@ ShaderGenerator::ShaderGenerator(
MaterialBuilder::OutputList const& outputs, MaterialBuilder::OutputList const& outputs,
MaterialBuilder::PreprocessorDefineList const& defines, MaterialBuilder::PreprocessorDefineList const& defines,
MaterialBuilder::ConstantList const& constants, MaterialBuilder::ConstantList const& constants,
MaterialBuilder::ConstantList const& mutableConstants,
MaterialBuilder::PushConstantList const& pushConstants, MaterialBuilder::PushConstantList const& pushConstants,
CString const& materialCode, size_t const lineOffset, CString const& materialCode, size_t const lineOffset,
CString const& materialVertexCode, size_t const vertexLineOffset, CString const& materialVertexCode, size_t const vertexLineOffset,
@@ -338,6 +346,7 @@ ShaderGenerator::ShaderGenerator(
mMaterialDomain = materialDomain; mMaterialDomain = materialDomain;
mDefines = defines; mDefines = defines;
mConstants = constants; mConstants = constants;
mMutableConstants = mutableConstants;
mPushConstants = pushConstants; mPushConstants = pushConstants;
if (mMaterialFragmentCode.empty()) { if (mMaterialFragmentCode.empty()) {
@@ -394,7 +403,7 @@ std::string ShaderGenerator::createSurfaceVertexProgram(ShaderModel const shader
cg.generateCommonProlog(vs, ShaderStage::VERTEX, material, variant); 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 // 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 // we're in masked mode because fragment shader needs the color varyings
@@ -527,7 +536,7 @@ std::string ShaderGenerator::createSurfaceFragmentProgram(ShaderModel const shad
io::sstream fs; io::sstream fs;
cg.generateCommonProlog(fs, ShaderStage::FRAGMENT, material, variant); cg.generateCommonProlog(fs, ShaderStage::FRAGMENT, material, variant);
generateUserSpecConstants(cg, fs, mConstants); generateAllUserSpecConstants(cg, fs, mConstants, mMutableConstants);
generateSurfaceMaterialVariantDefines( generateSurfaceMaterialVariantDefines(
fs, ShaderStage::FRAGMENT, featureLevel, material, variant); fs, ShaderStage::FRAGMENT, featureLevel, material, variant);
@@ -683,7 +692,7 @@ std::string ShaderGenerator::createSurfaceComputeProgram(ShaderModel const shade
cg.generateCommonProlog(s, ShaderStage::COMPUTE, material, {}); cg.generateCommonProlog(s, ShaderStage::COMPUTE, material, {});
generateUserSpecConstants(cg, s, mConstants); generateAllUserSpecConstants(cg, s, mConstants, mMutableConstants);
CodeGenerator::generateSurfaceTypes(s, ShaderStage::COMPUTE); CodeGenerator::generateSurfaceTypes(s, ShaderStage::COMPUTE);
@@ -723,7 +732,7 @@ std::string ShaderGenerator::createPostProcessVertexProgram(ShaderModel const sm
io::sstream vs; io::sstream vs;
cg.generateCommonProlog(vs, ShaderStage::VERTEX, material, {}); cg.generateCommonProlog(vs, ShaderStage::VERTEX, material, {});
generateUserSpecConstants(cg, vs, mConstants); generateAllUserSpecConstants(cg, vs, mConstants, mMutableConstants);
CodeGenerator::generateDefine(vs, "LOCATION_POSITION", uint32_t(POSITION)); CodeGenerator::generateDefine(vs, "LOCATION_POSITION", uint32_t(POSITION));
@@ -767,7 +776,7 @@ std::string ShaderGenerator::createPostProcessFragmentProgram(ShaderModel const
io::sstream fs; io::sstream fs;
cg.generateCommonProlog(fs, ShaderStage::FRAGMENT, material, {}); cg.generateCommonProlog(fs, ShaderStage::FRAGMENT, material, {});
generateUserSpecConstants(cg, fs, mConstants); generateAllUserSpecConstants(cg, fs, mConstants, mMutableConstants);
generatePostProcessMaterialVariantDefines(fs, PostProcessVariant(variant)); generatePostProcessMaterialVariantDefines(fs, PostProcessVariant(variant));

View File

@@ -51,6 +51,7 @@ public:
MaterialBuilder::OutputList const& outputs, MaterialBuilder::OutputList const& outputs,
MaterialBuilder::PreprocessorDefineList const& defines, MaterialBuilder::PreprocessorDefineList const& defines,
MaterialBuilder::ConstantList const& constants, MaterialBuilder::ConstantList const& constants,
MaterialBuilder::ConstantList const& mutableConstants,
MaterialBuilder::PushConstantList const& pushConstants, MaterialBuilder::PushConstantList const& pushConstants,
utils::CString const& materialCode, utils::CString const& materialCode,
size_t lineOffset, size_t lineOffset,
@@ -104,9 +105,14 @@ private:
static void generatePostProcessMaterialVariantDefines(utils::io::sstream& out, static void generatePostProcessMaterialVariantDefines(utils::io::sstream& out,
filament::PostProcessVariant variant) noexcept; 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( static void generateUserSpecConstants(
const CodeGenerator& cg, utils::io::sstream& fs, 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, std::string createPostProcessVertexProgram(filament::backend::ShaderModel sm,
MaterialBuilder::TargetApi targetApi, MaterialBuilder::TargetLanguage targetLanguage, MaterialBuilder::TargetApi targetApi, MaterialBuilder::TargetLanguage targetLanguage,
@@ -135,6 +141,7 @@ private:
MaterialBuilder::MaterialDomain mMaterialDomain; MaterialBuilder::MaterialDomain mMaterialDomain;
MaterialBuilder::PreprocessorDefineList mDefines; MaterialBuilder::PreprocessorDefineList mDefines;
MaterialBuilder::ConstantList mConstants; MaterialBuilder::ConstantList mConstants;
MaterialBuilder::ConstantList mMutableConstants;
MaterialBuilder::PushConstantList mPushConstants; MaterialBuilder::PushConstantList mPushConstants;
utils::CString mMaterialFragmentCode; // fragment or compute code utils::CString mMaterialFragmentCode; // fragment or compute code
utils::CString mMaterialVertexCode; utils::CString mMaterialVertexCode;

View File

@@ -888,10 +888,10 @@ TEST_F(MaterialCompiler, ConstantParameter) {
} }
)"); )");
filamat::MaterialBuilder builder; filamat::MaterialBuilder builder;
builder.constant("myFloatConstant", ConstantType::FLOAT, 1.0f); builder.constant("myFloatConstant", ConstantType::FLOAT, /*isMutable=*/false, 1.0f);
builder.constant("myIntConstant", ConstantType::INT, 123); builder.constant("myIntConstant", ConstantType::INT, /*isMutable=*/false, 123);
builder.constant("myBoolConstant", ConstantType::BOOL, true); builder.constant("myBoolConstant", ConstantType::BOOL, /*isMutable=*/false, true);
builder.constant<bool>("myOtherBoolConstant", ConstantType::BOOL); builder.constant<bool>("myOtherBoolConstant", ConstantType::BOOL, /*isMutable=*/false);
builder.shading(filament::Shading::LIT); builder.shading(filament::Shading::LIT);
builder.material(shaderCode.c_str()); builder.material(shaderCode.c_str());
@@ -904,8 +904,8 @@ TEST_F(MaterialCompiler, ConstantParameterSameName) {
#ifdef __EXCEPTIONS #ifdef __EXCEPTIONS
EXPECT_THROW({ EXPECT_THROW({
filamat::MaterialBuilder builder; filamat::MaterialBuilder builder;
builder.constant("myFloatConstant", ConstantType::FLOAT, 1.0f); builder.constant("myFloatConstant", ConstantType::FLOAT, /*isMutable=*/false, 1.0f);
builder.constant("myFloatConstant", ConstantType::FLOAT, 1.0f); builder.constant("myFloatConstant", ConstantType::FLOAT, /*isMutable=*/false, 1.0f);
}, utils::PostconditionPanic); }, utils::PostconditionPanic);
#endif #endif
} }
@@ -914,7 +914,7 @@ TEST_F(MaterialCompiler, ConstantParameterWrongType) {
#ifdef __EXCEPTIONS #ifdef __EXCEPTIONS
EXPECT_THROW({ EXPECT_THROW({
filamat::MaterialBuilder builder; filamat::MaterialBuilder builder;
builder.constant("myFloatConstant", ConstantType::FLOAT, 10); builder.constant("myFloatConstant", ConstantType::FLOAT, /*isMutable=*/false, 10);
}, utils::PostconditionPanic); }, utils::PostconditionPanic);
#endif #endif
} }

View File

@@ -352,6 +352,19 @@ static bool processConstant(MaterialBuilder& builder, const JsonishObject& jsonO
return false; 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 typeString = typeValue->toJsonString()->getString();
auto nameString = nameValue->toJsonString()->getString(); auto nameString = nameValue->toJsonString()->getString();
const JsonishValue* defaultValue = jsonObject.getValue("default"); const JsonishValue* defaultValue = jsonObject.getValue("default");
@@ -370,7 +383,7 @@ static bool processConstant(MaterialBuilder& builder, const JsonishObject& jsonO
// FIXME: Jsonish doesn't distinguish between integers and floats. // FIXME: Jsonish doesn't distinguish between integers and floats.
intDefault = (int32_t)defaultValue->toJsonNumber()->getFloat(); intDefault = (int32_t)defaultValue->toJsonNumber()->getFloat();
} }
builder.constant(nameString.c_str(), type, intDefault); builder.constant(nameString.c_str(), type, isMutable, intDefault);
break; break;
} }
case ConstantType::FLOAT: { case ConstantType::FLOAT: {
@@ -383,7 +396,7 @@ static bool processConstant(MaterialBuilder& builder, const JsonishObject& jsonO
} }
floatDefault = defaultValue->toJsonNumber()->getFloat(); floatDefault = defaultValue->toJsonNumber()->getFloat();
} }
builder.constant(nameString.c_str(), type, floatDefault); builder.constant(nameString.c_str(), type, isMutable, floatDefault);
break; break;
} }
case ConstantType::BOOL: case ConstantType::BOOL:
@@ -396,7 +409,7 @@ static bool processConstant(MaterialBuilder& builder, const JsonishObject& jsonO
} }
boolDefault = defaultValue->toJsonBool()->getBool(); boolDefault = defaultValue->toJsonBool()->getBool();
} }
builder.constant(nameString.c_str(), type, boolDefault); builder.constant(nameString.c_str(), type, isMutable, boolDefault);
break; break;
} }
} else { } else {

View File

@@ -19,6 +19,7 @@ set(RESOURCE_BINS)
set(MATERIAL_SRCS set(MATERIAL_SRCS
materials/aiDefaultMat.mat materials/aiDefaultMat.mat
materials/bakedColor.mat materials/bakedColor.mat
materials/bakedColorWithMutableConstants.mat
materials/bakedTexture.mat materials/bakedTexture.mat
materials/aoPreview.mat materials/aoPreview.mat
materials/arrayTexture.mat materials/arrayTexture.mat
@@ -281,6 +282,7 @@ if (NOT ANDROID)
add_demo(lightbulb) add_demo(lightbulb)
add_demo(material_sandbox) add_demo(material_sandbox)
add_demo(multiple_windows) add_demo(multiple_windows)
add_demo(mutable_constants)
# Tint does not support setting gl_PointSize, disable the relevant sample if using WebGPU # Tint does not support setting gl_PointSize, disable the relevant sample if using WebGPU
# https://github.com/gpuweb/gpuweb/issues/1190 # https://github.com/gpuweb/gpuweb/issues/1190
if (NOT FILAMENT_SUPPORTS_WEBGPU) if (NOT FILAMENT_SUPPORTS_WEBGPU)

View File

@@ -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;
}
}
}

View File

@@ -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 <filament/Camera.h>
#include <filament/Engine.h>
#include <filament/IndexBuffer.h>
#include <filament/Material.h>
#include <filament/MaterialInstance.h>
#include <filament/RenderableManager.h>
#include <filament/Scene.h>
#include <filament/Skybox.h>
#include <filament/TransformManager.h>
#include <filament/VertexBuffer.h>
#include <filament/View.h>
#include <imgui.h>
#include <utils/EntityManager.h>
#include <filamentapp/Config.h>
#include <filamentapp/FilamentApp.h>
#include <getopt/getopt.h>
#include <cmath>
#include <iostream>
#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<bool>("swizzle");
bool darken = mi->getConstant<bool>("darken");
ImGui::Checkbox("Swizzle", &swizzle);
ImGui::Checkbox("Darken", &darken);
if (swizzle != mi->getConstant<bool>("swizzle")) {
mi->setConstant("swizzle", swizzle);
}
if (darken != mi->getConstant<bool>("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;
}