Expose specialization constants to materials as constant parameters (#6652)

This commit is contained in:
Ben Doherty
2023-04-11 11:09:53 -07:00
committed by GitHub
parent ca2f3d76e6
commit 6cab8d2cd4
25 changed files with 597 additions and 14 deletions
+1
View File
@@ -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)
+57
View File
@@ -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,
+28
View File
@@ -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.
*
+40
View File
@@ -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
+7
View File
@@ -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
+58 -8
View File
@@ -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;
}
+2
View File
@@ -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:
+2
View File
@@ -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;
+11
View File
@@ -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
+59 -5
View File
@@ -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(
+1 -1
View File
@@ -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);
+1
View File
@@ -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;
+49
View File
@@ -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();
+10
View File
@@ -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;
+39
View File
@@ -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 };