Expose specialization constants to materials as constant parameters (#6652)
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -105,6 +105,34 @@ public:
|
||||
*/
|
||||
Builder& package(const void* payload, size_t size);
|
||||
|
||||
template<typename T>
|
||||
using is_supported_constant_parameter_t = typename std::enable_if<
|
||||
std::is_same<int32_t, T>::value ||
|
||||
std::is_same<float, T>::value ||
|
||||
std::is_same<bool, T>::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<typename T, typename = is_supported_constant_parameter_t<T>>
|
||||
Builder& constant(const char* name, size_t nameLength, T value);
|
||||
|
||||
/** inline helper to provide the constant name as a null-terminated C string */
|
||||
template<typename T, typename = is_supported_constant_parameter_t<T>>
|
||||
inline Builder& constant(const char* name, T value) {
|
||||
return constant(name, strlen(name), value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the Material object and returns a pointer to it.
|
||||
*
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#include <private/filament/BufferInterfaceBlock.h>
|
||||
#include <private/filament/SubpassInfo.h>
|
||||
#include <private/filament/Variant.h>
|
||||
#include <private/filament/ConstantInfo.h>
|
||||
|
||||
#include <utils/CString.h>
|
||||
|
||||
@@ -177,6 +178,13 @@ bool MaterialParser::getSamplerBlockBindings(
|
||||
pSamplerGroupInfoList, pSamplerBindingToNameMap);
|
||||
}
|
||||
|
||||
bool MaterialParser::getConstants(utils::FixedCapacityVector<MaterialConstant>* 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<MaterialConstant>* 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<backend::ConstantType>(constantType);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace filament
|
||||
|
||||
@@ -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<std::pair<utils::CString, uint8_t>>* value) const noexcept;
|
||||
bool getSamplerBlockBindings(SamplerGroupBindingInfoList* pSamplerGroupInfoList,
|
||||
SamplerBindingToNameMap* pSamplerBindingToNameMap) const noexcept;
|
||||
bool getConstants(utils::FixedCapacityVector<MaterialConstant>* 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<MaterialConstant>* materialConstants);
|
||||
};
|
||||
|
||||
} // namespace filament
|
||||
|
||||
#endif // TNT_FILAMENT_MATERIALPARSER_H
|
||||
|
||||
@@ -32,8 +32,11 @@
|
||||
#include <backend/Program.h>
|
||||
|
||||
#include <utils/CString.h>
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
#include <utils/Panic.h>
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
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<std::string, std::variant<int32_t, float, bool>> mConstantSpecializations;
|
||||
};
|
||||
|
||||
FMaterial::DefaultMaterialBuilder::DefaultMaterialBuilder() : Material::Builder() {
|
||||
@@ -91,6 +95,17 @@ Material::Builder& Material::Builder::package(const void* payload, size_t size)
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename T, typename>
|
||||
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<int32_t>(const char*, size_t, int32_t);
|
||||
template Material::Builder& Material::Builder::constant<float>(const char*, size_t, float);
|
||||
template Material::Builder& Material::Builder::constant<bool>(const char*, size_t, bool);
|
||||
|
||||
Material* Material::Builder::build(Engine& engine) {
|
||||
std::unique_ptr<MaterialParser> 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<MaterialConstant> 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<int32_t>(value), errorMessage,
|
||||
name.c_str(), mName.c_str_safe(), "int", types[value.index()]);
|
||||
break;
|
||||
case ConstantType::FLOAT:
|
||||
ASSERT_PRECONDITION(std::holds_alternative<float>(value), errorMessage,
|
||||
name.c_str(), mName.c_str_safe(), "float", types[value.index()]);
|
||||
break;
|
||||
case ConstantType::BOOL:
|
||||
ASSERT_PRECONDITION(std::holds_alternative<bool>(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;
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include <private/filament/SamplerInterfaceBlock.h>
|
||||
#include <private/filament/SubpassInfo.h>
|
||||
#include <private/filament/Variant.h>
|
||||
#include <private/filament/ConstantInfo.h>
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/Mutex.h>
|
||||
@@ -232,6 +233,7 @@ private:
|
||||
|
||||
SamplerGroupBindingInfoList mSamplerGroupBindingInfoList;
|
||||
SamplerBindingToNameMap mSamplerBindingToNameMap;
|
||||
utils::FixedCapacityVector<backend::Program::SpecializationConstant> mSpecializationConstants;
|
||||
|
||||
#if FILAMENT_ENABLE_MATDBG
|
||||
matdbg::MaterialKey mDebuggerId;
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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 <backend/DriverEnums.h>
|
||||
|
||||
#include <utils/CString.h>
|
||||
|
||||
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
|
||||
@@ -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:
|
||||
|
||||
@@ -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<std::string, OutputTarget> mStringToOutputTarget;
|
||||
static std::unordered_map<std::string, OutputQualifier> mStringToOutputQualifier;
|
||||
static std::unordered_map<std::string, OutputType> mStringToOutputType;
|
||||
static std::unordered_map<std::string, ConstantType> mStringToConstantType;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <variant>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
@@ -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<typename T>
|
||||
using is_supported_constant_parameter_t = typename std::enable_if<
|
||||
std::is_same<int32_t, T>::value ||
|
||||
std::is_same<float, T>::value ||
|
||||
std::is_same<bool, T>::value>::type;
|
||||
template<typename T, typename = is_supported_constant_parameter_t<T>>
|
||||
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<std::unique_ptr<filament::BufferInterfaceBlock>>;
|
||||
using ConstantList = std::vector<Constant>;
|
||||
|
||||
// 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;
|
||||
|
||||
@@ -160,4 +160,15 @@ std::unordered_map<std::string, SamplerFormat>& Enums::getMap<SamplerFormat>() n
|
||||
return mStringToSamplerFormat;
|
||||
};
|
||||
|
||||
std::unordered_map<std::string, ConstantType > Enums::mStringToConstantType = {
|
||||
{ "int", ConstantType::INT },
|
||||
{ "float", ConstantType::FLOAT },
|
||||
{ "bool", ConstantType::BOOL },
|
||||
};
|
||||
|
||||
template <>
|
||||
std::unordered_map<std::string, ConstantType>& Enums::getMap<ConstantType>() noexcept {
|
||||
return mStringToConstantType;
|
||||
};
|
||||
|
||||
} // namespace filamat
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
#include <private/filament/BufferInterfaceBlock.h>
|
||||
#include <private/filament/SamplerInterfaceBlock.h>
|
||||
#include <private/filament/UibStructs.h>
|
||||
#include <private/filament/ConstantInfo.h>
|
||||
|
||||
#include <utils/JobSystem.h>
|
||||
#include <utils/Log.h>
|
||||
@@ -212,6 +213,53 @@ MaterialBuilder& MaterialBuilder::parameter(const char* name, SamplerType sample
|
||||
return parameter(name, samplerType, SamplerFormat::FLOAT, precision);
|
||||
}
|
||||
|
||||
template<typename T, typename>
|
||||
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<T, int32_t>) {
|
||||
ASSERT_POSTCONDITION(
|
||||
type == ConstantType::INT, errMessage, name, toString(type), "an int");
|
||||
constant.defaultValue.i = defaultValue;
|
||||
} else if constexpr (std::is_same_v<T, float>) {
|
||||
ASSERT_POSTCONDITION(
|
||||
type == ConstantType::FLOAT, errMessage, name, toString(type), "a float");
|
||||
constant.defaultValue.f = defaultValue;
|
||||
} else if constexpr (std::is_same_v<T, bool>) {
|
||||
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<int32_t>(
|
||||
const char* name, ConstantType type, int32_t defaultValue);
|
||||
template MaterialBuilder& MaterialBuilder::constant<float>(
|
||||
const char* name, ConstantType type, float defaultValue);
|
||||
template MaterialBuilder& MaterialBuilder::constant<bool>(
|
||||
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<filament::BufferInterfaceBlock>(std::move(bib)));
|
||||
@@ -699,10 +747,10 @@ bool MaterialBuilder::generateShaders(JobSystem& jobSystem, const std::vector<Va
|
||||
#endif
|
||||
// End: must be protected by lock
|
||||
|
||||
ShaderGenerator sg(
|
||||
mProperties, mVariables, mOutputs, mDefines, mMaterialFragmentCode.getResolved(),
|
||||
mMaterialFragmentCode.getLineOffset(), mMaterialVertexCode.getResolved(),
|
||||
mMaterialVertexCode.getLineOffset(), mMaterialDomain);
|
||||
ShaderGenerator sg(mProperties, mVariables, mOutputs, mDefines, mConstants,
|
||||
mMaterialFragmentCode.getResolved(), mMaterialFragmentCode.getLineOffset(),
|
||||
mMaterialVertexCode.getResolved(), mMaterialVertexCode.getLineOffset(),
|
||||
mMaterialDomain);
|
||||
|
||||
container.emplace<bool>(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<MaterialSamplerInterfaceBlockChunk>(info.sib);
|
||||
|
||||
// User constant parameters
|
||||
utils::FixedCapacityVector<MaterialConstant> constantsEntry(mConstants.size());
|
||||
std::transform(mConstants.begin(), mConstants.end(), constantsEntry.begin(),
|
||||
[](Constant const& c) { return MaterialConstant(c.name.c_str(), c.type); });
|
||||
container.push<MaterialConstantParametersChunk>(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.
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include <private/filament/SamplerInterfaceBlock.h>
|
||||
#include <private/filament/BufferInterfaceBlock.h>
|
||||
#include <private/filament/SubpassInfo.h>
|
||||
#include <private/filament/ConstantInfo.h>
|
||||
|
||||
#include <utility>
|
||||
|
||||
@@ -89,6 +90,20 @@ void MaterialSubpassInterfaceBlockChunk::flatten(Flattener& f) {
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
MaterialConstantParametersChunk::MaterialConstantParametersChunk(
|
||||
utils::FixedCapacityVector<MaterialConstant> 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<uint8_t>(constant.type));
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
MaterialUniformBlockBindingsChunk::MaterialUniformBlockBindingsChunk(
|
||||
utils::FixedCapacityVector<std::pair<std::string_view, filament::UniformBindingPoints>> list)
|
||||
: Chunk(ChunkType::MaterialUniformBindings),
|
||||
|
||||
@@ -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<filament::MaterialConstant> constants);
|
||||
~MaterialConstantParametersChunk() final = default;
|
||||
|
||||
private:
|
||||
void flatten(Flattener&) final;
|
||||
|
||||
utils::FixedCapacityVector<filament::MaterialConstant> mConstants;
|
||||
};
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
class MaterialUniformBlockBindingsChunk final : public Chunk {
|
||||
public:
|
||||
explicit MaterialUniformBlockBindingsChunk(
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include <private/filament/SubpassInfo.h>
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
|
||||
namespace filamat {
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<bool>("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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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<ConstantType>(typeString)) {
|
||||
auto type = Enums::toEnum<ConstantType>(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 };
|
||||
|
||||
Reference in New Issue
Block a user