1085 lines
39 KiB
C++
1085 lines
39 KiB
C++
/*
|
|
* Copyright (C) 2017 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 "filamat/MaterialBuilder.h"
|
|
|
|
#include <atomic>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include <utils/JobSystem.h>
|
|
#include <utils/Log.h>
|
|
#include <utils/Mutex.h>
|
|
#include <utils/Panic.h>
|
|
|
|
#include <private/filament/UniformInterfaceBlock.h>
|
|
#include <private/filament/SamplerInterfaceBlock.h>
|
|
|
|
#include <private/filament/SibGenerator.h>
|
|
|
|
#include "MaterialVariants.h"
|
|
|
|
#include "shaders/MaterialInfo.h"
|
|
#include "shaders/ShaderGenerator.h"
|
|
|
|
#include "eiff/BlobDictionary.h"
|
|
#include "eiff/LineDictionary.h"
|
|
#include "eiff/MaterialInterfaceBlockChunk.h"
|
|
#include "eiff/MaterialTextChunk.h"
|
|
#include "eiff/MaterialSpirvChunk.h"
|
|
#include "eiff/ChunkContainer.h"
|
|
#include "eiff/SimpleFieldChunk.h"
|
|
#include "eiff/DictionaryTextChunk.h"
|
|
#include "eiff/DictionarySpirvChunk.h"
|
|
|
|
#include "Includes.h"
|
|
|
|
#ifndef FILAMAT_LITE
|
|
#include "GLSLPostProcessor.h"
|
|
#include "sca/GLSLTools.h"
|
|
#else
|
|
#include "sca/GLSLToolsLite.h"
|
|
#endif
|
|
|
|
using namespace utils;
|
|
|
|
namespace filamat {
|
|
|
|
std::atomic<int> MaterialBuilderBase::materialBuilderClients(0);
|
|
|
|
inline void assertSingleTargetApi(MaterialBuilderBase::TargetApi api) {
|
|
// Assert that a single bit is set.
|
|
UTILS_UNUSED uint8_t bits = (uint8_t) api;
|
|
assert(bits && !(bits & bits - 1u));
|
|
}
|
|
|
|
void MaterialBuilderBase::prepare(bool vulkanSemantics) {
|
|
mCodeGenPermutations.clear();
|
|
mShaderModels.reset();
|
|
|
|
if (mPlatform == Platform::MOBILE) {
|
|
mShaderModels.set(static_cast<size_t>(ShaderModel::GL_ES_30));
|
|
} else if (mPlatform == Platform::DESKTOP) {
|
|
mShaderModels.set(static_cast<size_t>(ShaderModel::GL_CORE_41));
|
|
} else if (mPlatform == Platform::ALL) {
|
|
mShaderModels.set(static_cast<size_t>(ShaderModel::GL_ES_30));
|
|
mShaderModels.set(static_cast<size_t>(ShaderModel::GL_CORE_41));
|
|
}
|
|
|
|
// OpenGL is a special case. If we're doing any optimization, then we need to go to Spir-V.
|
|
TargetLanguage glTargetLanguage = mOptimization > MaterialBuilder::Optimization::PREPROCESSOR ?
|
|
TargetLanguage::SPIRV : TargetLanguage::GLSL;
|
|
if (vulkanSemantics) {
|
|
// Currently GLSLPostProcessor.cpp is incapable of compiling SPIRV to GLSL without
|
|
// running the optimizer. For now we just activate the optimizer in that case.
|
|
mOptimization = MaterialBuilder::Optimization::PERFORMANCE;
|
|
glTargetLanguage = TargetLanguage::SPIRV;
|
|
}
|
|
|
|
// Select OpenGL as the default TargetApi if none was specified.
|
|
if (none(mTargetApi)) {
|
|
mTargetApi = TargetApi::OPENGL;
|
|
}
|
|
|
|
// Build a list of codegen permutations, which is useful across all types of material builders.
|
|
// The shader model loop starts at 1 to skip ShaderModel::UNKNOWN.
|
|
for (uint8_t i = 1; i < filament::backend::SHADER_MODEL_COUNT; i++) {
|
|
if (!mShaderModels.test(i)) {
|
|
continue; // skip this shader model since it was not requested.
|
|
}
|
|
if (any(mTargetApi & TargetApi::OPENGL)) {
|
|
mCodeGenPermutations.push_back({i, TargetApi::OPENGL, glTargetLanguage});
|
|
}
|
|
if (any(mTargetApi & TargetApi::VULKAN)) {
|
|
mCodeGenPermutations.push_back({i, TargetApi::VULKAN, TargetLanguage::SPIRV});
|
|
}
|
|
if (any(mTargetApi & TargetApi::METAL)) {
|
|
mCodeGenPermutations.push_back({i, TargetApi::METAL, TargetLanguage::SPIRV});
|
|
}
|
|
}
|
|
}
|
|
|
|
MaterialBuilder::MaterialBuilder() : mMaterialName("Unnamed") {
|
|
std::fill_n(mProperties, MATERIAL_PROPERTIES_COUNT, false);
|
|
mShaderModels.reset();
|
|
}
|
|
|
|
void MaterialBuilderBase::init() {
|
|
materialBuilderClients++;
|
|
#ifndef FILAMAT_LITE
|
|
GLSLTools::init();
|
|
#endif
|
|
}
|
|
|
|
void MaterialBuilderBase::shutdown() {
|
|
materialBuilderClients--;
|
|
#ifndef FILAMAT_LITE
|
|
GLSLTools::shutdown();
|
|
#endif
|
|
}
|
|
|
|
MaterialBuilder& MaterialBuilder::name(const char* name) noexcept {
|
|
mMaterialName = CString(name);
|
|
return *this;
|
|
}
|
|
|
|
MaterialBuilder& MaterialBuilder::fileName(const char* fileName) noexcept {
|
|
mFileName = CString(fileName);
|
|
return *this;
|
|
}
|
|
|
|
MaterialBuilder& MaterialBuilder::material(const char* code, size_t line) noexcept {
|
|
mMaterialFragmentCode.setUnresolved(CString(code));
|
|
mMaterialFragmentCode.setLineOffset(line);
|
|
return *this;
|
|
}
|
|
|
|
MaterialBuilder& MaterialBuilder::includeCallback(IncludeCallback callback) noexcept {
|
|
mIncludeCallback = std::move(callback);
|
|
return *this;
|
|
}
|
|
|
|
MaterialBuilder& MaterialBuilder::materialVertex(const char* code, size_t line) noexcept {
|
|
mMaterialVertexCode.setUnresolved(CString(code));
|
|
mMaterialVertexCode.setLineOffset(line);
|
|
return *this;
|
|
}
|
|
|
|
MaterialBuilder& MaterialBuilder::shading(Shading shading) noexcept {
|
|
mShading = shading;
|
|
return *this;
|
|
}
|
|
|
|
MaterialBuilder& MaterialBuilder::interpolation(Interpolation interpolation) noexcept {
|
|
mInterpolation = interpolation;
|
|
return *this;
|
|
}
|
|
|
|
MaterialBuilder& MaterialBuilder::variable(Variable v, const char* name) noexcept {
|
|
switch (v) {
|
|
case Variable::CUSTOM0:
|
|
case Variable::CUSTOM1:
|
|
case Variable::CUSTOM2:
|
|
case Variable::CUSTOM3:
|
|
assert(size_t(v) < MATERIAL_VARIABLES_COUNT);
|
|
mVariables[size_t(v)] = CString(name);
|
|
break;
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
MaterialBuilder& MaterialBuilder::parameter(
|
|
UniformType type, ParameterPrecision precision, const char* name) noexcept {
|
|
ASSERT_POSTCONDITION(mParameterCount < MAX_PARAMETERS_COUNT, "Too many parameters");
|
|
mParameters[mParameterCount++] = { name, type, 1, precision };
|
|
return *this;
|
|
}
|
|
|
|
MaterialBuilder& MaterialBuilder::parameter(
|
|
UniformType type, size_t size, ParameterPrecision precision, const char* name) noexcept {
|
|
ASSERT_POSTCONDITION(mParameterCount < MAX_PARAMETERS_COUNT, "Too many parameters");
|
|
mParameters[mParameterCount++] = { name, type, size, precision };
|
|
return *this;
|
|
}
|
|
|
|
MaterialBuilder& MaterialBuilder::parameter(
|
|
SamplerType samplerType, SamplerFormat format, ParameterPrecision precision, const char* name) noexcept {
|
|
ASSERT_POSTCONDITION(mParameterCount < MAX_PARAMETERS_COUNT, "Too many parameters");
|
|
mParameters[mParameterCount++] = { name, samplerType, format, precision };
|
|
return *this;
|
|
}
|
|
|
|
MaterialBuilder& MaterialBuilder::parameter(SubpassType subpassType, SamplerFormat format,
|
|
ParameterPrecision precision, const char* name) noexcept {
|
|
ASSERT_PRECONDITION(format == SamplerFormat::FLOAT,
|
|
"Subpass parameters must have FLOAT format.");
|
|
|
|
auto subpassCount = std::count_if(std::begin(mParameters), std::end(mParameters),
|
|
[](const auto& p) { return p.isSubpass(); });
|
|
|
|
ASSERT_POSTCONDITION(subpassCount < MAX_SUBPASS_COUNT, "Too many subpasses");
|
|
ASSERT_POSTCONDITION(mParameterCount < MAX_PARAMETERS_COUNT, "Too many parameters");
|
|
mParameters[mParameterCount++] = { name, subpassType, format, precision };
|
|
return *this;
|
|
}
|
|
|
|
MaterialBuilder& MaterialBuilder::parameter(
|
|
SamplerType samplerType, SamplerFormat format, const char* name) noexcept {
|
|
return parameter(samplerType, format, ParameterPrecision::DEFAULT, name);
|
|
}
|
|
|
|
MaterialBuilder& MaterialBuilder::parameter(
|
|
SamplerType samplerType, ParameterPrecision precision, const char* name) noexcept {
|
|
return parameter(samplerType, SamplerFormat::FLOAT, precision, name);
|
|
}
|
|
|
|
MaterialBuilder& MaterialBuilder::parameter(
|
|
SamplerType samplerType, const char* name) noexcept {
|
|
return parameter(samplerType, SamplerFormat::FLOAT, ParameterPrecision::DEFAULT, name);
|
|
}
|
|
|
|
MaterialBuilder& MaterialBuilder::parameter(SubpassType subpassType, SamplerFormat format,
|
|
const char* name) noexcept {
|
|
return parameter(subpassType, format, ParameterPrecision::DEFAULT, name);
|
|
}
|
|
|
|
MaterialBuilder& MaterialBuilder::parameter(SubpassType subpassType, ParameterPrecision precision,
|
|
const char* name) noexcept {
|
|
return parameter(subpassType, SamplerFormat::FLOAT, precision, name);
|
|
}
|
|
|
|
MaterialBuilder& MaterialBuilder::parameter(SubpassType subpassType, const char* name) noexcept {
|
|
return parameter(subpassType, SamplerFormat::FLOAT, ParameterPrecision::DEFAULT, name);
|
|
}
|
|
|
|
MaterialBuilder& MaterialBuilder::require(VertexAttribute attribute) noexcept {
|
|
mRequiredAttributes.set(attribute);
|
|
return *this;
|
|
}
|
|
|
|
MaterialBuilder& MaterialBuilder::materialDomain(filament::MaterialDomain materialDomain) noexcept {
|
|
mMaterialDomain = materialDomain;
|
|
return *this;
|
|
}
|
|
|
|
MaterialBuilder& MaterialBuilder::refractionMode(RefractionMode refraction) noexcept {
|
|
mRefractionMode = refraction;
|
|
return *this;
|
|
}
|
|
|
|
MaterialBuilder& MaterialBuilder::refractionType(RefractionType refractionType) noexcept {
|
|
mRefractionType = refractionType;
|
|
return *this;
|
|
}
|
|
|
|
MaterialBuilder& MaterialBuilder::quality(ShaderQuality quality) noexcept {
|
|
mShaderQuality = quality;
|
|
return *this;
|
|
}
|
|
|
|
MaterialBuilder& MaterialBuilder::blending(BlendingMode blending) noexcept {
|
|
mBlendingMode = blending;
|
|
return *this;
|
|
}
|
|
|
|
MaterialBuilder& MaterialBuilder::postLightingBlending(BlendingMode blending) noexcept {
|
|
mPostLightingBlendingMode = blending;
|
|
return *this;
|
|
}
|
|
|
|
MaterialBuilder& MaterialBuilder::vertexDomain(VertexDomain domain) noexcept {
|
|
mVertexDomain = domain;
|
|
return *this;
|
|
}
|
|
|
|
MaterialBuilder& MaterialBuilder::culling(CullingMode culling) noexcept {
|
|
mCullingMode = culling;
|
|
return *this;
|
|
}
|
|
|
|
MaterialBuilder& MaterialBuilder::colorWrite(bool enable) noexcept {
|
|
mColorWrite = enable;
|
|
return *this;
|
|
}
|
|
|
|
MaterialBuilder& MaterialBuilder::depthWrite(bool enable) noexcept {
|
|
mDepthWrite = enable;
|
|
mDepthWriteSet = true;
|
|
return *this;
|
|
}
|
|
|
|
MaterialBuilder& MaterialBuilder::depthCulling(bool enable) noexcept {
|
|
mDepthTest = enable;
|
|
return *this;
|
|
}
|
|
|
|
MaterialBuilder& MaterialBuilder::doubleSided(bool doubleSided) noexcept {
|
|
mDoubleSided = doubleSided;
|
|
mDoubleSidedCapability = true;
|
|
return *this;
|
|
}
|
|
|
|
MaterialBuilder& MaterialBuilder::maskThreshold(float threshold) noexcept {
|
|
mMaskThreshold = threshold;
|
|
return *this;
|
|
}
|
|
|
|
MaterialBuilder& MaterialBuilder::shadowMultiplier(bool shadowMultiplier) noexcept {
|
|
mShadowMultiplier = shadowMultiplier;
|
|
return *this;
|
|
}
|
|
|
|
MaterialBuilder& MaterialBuilder::transparentShadow(bool transparentShadow) noexcept {
|
|
mTransparentShadow = transparentShadow;
|
|
return *this;
|
|
}
|
|
|
|
MaterialBuilder& MaterialBuilder::specularAntiAliasing(bool specularAntiAliasing) noexcept {
|
|
mSpecularAntiAliasing = specularAntiAliasing;
|
|
return *this;
|
|
}
|
|
|
|
MaterialBuilder& MaterialBuilder::specularAntiAliasingVariance(float screenSpaceVariance) noexcept {
|
|
mSpecularAntiAliasingVariance = screenSpaceVariance;
|
|
return *this;
|
|
}
|
|
|
|
MaterialBuilder& MaterialBuilder::specularAntiAliasingThreshold(float threshold) noexcept {
|
|
mSpecularAntiAliasingThreshold = threshold;
|
|
return *this;
|
|
}
|
|
|
|
MaterialBuilder& MaterialBuilder::clearCoatIorChange(bool clearCoatIorChange) noexcept {
|
|
mClearCoatIorChange = clearCoatIorChange;
|
|
return *this;
|
|
}
|
|
|
|
MaterialBuilder& MaterialBuilder::flipUV(bool flipUV) noexcept {
|
|
mFlipUV = flipUV;
|
|
return *this;
|
|
}
|
|
|
|
MaterialBuilder& MaterialBuilder::customSurfaceShading(bool customSurfaceShading) noexcept {
|
|
mCustomSurfaceShading = customSurfaceShading;
|
|
return *this;
|
|
}
|
|
|
|
MaterialBuilder& MaterialBuilder::multiBounceAmbientOcclusion(bool multiBounceAO) noexcept {
|
|
mMultiBounceAO = multiBounceAO;
|
|
mMultiBounceAOSet = true;
|
|
return *this;
|
|
}
|
|
|
|
MaterialBuilder& MaterialBuilder::specularAmbientOcclusion(SpecularAmbientOcclusion specularAO) noexcept {
|
|
mSpecularAO = specularAO;
|
|
mSpecularAOSet = true;
|
|
return *this;
|
|
}
|
|
|
|
MaterialBuilder& MaterialBuilder::transparencyMode(TransparencyMode mode) noexcept {
|
|
mTransparencyMode = mode;
|
|
return *this;
|
|
}
|
|
|
|
MaterialBuilder& MaterialBuilder::reflectionMode(ReflectionMode mode) noexcept {
|
|
mReflectionMode = mode;
|
|
return *this;
|
|
}
|
|
|
|
MaterialBuilder& MaterialBuilder::platform(Platform platform) noexcept {
|
|
mPlatform = platform;
|
|
return *this;
|
|
}
|
|
|
|
MaterialBuilder& MaterialBuilder::targetApi(TargetApi targetApi) noexcept {
|
|
mTargetApi |= targetApi;
|
|
return *this;
|
|
}
|
|
|
|
MaterialBuilder& MaterialBuilder::optimization(Optimization optimization) noexcept {
|
|
mOptimization = optimization;
|
|
return *this;
|
|
}
|
|
|
|
MaterialBuilder& MaterialBuilder::printShaders(bool printShaders) noexcept {
|
|
mPrintShaders = printShaders;
|
|
return *this;
|
|
}
|
|
|
|
MaterialBuilder& MaterialBuilder::generateDebugInfo(bool generateDebugInfo) noexcept {
|
|
mGenerateDebugInfo = generateDebugInfo;
|
|
return *this;
|
|
}
|
|
|
|
MaterialBuilder& MaterialBuilder::variantFilter(filament::UserVariantFilterMask variantFilter) noexcept {
|
|
mVariantFilter = variantFilter;
|
|
return *this;
|
|
}
|
|
|
|
MaterialBuilder& MaterialBuilder::shaderDefine(const char* name, const char* value) noexcept {
|
|
mDefines.emplace_back(name, value);
|
|
return *this;
|
|
}
|
|
|
|
bool MaterialBuilder::hasExternalSampler() const noexcept {
|
|
for (size_t i = 0, c = mParameterCount; i < c; i++) {
|
|
auto const& param = mParameters[i];
|
|
if (param.isSampler() && param.samplerType == SamplerType::SAMPLER_EXTERNAL) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void MaterialBuilder::prepareToBuild(MaterialInfo& info) noexcept {
|
|
MaterialBuilderBase::prepare(mEnableFramebufferFetch);
|
|
|
|
// Build the per-material sampler block and uniform block.
|
|
filament::SamplerInterfaceBlock::Builder sbb;
|
|
filament::UniformInterfaceBlock::Builder ibb;
|
|
for (size_t i = 0, c = mParameterCount; i < c; i++) {
|
|
auto const& param = mParameters[i];
|
|
if (param.isSampler()) {
|
|
sbb.add(param.name, param.samplerType, param.format, param.precision);
|
|
} else if (param.isUniform()) {
|
|
ibb.add(param.name, param.size, param.uniformType, param.precision);
|
|
} else if (param.isSubpass()) {
|
|
// For now, we only support a single subpass for attachment 0.
|
|
// Subpasses belong to the "MaterialParams" block.
|
|
const uint8_t attachmentIndex = 0;
|
|
const uint8_t binding = 0;
|
|
info.subpass = { utils::CString("MaterialParams"), param.name, param.subpassType,
|
|
param.format, param.precision, attachmentIndex, binding };
|
|
}
|
|
}
|
|
|
|
if (mSpecularAntiAliasing) {
|
|
ibb.add("_specularAntiAliasingVariance", 1, UniformType::FLOAT);
|
|
ibb.add("_specularAntiAliasingThreshold", 1, UniformType::FLOAT);
|
|
}
|
|
|
|
if (mBlendingMode == BlendingMode::MASKED) {
|
|
ibb.add("_maskThreshold", 1, UniformType::FLOAT);
|
|
}
|
|
|
|
if (mDoubleSidedCapability) {
|
|
ibb.add("_doubleSided", 1, UniformType::BOOL);
|
|
}
|
|
|
|
mRequiredAttributes.set(filament::VertexAttribute::POSITION);
|
|
if (mShading != filament::Shading::UNLIT || mShadowMultiplier) {
|
|
mRequiredAttributes.set(filament::VertexAttribute::TANGENTS);
|
|
}
|
|
|
|
info.sib = sbb.name("MaterialParams").build();
|
|
info.uib = ibb.name("MaterialParams").build();
|
|
|
|
info.isLit = isLit();
|
|
info.hasDoubleSidedCapability = mDoubleSidedCapability;
|
|
info.hasExternalSamplers = hasExternalSampler();
|
|
info.specularAntiAliasing = mSpecularAntiAliasing;
|
|
info.clearCoatIorChange = mClearCoatIorChange;
|
|
info.flipUV = mFlipUV;
|
|
info.requiredAttributes = mRequiredAttributes;
|
|
info.blendingMode = mBlendingMode;
|
|
info.postLightingBlendingMode = mPostLightingBlendingMode;
|
|
info.shading = mShading;
|
|
info.hasShadowMultiplier = mShadowMultiplier;
|
|
info.hasTransparentShadow = mTransparentShadow;
|
|
info.multiBounceAO = mMultiBounceAO;
|
|
info.multiBounceAOSet = mMultiBounceAOSet;
|
|
info.specularAO = mSpecularAO;
|
|
info.specularAOSet = mSpecularAOSet;
|
|
info.refractionMode = mRefractionMode;
|
|
info.refractionType = mRefractionType;
|
|
info.reflectionMode = mReflectionMode;
|
|
info.quality = mShaderQuality;
|
|
info.hasCustomSurfaceShading = mCustomSurfaceShading;
|
|
info.useLegacyMorphing = mUseLegacyMorphing;
|
|
}
|
|
|
|
bool MaterialBuilder::findProperties(filament::backend::ShaderType type,
|
|
MaterialBuilder::PropertyList& allProperties) noexcept {
|
|
#ifndef FILAMAT_LITE
|
|
GLSLTools glslTools;
|
|
std::string shaderCodeAllProperties = peek(type, mSemanticCodeGenParams, allProperties);
|
|
// Populate mProperties with the properties set in the shader.
|
|
if (!glslTools.findProperties(type, shaderCodeAllProperties, mProperties,
|
|
mSemanticCodeGenParams.targetApi,
|
|
ShaderModel(mSemanticCodeGenParams.shaderModel))) {
|
|
return false;
|
|
}
|
|
return true;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
bool MaterialBuilder::findAllProperties() noexcept {
|
|
if (mMaterialDomain != MaterialDomain::SURFACE) {
|
|
return true;
|
|
}
|
|
|
|
using namespace filament::backend;
|
|
|
|
#ifndef FILAMAT_LITE
|
|
// Some fields in MaterialInputs only exist if the property is set (e.g: normal, subsurface
|
|
// for cloth shading model). Give our shader all properties. This will enable us to parse and
|
|
// static code analyse the AST.
|
|
MaterialBuilder::PropertyList allProperties;
|
|
std::fill_n(allProperties, MATERIAL_PROPERTIES_COUNT, true);
|
|
|
|
findProperties(ShaderType::FRAGMENT, allProperties);
|
|
findProperties(ShaderType::VERTEX, allProperties);
|
|
|
|
return true;
|
|
#else
|
|
GLSLToolsLite glslTools;
|
|
if (glslTools.findProperties(ShaderType::FRAGMENT, mMaterialFragmentCode.getResolved(), mProperties)) {
|
|
return glslTools.findProperties(
|
|
ShaderType::VERTEX, mMaterialVertexCode.getResolved(), mProperties);
|
|
}
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
bool MaterialBuilder::runSemanticAnalysis() noexcept {
|
|
#ifndef FILAMAT_LITE
|
|
using namespace filament::backend;
|
|
GLSLTools glslTools;
|
|
|
|
TargetApi targetApi = mSemanticCodeGenParams.targetApi;
|
|
assertSingleTargetApi(targetApi);
|
|
|
|
if (mEnableFramebufferFetch) {
|
|
// framebuffer fetch is only available with vulkan semantics
|
|
targetApi = TargetApi::VULKAN;
|
|
}
|
|
|
|
ShaderModel model = static_cast<ShaderModel>(mSemanticCodeGenParams.shaderModel);
|
|
std::string shaderCode = peek(ShaderType::VERTEX, mSemanticCodeGenParams, mProperties);
|
|
bool result = glslTools.analyzeVertexShader(shaderCode, model, mMaterialDomain, targetApi);
|
|
if (!result) return false;
|
|
|
|
shaderCode = peek(ShaderType::FRAGMENT, mSemanticCodeGenParams, mProperties);
|
|
result = glslTools.analyzeFragmentShader(shaderCode, model, mMaterialDomain, targetApi,
|
|
mCustomSurfaceShading);
|
|
return result;
|
|
#else
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
bool MaterialBuilder::checkLiteRequirements() noexcept {
|
|
#ifdef FILAMAT_LITE
|
|
if (mTargetApi != TargetApi::OPENGL) {
|
|
utils::slog.e
|
|
<< "Filamat lite only supports building materials for the OpenGL backend."
|
|
<< utils::io::endl;
|
|
return false;
|
|
}
|
|
|
|
if (mOptimization != Optimization::NONE) {
|
|
utils::slog.e
|
|
<< "Filamat lite does not support material optimization." << utils::io::endl
|
|
<< "Ensure optimization is set to NONE." << utils::io::endl;
|
|
return false;
|
|
}
|
|
#endif
|
|
return true;
|
|
}
|
|
|
|
bool MaterialBuilder::ShaderCode::resolveIncludes(IncludeCallback callback,
|
|
const utils::CString& fileName) noexcept {
|
|
if (!mCode.empty()) {
|
|
ResolveOptions options {
|
|
.insertLineDirectives = true,
|
|
.insertLineDirectiveCheck = true
|
|
};
|
|
IncludeResult source {
|
|
.includeName = fileName,
|
|
.text = mCode,
|
|
.lineNumberOffset = getLineOffset(),
|
|
.name = utils::CString("")
|
|
};
|
|
if (!::filamat::resolveIncludes(source, std::move(callback), options)) {
|
|
return false;
|
|
}
|
|
mCode = source.text;
|
|
}
|
|
|
|
mIncludesResolved = true;
|
|
return true;
|
|
}
|
|
|
|
static void showErrorMessage(const char* materialName, filament::Variant variant,
|
|
MaterialBuilder::TargetApi targetApi, filament::backend::ShaderType shaderType,
|
|
const std::string& shaderCode) {
|
|
using ShaderType = filament::backend::ShaderType;
|
|
using TargetApi = MaterialBuilder::TargetApi;
|
|
utils::slog.e
|
|
<< "Error in \"" << materialName << "\""
|
|
<< ", Variant 0x" << io::hex << +variant.key
|
|
<< (targetApi == TargetApi::VULKAN ? ", Vulkan.\n" : ", OpenGL.\n")
|
|
<< "=========================\n"
|
|
<< "Generated "
|
|
<< (shaderType == ShaderType::VERTEX ? "Vertex Shader\n" : "Fragment Shader\n")
|
|
<< "=========================\n"
|
|
<< shaderCode;
|
|
}
|
|
|
|
bool MaterialBuilder::generateShaders(JobSystem& jobSystem, const std::vector<Variant>& variants,
|
|
ChunkContainer& container, const MaterialInfo& info) const noexcept {
|
|
// Create a postprocessor to optimize / compile to Spir-V if necessary.
|
|
#ifndef FILAMAT_LITE
|
|
uint32_t flags = 0;
|
|
flags |= mPrintShaders ? GLSLPostProcessor::PRINT_SHADERS : 0;
|
|
flags |= mGenerateDebugInfo ? GLSLPostProcessor::GENERATE_DEBUG_INFO : 0;
|
|
GLSLPostProcessor postProcessor(mOptimization, flags);
|
|
#endif
|
|
|
|
// Start: must be protected by lock
|
|
Mutex entriesLock;
|
|
std::vector<TextEntry> glslEntries;
|
|
std::vector<SpirvEntry> spirvEntries;
|
|
std::vector<TextEntry> metalEntries;
|
|
LineDictionary textDictionary;
|
|
#ifndef FILAMAT_LITE
|
|
BlobDictionary spirvDictionary;
|
|
#endif
|
|
// End: must be protected by lock
|
|
|
|
ShaderGenerator sg(
|
|
mProperties, mVariables, mOutputs, mDefines, mMaterialFragmentCode.getResolved(),
|
|
mMaterialFragmentCode.getLineOffset(), mMaterialVertexCode.getResolved(),
|
|
mMaterialVertexCode.getLineOffset(), mMaterialDomain);
|
|
|
|
container.addSimpleChild<bool>(ChunkType::MaterialHasCustomDepthShader, needsStandardDepthProgram());
|
|
|
|
std::atomic_bool cancelJobs(false);
|
|
bool firstJob = true;
|
|
|
|
for (const auto& params : mCodeGenPermutations) {
|
|
if (cancelJobs.load()) {
|
|
return false;
|
|
}
|
|
|
|
const ShaderModel shaderModel = ShaderModel(params.shaderModel);
|
|
const TargetApi targetApi = params.targetApi;
|
|
const TargetLanguage targetLanguage = params.targetLanguage;
|
|
|
|
assertSingleTargetApi(targetApi);
|
|
|
|
// Metal Shading Language is cross-compiled from Vulkan.
|
|
const bool targetApiNeedsSpirv =
|
|
(targetApi == TargetApi::VULKAN || targetApi == TargetApi::METAL);
|
|
const bool targetApiNeedsMsl = targetApi == TargetApi::METAL;
|
|
const bool targetApiNeedsGlsl = targetApi == TargetApi::OPENGL;
|
|
|
|
// Set when a job fails
|
|
JobSystem::Job* parent = jobSystem.createJob();
|
|
|
|
for (const auto& v : variants) {
|
|
JobSystem::Job* job = jobs::createJob(jobSystem, parent, [&]() {
|
|
if (cancelJobs.load()) {
|
|
return;
|
|
}
|
|
|
|
// TODO: avoid allocations when not required
|
|
std::vector<uint32_t> spirv;
|
|
std::string msl;
|
|
|
|
std::vector<uint32_t>* pSpirv = targetApiNeedsSpirv ? &spirv : nullptr;
|
|
std::string* pMsl = targetApiNeedsMsl ? &msl : nullptr;
|
|
|
|
TextEntry glslEntry{0};
|
|
SpirvEntry spirvEntry{0};
|
|
TextEntry metalEntry{0};
|
|
|
|
glslEntry.shaderModel = static_cast<uint8_t>(params.shaderModel);
|
|
spirvEntry.shaderModel = static_cast<uint8_t>(params.shaderModel);
|
|
metalEntry.shaderModel = static_cast<uint8_t>(params.shaderModel);
|
|
|
|
glslEntry.variantKey = v.variant.key;
|
|
spirvEntry.variantKey = v.variant.key;
|
|
metalEntry.variantKey = v.variant.key;
|
|
|
|
// Generate raw shader code.
|
|
// The quotes in Google-style line directives cause problems with certain drivers. These
|
|
// directives are optimized away when using the full filamat, so down below we
|
|
// explicitly remove them when using filamat lite.
|
|
std::string shader;
|
|
if (v.stage == filament::backend::ShaderType::VERTEX) {
|
|
shader = sg.createVertexProgram(
|
|
shaderModel, targetApi, targetLanguage, info, v.variant,
|
|
mInterpolation, mVertexDomain);
|
|
#ifdef FILAMAT_LITE
|
|
GLSLToolsLite glslTools;
|
|
glslTools.removeGoogleLineDirectives(shader);
|
|
#endif
|
|
} else if (v.stage == filament::backend::ShaderType::FRAGMENT) {
|
|
shader = sg.createFragmentProgram(
|
|
shaderModel, targetApi, targetLanguage, info, v.variant, mInterpolation);
|
|
#ifdef FILAMAT_LITE
|
|
GLSLToolsLite glslTools;
|
|
glslTools.removeGoogleLineDirectives(shader);
|
|
#endif
|
|
}
|
|
|
|
std::string* pGlsl = nullptr;
|
|
if (targetApiNeedsGlsl) {
|
|
pGlsl = &shader;
|
|
}
|
|
|
|
#ifndef FILAMAT_LITE
|
|
GLSLPostProcessor::Config config{
|
|
.variant = v.variant,
|
|
.targetApi = targetApi,
|
|
.shaderType = v.stage,
|
|
.shaderModel = shaderModel,
|
|
.domain = mMaterialDomain,
|
|
.materialInfo = &info,
|
|
.glsl = {},
|
|
};
|
|
|
|
config.hasFramebufferFetch = mEnableFramebufferFetch;
|
|
|
|
if (mEnableFramebufferFetch) {
|
|
config.glsl.subpassInputToColorLocation.emplace_back(0, 0);
|
|
}
|
|
|
|
bool ok = postProcessor.process(shader, config, pGlsl, pSpirv, pMsl);
|
|
#else
|
|
bool ok = true;
|
|
#endif
|
|
if (!ok) {
|
|
showErrorMessage(mMaterialName.c_str_safe(), v.variant, targetApi, v.stage, shader);
|
|
cancelJobs = true;
|
|
return;
|
|
}
|
|
|
|
if (targetApi == TargetApi::OPENGL) {
|
|
if (targetLanguage == TargetLanguage::SPIRV) {
|
|
ShaderGenerator::fixupExternalSamplers(shaderModel, shader, info);
|
|
}
|
|
}
|
|
|
|
// NOTE: Everything below touches shared structures protected by a lock
|
|
// NOTE: do not execute expensive work from here on!
|
|
std::unique_lock<utils::Mutex> lock(entriesLock);
|
|
|
|
if (targetApi == TargetApi::OPENGL) {
|
|
glslEntry.stage = v.stage;
|
|
glslEntry.shader = shader;
|
|
|
|
glslEntries.push_back(glslEntry);
|
|
}
|
|
|
|
#ifndef FILAMAT_LITE
|
|
if (targetApi == TargetApi::VULKAN) {
|
|
assert(!spirv.empty());
|
|
spirvEntry.stage = v.stage;
|
|
spirvEntry.spirv = std::move(spirv);
|
|
|
|
spirvEntries.push_back(spirvEntry);
|
|
}
|
|
|
|
if (targetApi == TargetApi::METAL) {
|
|
assert(!spirv.empty());
|
|
assert(msl.length() > 0);
|
|
metalEntry.stage = v.stage;
|
|
metalEntry.shader = msl;
|
|
|
|
metalEntries.push_back(metalEntry);
|
|
}
|
|
#endif
|
|
});
|
|
|
|
// NOTE: We run the first job separately to work the lack of thread safety
|
|
// guarantees in glslang. This library performs unguarded global
|
|
// operations on first use.
|
|
if (firstJob) {
|
|
jobSystem.runAndWait(job);
|
|
firstJob = false;
|
|
} else {
|
|
jobSystem.run(job);
|
|
}
|
|
}
|
|
|
|
jobSystem.runAndWait(parent);
|
|
}
|
|
|
|
if (cancelJobs.load()) {
|
|
return false;
|
|
}
|
|
|
|
// Sort the variants.
|
|
auto compare = [](const auto& a, const auto& b) {
|
|
static_assert(sizeof(decltype(a.variantKey)) == 1);
|
|
static_assert(sizeof(decltype(b.variantKey)) == 1);
|
|
const uint32_t akey = (a.shaderModel << 16) | (a.variantKey << 8) | a.stage;
|
|
const uint32_t bkey = (b.shaderModel << 16) | (b.variantKey << 8) | b.stage;
|
|
return akey < bkey;
|
|
};
|
|
std::sort(glslEntries.begin(), glslEntries.end(), compare);
|
|
std::sort(spirvEntries.begin(), spirvEntries.end(), compare);
|
|
std::sort(metalEntries.begin(), metalEntries.end(), compare);
|
|
|
|
// Generate the dictionaries.
|
|
for (const auto& s : glslEntries) {
|
|
textDictionary.addText(s.shader);
|
|
}
|
|
#ifndef FILAMAT_LITE
|
|
for (auto& s : spirvEntries) {
|
|
std::vector<uint32_t> spirv = std::move(s.spirv);
|
|
s.dictionaryIndex = spirvDictionary.addBlob(spirv);
|
|
}
|
|
#endif
|
|
for (const auto& s : metalEntries) {
|
|
textDictionary.addText(s.shader);
|
|
}
|
|
|
|
// Emit dictionary chunk (TextDictionaryReader and DictionaryTextChunk)
|
|
const auto& dictionaryChunk = container.addChild<filamat::DictionaryTextChunk>(
|
|
std::move(textDictionary), ChunkType::DictionaryText);
|
|
|
|
// Emit GLSL chunk (MaterialTextChunk).
|
|
if (!glslEntries.empty()) {
|
|
container.addChild<MaterialTextChunk>(std::move(glslEntries),
|
|
dictionaryChunk.getDictionary(), ChunkType::MaterialGlsl);
|
|
}
|
|
|
|
// Emit SPIRV chunks (SpirvDictionaryReader and MaterialSpirvChunk).
|
|
#ifndef FILAMAT_LITE
|
|
if (!spirvEntries.empty()) {
|
|
const bool stripInfo = !mGenerateDebugInfo;
|
|
container.addChild<filamat::DictionarySpirvChunk>(std::move(spirvDictionary), stripInfo);
|
|
container.addChild<MaterialSpirvChunk>(std::move(spirvEntries));
|
|
}
|
|
|
|
// Emit Metal chunk (MaterialTextChunk).
|
|
if (!metalEntries.empty()) {
|
|
container.addChild<MaterialTextChunk>(std::move(metalEntries),
|
|
dictionaryChunk.getDictionary(), ChunkType::MaterialMetal);
|
|
}
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
MaterialBuilder& MaterialBuilder::output(VariableQualifier qualifier, OutputTarget target,
|
|
OutputType type, const char* name, int location) noexcept {
|
|
|
|
ASSERT_PRECONDITION(target != OutputTarget::DEPTH || type == OutputType::FLOAT,
|
|
"Depth outputs must be of type FLOAT.");
|
|
ASSERT_PRECONDITION(target != OutputTarget::DEPTH || qualifier == VariableQualifier::OUT,
|
|
"Depth outputs must use OUT qualifier.");
|
|
|
|
ASSERT_PRECONDITION(location >= -1,
|
|
"Output location must be >= 0 (or use -1 for default location).");
|
|
|
|
// A location value of -1 signals using the default location. We'll simply take the previous
|
|
// output's location and add 1.
|
|
if (location == -1) {
|
|
location = mOutputs.empty() ? 0 : mOutputs.back().location + 1;
|
|
}
|
|
|
|
// Unconditionally add this output, then we'll check if we've maxed on on any particular target.
|
|
mOutputs.emplace_back(name, qualifier, target, type, location);
|
|
|
|
uint8_t colorOutputCount = 0;
|
|
uint8_t depthOutputCount = 0;
|
|
for (const auto& output : mOutputs) {
|
|
if (output.target == OutputTarget::COLOR) {
|
|
colorOutputCount++;
|
|
}
|
|
if (output.target == OutputTarget::DEPTH) {
|
|
depthOutputCount++;
|
|
}
|
|
}
|
|
|
|
ASSERT_PRECONDITION(colorOutputCount <= MAX_COLOR_OUTPUT,
|
|
"A maximum of %d COLOR outputs is allowed.", MAX_COLOR_OUTPUT);
|
|
ASSERT_PRECONDITION(depthOutputCount <= MAX_DEPTH_OUTPUT,
|
|
"A maximum of %d DEPTH output is allowed.", MAX_DEPTH_OUTPUT);
|
|
|
|
assert(mOutputs.size() <= MAX_COLOR_OUTPUT + MAX_DEPTH_OUTPUT);
|
|
|
|
return *this;
|
|
}
|
|
|
|
MaterialBuilder& MaterialBuilder::enableFramebufferFetch() noexcept {
|
|
// This API is temporary, it is used to enable EXT_framebuffer_fetch for GLSL shaders,
|
|
// this is used sparingly by filament's post-processing stage.
|
|
mEnableFramebufferFetch = true;
|
|
return *this;
|
|
}
|
|
|
|
MaterialBuilder& MaterialBuilder::useLegacyMorphing() noexcept {
|
|
mUseLegacyMorphing = true;
|
|
return *this;
|
|
}
|
|
|
|
Package MaterialBuilder::build(JobSystem& jobSystem) noexcept {
|
|
if (materialBuilderClients == 0) {
|
|
utils::slog.e << "Error: MaterialBuilder::init() must be called before build()."
|
|
<< utils::io::endl;
|
|
// Return an empty package to signal a failure to build the material.
|
|
return Package::invalidPackage();
|
|
}
|
|
|
|
// Add a default color output.
|
|
if (mMaterialDomain == MaterialDomain::POST_PROCESS && mOutputs.empty()) {
|
|
output(VariableQualifier::OUT, OutputTarget::COLOR, OutputType::FLOAT4, "color");
|
|
}
|
|
|
|
// Resolve all the #include directives within user code.
|
|
if (!mMaterialFragmentCode.resolveIncludes(mIncludeCallback, mFileName) ||
|
|
!mMaterialVertexCode.resolveIncludes(mIncludeCallback, mFileName)) {
|
|
return Package::invalidPackage();
|
|
}
|
|
|
|
if (mCustomSurfaceShading && mShading != Shading::LIT) {
|
|
utils::slog.e << "Error: customSurfaceShading can only be used with lit materials."
|
|
<< utils::io::endl;
|
|
return Package::invalidPackage();
|
|
}
|
|
|
|
// prepareToBuild must be called first, to populate mCodeGenPermutations.
|
|
MaterialInfo info {};
|
|
prepareToBuild(info);
|
|
|
|
// Run checks, in order.
|
|
// The call to findProperties populates mProperties and must come before runSemanticAnalysis.
|
|
if (!checkLiteRequirements() || !findAllProperties() || !runSemanticAnalysis()) {
|
|
// Return an empty package to signal a failure to build the material.
|
|
return Package::invalidPackage();
|
|
}
|
|
|
|
filament::SamplerBindingMap map;
|
|
map.populate(&info.sib, mMaterialName.c_str());
|
|
info.samplerBindings = std::move(map);
|
|
|
|
// Create chunk tree.
|
|
ChunkContainer container;
|
|
writeCommonChunks(container, info);
|
|
if (mMaterialDomain == MaterialDomain::SURFACE) {
|
|
writeSurfaceChunks(container);
|
|
}
|
|
|
|
info.useLegacyMorphing = mUseLegacyMorphing;
|
|
|
|
// Generate all shaders and write the shader chunks.
|
|
const auto variants = mMaterialDomain == MaterialDomain::SURFACE ?
|
|
determineSurfaceVariants(mVariantFilter, isLit(), mShadowMultiplier) :
|
|
determinePostProcessVariants();
|
|
bool success = generateShaders(jobSystem, variants, container, info);
|
|
|
|
if (!success) {
|
|
// Return an empty package to signal a failure to build the material.
|
|
return Package::invalidPackage();
|
|
}
|
|
|
|
// Flatten all chunks in the container into a Package.
|
|
Package package(container.getSize());
|
|
Flattener f(package);
|
|
container.flatten(f);
|
|
|
|
return package;
|
|
}
|
|
|
|
bool MaterialBuilder::hasCustomVaryings() const noexcept {
|
|
for (const auto& variable : mVariables) {
|
|
if (!variable.empty()) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool MaterialBuilder::needsStandardDepthProgram() const noexcept {
|
|
const bool hasEmptyVertexCode = mMaterialVertexCode.getResolved().empty();
|
|
return !hasEmptyVertexCode ||
|
|
hasCustomVaryings() ||
|
|
mBlendingMode == BlendingMode::MASKED ||
|
|
(mTransparentShadow &&
|
|
(mBlendingMode == BlendingMode::TRANSPARENT ||
|
|
mBlendingMode == BlendingMode::FADE));
|
|
}
|
|
|
|
std::string MaterialBuilder::peek(filament::backend::ShaderType type,
|
|
const CodeGenParams& params, const PropertyList& properties) noexcept {
|
|
|
|
ShaderGenerator sg(properties, mVariables, mOutputs, mDefines, mMaterialFragmentCode.getResolved(),
|
|
mMaterialFragmentCode.getLineOffset(), mMaterialVertexCode.getResolved(),
|
|
mMaterialVertexCode.getLineOffset(), mMaterialDomain);
|
|
|
|
MaterialInfo info;
|
|
prepareToBuild(info);
|
|
|
|
filament::SamplerBindingMap map;
|
|
map.populate(&info.sib, mMaterialName.c_str());
|
|
info.samplerBindings = std::move(map);
|
|
|
|
if (type == filament::backend::ShaderType::VERTEX) {
|
|
return sg.createVertexProgram(ShaderModel(params.shaderModel),
|
|
params.targetApi, params.targetLanguage, info, {}, mInterpolation, mVertexDomain);
|
|
} else {
|
|
return sg.createFragmentProgram(ShaderModel(params.shaderModel), params.targetApi,
|
|
params.targetLanguage, info, {}, mInterpolation);
|
|
}
|
|
}
|
|
|
|
void MaterialBuilder::writeCommonChunks(ChunkContainer& container, MaterialInfo& info) const noexcept {
|
|
container.addSimpleChild<uint32_t>(ChunkType::MaterialVersion, filament::MATERIAL_VERSION);
|
|
container.addSimpleChild<const char*>(ChunkType::MaterialName, mMaterialName.c_str_safe());
|
|
container.addSimpleChild<uint32_t>(ChunkType::MaterialShaderModels, mShaderModels.getValue());
|
|
container.addSimpleChild<uint8_t>(ChunkType::MaterialDomain, static_cast<uint8_t>(mMaterialDomain));
|
|
container.addSimpleChild<uint8_t>(ChunkType::MaterialRefraction, static_cast<uint8_t>(mRefractionMode));
|
|
container.addSimpleChild<uint8_t>(ChunkType::MaterialRefractionType, static_cast<uint8_t>(mRefractionType));
|
|
|
|
// UIB
|
|
container.addChild<MaterialUniformInterfaceBlockChunk>(info.uib);
|
|
|
|
// SIB
|
|
container.addChild<MaterialSamplerInterfaceBlockChunk>(info.sib);
|
|
|
|
// Subpass
|
|
container.addChild<MaterialSubpassInterfaceBlockChunk>(info.subpass);
|
|
|
|
container.addSimpleChild<bool>(ChunkType::MaterialDoubleSidedSet, mDoubleSidedCapability);
|
|
container.addSimpleChild<bool>(ChunkType::MaterialDoubleSided, mDoubleSided);
|
|
|
|
container.addSimpleChild<uint8_t>(ChunkType::MaterialBlendingMode, static_cast<uint8_t>(mBlendingMode));
|
|
container.addSimpleChild<uint8_t>(ChunkType::MaterialTransparencyMode, static_cast<uint8_t>(mTransparencyMode));
|
|
container.addSimpleChild<uint8_t>(ChunkType::MaterialReflectionMode, static_cast<uint8_t>(mReflectionMode));
|
|
container.addSimpleChild<bool>(ChunkType::MaterialDepthWriteSet, mDepthWriteSet);
|
|
container.addSimpleChild<bool>(ChunkType::MaterialColorWrite, mColorWrite);
|
|
container.addSimpleChild<bool>(ChunkType::MaterialDepthWrite, mDepthWrite);
|
|
container.addSimpleChild<bool>(ChunkType::MaterialDepthTest, mDepthTest);
|
|
container.addSimpleChild<uint8_t>(ChunkType::MaterialCullingMode, static_cast<uint8_t>(mCullingMode));
|
|
|
|
uint64_t properties = 0;
|
|
for (size_t i = 0; i < MATERIAL_PROPERTIES_COUNT; i++) {
|
|
if (mProperties[i]) {
|
|
properties |= uint64_t(1u) << i;
|
|
}
|
|
}
|
|
container.addSimpleChild<uint64_t>(ChunkType::MaterialProperties, properties);
|
|
}
|
|
|
|
void MaterialBuilder::writeSurfaceChunks(ChunkContainer& container) const noexcept {
|
|
if (mBlendingMode == BlendingMode::MASKED) {
|
|
container.addSimpleChild<float>(ChunkType::MaterialMaskThreshold, mMaskThreshold);
|
|
}
|
|
|
|
container.addSimpleChild<uint8_t>(ChunkType::MaterialShading, static_cast<uint8_t>(mShading));
|
|
|
|
if (mShading == Shading::UNLIT) {
|
|
container.addSimpleChild<bool>(ChunkType::MaterialShadowMultiplier, mShadowMultiplier);
|
|
}
|
|
|
|
container.addSimpleChild<bool>(ChunkType::MaterialClearCoatIorChange, mClearCoatIorChange);
|
|
container.addSimpleChild<uint32_t>(ChunkType::MaterialRequiredAttributes, mRequiredAttributes.getValue());
|
|
container.addSimpleChild<bool>(ChunkType::MaterialSpecularAntiAliasing, mSpecularAntiAliasing);
|
|
container.addSimpleChild<float>(ChunkType::MaterialSpecularAntiAliasingVariance, mSpecularAntiAliasingVariance);
|
|
container.addSimpleChild<float>(ChunkType::MaterialSpecularAntiAliasingThreshold, mSpecularAntiAliasingThreshold);
|
|
container.addSimpleChild<uint8_t>(ChunkType::MaterialVertexDomain, static_cast<uint8_t>(mVertexDomain));
|
|
container.addSimpleChild<uint8_t>(ChunkType::MaterialInterpolation, static_cast<uint8_t>(mInterpolation));
|
|
}
|
|
|
|
} // namespace filamat
|