1560 lines
60 KiB
C++
1560 lines
60 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 <filamat/Enums.h>
|
|
|
|
#include "Includes.h"
|
|
#include "MaterialVariants.h"
|
|
#include "shaders/SibGenerator.h"
|
|
#include "shaders/UibGenerator.h"
|
|
|
|
#include "GLSLPostProcessor.h"
|
|
#include "sca/GLSLTools.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 <private/filament/BufferInterfaceBlock.h>
|
|
#include <private/filament/SamplerInterfaceBlock.h>
|
|
#include <private/filament/UibStructs.h>
|
|
#include <private/filament/ConstantInfo.h>
|
|
|
|
#include <backend/Program.h>
|
|
|
|
#include <utils/JobSystem.h>
|
|
#include <utils/Log.h>
|
|
#include <utils/Mutex.h>
|
|
#include <utils/Panic.h>
|
|
#include <utils/Hash.h>
|
|
|
|
#include <atomic>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
|
|
namespace filamat {
|
|
|
|
using namespace utils;
|
|
using namespace filament;
|
|
|
|
// Note: the VertexAttribute enum value must match the index in the array
|
|
const MaterialBuilder::AttributeDatabase MaterialBuilder::sAttributeDatabase = {{
|
|
{ "position", AttributeType::FLOAT4, VertexAttribute::POSITION },
|
|
{ "tangents", AttributeType::FLOAT4, VertexAttribute::TANGENTS },
|
|
{ "color", AttributeType::FLOAT4, VertexAttribute::COLOR },
|
|
{ "uv0", AttributeType::FLOAT2, VertexAttribute::UV0 },
|
|
{ "uv1", AttributeType::FLOAT2, VertexAttribute::UV1 },
|
|
{ "bone_indices", AttributeType::UINT4, VertexAttribute::BONE_INDICES },
|
|
{ "bone_weights", AttributeType::FLOAT4, VertexAttribute::BONE_WEIGHTS },
|
|
{ },
|
|
{ "custom0", AttributeType::FLOAT4, VertexAttribute::CUSTOM0 },
|
|
{ "custom1", AttributeType::FLOAT4, VertexAttribute::CUSTOM1 },
|
|
{ "custom2", AttributeType::FLOAT4, VertexAttribute::CUSTOM2 },
|
|
{ "custom3", AttributeType::FLOAT4, VertexAttribute::CUSTOM3 },
|
|
{ "custom4", AttributeType::FLOAT4, VertexAttribute::CUSTOM4 },
|
|
{ "custom5", AttributeType::FLOAT4, VertexAttribute::CUSTOM5 },
|
|
{ "custom6", AttributeType::FLOAT4, VertexAttribute::CUSTOM6 },
|
|
{ "custom7", AttributeType::FLOAT4, VertexAttribute::CUSTOM7 },
|
|
}};
|
|
|
|
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,
|
|
filament::backend::FeatureLevel featureLevel) {
|
|
mCodeGenPermutations.clear();
|
|
mShaderModels.reset();
|
|
|
|
if (mPlatform == Platform::MOBILE) {
|
|
mShaderModels.set(static_cast<size_t>(ShaderModel::MOBILE));
|
|
} else if (mPlatform == Platform::DESKTOP) {
|
|
mShaderModels.set(static_cast<size_t>(ShaderModel::DESKTOP));
|
|
} else if (mPlatform == Platform::ALL) {
|
|
mShaderModels.set(static_cast<size_t>(ShaderModel::MOBILE));
|
|
mShaderModels.set(static_cast<size_t>(ShaderModel::DESKTOP));
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
// Generally build for a minimum of feature level 1. If feature level 0 is specified, an extra
|
|
// permutation is specifically included for the OpenGL/mobile target.
|
|
MaterialBuilder::FeatureLevel effectiveFeatureLevel =
|
|
std::max(featureLevel, filament::backend::FeatureLevel::FEATURE_LEVEL_1);
|
|
|
|
// Build a list of codegen permutations, which is useful across all types of material builders.
|
|
static_assert(backend::SHADER_MODEL_COUNT == 2);
|
|
for (const auto shaderModel: { ShaderModel::MOBILE, ShaderModel::DESKTOP }) {
|
|
const auto i = static_cast<uint8_t>(shaderModel);
|
|
if (!mShaderModels.test(i)) {
|
|
continue; // skip this shader model since it was not requested.
|
|
}
|
|
|
|
if (any(mTargetApi & TargetApi::OPENGL)) {
|
|
mCodeGenPermutations.push_back({
|
|
shaderModel,
|
|
TargetApi::OPENGL,
|
|
glTargetLanguage,
|
|
effectiveFeatureLevel,
|
|
});
|
|
if (featureLevel == filament::backend::FeatureLevel::FEATURE_LEVEL_0
|
|
&& shaderModel == ShaderModel::MOBILE) {
|
|
// ESSL1 code may never be compiled to SPIR-V.
|
|
mCodeGenPermutations.push_back({
|
|
shaderModel,
|
|
TargetApi::OPENGL,
|
|
TargetLanguage::GLSL,
|
|
filament::backend::FeatureLevel::FEATURE_LEVEL_0
|
|
});
|
|
}
|
|
}
|
|
if (any(mTargetApi & TargetApi::VULKAN)) {
|
|
mCodeGenPermutations.push_back({
|
|
shaderModel,
|
|
TargetApi::VULKAN,
|
|
TargetLanguage::SPIRV,
|
|
effectiveFeatureLevel,
|
|
});
|
|
}
|
|
if (any(mTargetApi & TargetApi::METAL)) {
|
|
mCodeGenPermutations.push_back({
|
|
shaderModel,
|
|
TargetApi::METAL,
|
|
TargetLanguage::SPIRV,
|
|
effectiveFeatureLevel,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
MaterialBuilder::MaterialBuilder() : mMaterialName("Unnamed") {
|
|
std::fill_n(mProperties, MATERIAL_PROPERTIES_COUNT, false);
|
|
mShaderModels.reset();
|
|
}
|
|
|
|
MaterialBuilder::~MaterialBuilder() = default;
|
|
|
|
void MaterialBuilderBase::init() {
|
|
materialBuilderClients++;
|
|
GLSLTools::init();
|
|
}
|
|
|
|
void MaterialBuilderBase::shutdown() {
|
|
materialBuilderClients--;
|
|
GLSLTools::shutdown();
|
|
}
|
|
|
|
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(const char* name, size_t size, UniformType type,
|
|
ParameterPrecision precision) noexcept {
|
|
ASSERT_POSTCONDITION(mParameterCount < MAX_PARAMETERS_COUNT, "Too many parameters");
|
|
mParameters[mParameterCount++] = { name, type, size, precision };
|
|
return *this;
|
|
}
|
|
|
|
MaterialBuilder& MaterialBuilder::parameter(const char* name, UniformType type,
|
|
ParameterPrecision precision) noexcept {
|
|
return parameter(name, 1, type, precision);
|
|
}
|
|
|
|
|
|
MaterialBuilder& MaterialBuilder::parameter(const char* name, SamplerType samplerType,
|
|
SamplerFormat format, ParameterPrecision precision) noexcept {
|
|
ASSERT_POSTCONDITION(mParameterCount < MAX_PARAMETERS_COUNT, "Too many parameters");
|
|
mParameters[mParameterCount++] = { name, samplerType, format, precision };
|
|
return *this;
|
|
}
|
|
|
|
MaterialBuilder& MaterialBuilder::parameter(const char* name, SamplerType samplerType,
|
|
ParameterPrecision precision) noexcept {
|
|
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)));
|
|
return *this;
|
|
}
|
|
|
|
MaterialBuilder& MaterialBuilder::subpass(SubpassType subpassType, SamplerFormat format,
|
|
ParameterPrecision precision, const char* name) noexcept {
|
|
ASSERT_PRECONDITION(format == SamplerFormat::FLOAT,
|
|
"Subpass parameters must have FLOAT format.");
|
|
|
|
ASSERT_POSTCONDITION(mSubpassCount < MAX_SUBPASS_COUNT, "Too many subpasses");
|
|
mSubpasses[mSubpassCount++] = { name, subpassType, format, precision };
|
|
return *this;
|
|
}
|
|
|
|
MaterialBuilder& MaterialBuilder::subpass(SubpassType subpassType, SamplerFormat format,
|
|
const char* name) noexcept {
|
|
return subpass(subpassType, format, ParameterPrecision::DEFAULT, name);
|
|
}
|
|
|
|
MaterialBuilder& MaterialBuilder::subpass(SubpassType subpassType, ParameterPrecision precision,
|
|
const char* name) noexcept {
|
|
return subpass(subpassType, SamplerFormat::FLOAT, precision, name);
|
|
}
|
|
|
|
MaterialBuilder& MaterialBuilder::subpass(SubpassType subpassType, const char* name) noexcept {
|
|
return subpass(subpassType, SamplerFormat::FLOAT, ParameterPrecision::DEFAULT, name);
|
|
}
|
|
|
|
MaterialBuilder& MaterialBuilder::require(VertexAttribute attribute) noexcept {
|
|
mRequiredAttributes.set(attribute);
|
|
return *this;
|
|
}
|
|
|
|
MaterialBuilder& MaterialBuilder::groupSize(filament::math::uint3 groupSize) noexcept {
|
|
mGroupSize = groupSize;
|
|
return *this;
|
|
}
|
|
|
|
MaterialBuilder& MaterialBuilder::materialDomain(
|
|
MaterialBuilder::MaterialDomain materialDomain) noexcept {
|
|
mMaterialDomain = materialDomain;
|
|
if (mMaterialDomain == MaterialDomain::COMPUTE) {
|
|
// compute implies feature level 2
|
|
if (mFeatureLevel < FeatureLevel::FEATURE_LEVEL_2) {
|
|
mFeatureLevel = FeatureLevel::FEATURE_LEVEL_2;
|
|
}
|
|
}
|
|
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::featureLevel(FeatureLevel featureLevel) noexcept {
|
|
mFeatureLevel = featureLevel;
|
|
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::instanced(bool enable) noexcept {
|
|
mInstanced = 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::alphaToCoverage(bool enable) noexcept {
|
|
mAlphaToCoverage = enable;
|
|
mAlphaToCoverageSet = true;
|
|
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(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::hasSamplerType(SamplerType samplerType) const noexcept {
|
|
for (size_t i = 0, c = mParameterCount; i < c; i++) {
|
|
auto const& param = mParameters[i];
|
|
if (param.isSampler() && param.samplerType == samplerType) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void MaterialBuilder::prepareToBuild(MaterialInfo& info) noexcept {
|
|
MaterialBuilderBase::prepare(mEnableFramebufferFetch, mFeatureLevel);
|
|
|
|
// Build the per-material sampler block and uniform block.
|
|
SamplerInterfaceBlock::Builder sbb;
|
|
BufferInterfaceBlock::Builder ibb;
|
|
for (size_t i = 0, c = mParameterCount; i < c; i++) {
|
|
auto const& param = mParameters[i];
|
|
assert_invariant(!param.isSubpass());
|
|
if (param.isSampler()) {
|
|
sbb.add({ param.name.data(), param.name.size() },
|
|
param.samplerType, param.format, param.precision);
|
|
} else if (param.isUniform()) {
|
|
ibb.add({{{ param.name.data(), param.name.size() },
|
|
uint32_t(param.size == 1u ? 0u : param.size), param.uniformType,
|
|
param.precision, FeatureLevel::FEATURE_LEVEL_0 }});
|
|
}
|
|
}
|
|
|
|
for (size_t i = 0, c = mSubpassCount; i < c; i++) {
|
|
auto const& param = mSubpasses[i];
|
|
assert_invariant(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 = { CString("MaterialParams"), param.name, param.subpassType,
|
|
param.format, param.precision, attachmentIndex, binding };
|
|
}
|
|
|
|
for (auto const& buffer : mBuffers) {
|
|
info.buffers.emplace_back(buffer.get());
|
|
}
|
|
|
|
if (mSpecularAntiAliasing) {
|
|
ibb.add({
|
|
{ "_specularAntiAliasingVariance", 0, UniformType::FLOAT },
|
|
{ "_specularAntiAliasingThreshold", 0, UniformType::FLOAT },
|
|
});
|
|
}
|
|
|
|
if (mBlendingMode == BlendingMode::MASKED) {
|
|
ibb.add({{ "_maskThreshold", 0, UniformType::FLOAT }});
|
|
}
|
|
|
|
if (mDoubleSidedCapability) {
|
|
ibb.add({{ "_doubleSided", 0, UniformType::BOOL }});
|
|
}
|
|
|
|
mRequiredAttributes.set(VertexAttribute::POSITION);
|
|
if (mShading != Shading::UNLIT || mShadowMultiplier) {
|
|
mRequiredAttributes.set(VertexAttribute::TANGENTS);
|
|
}
|
|
|
|
info.sib = sbb.name("MaterialParams").build();
|
|
info.uib = ibb.name("MaterialParams").build();
|
|
|
|
info.isLit = isLit();
|
|
info.hasDoubleSidedCapability = mDoubleSidedCapability;
|
|
info.hasExternalSamplers = hasSamplerType(SamplerType::SAMPLER_EXTERNAL);
|
|
info.has3dSamplers = hasSamplerType(SamplerType::SAMPLER_3D);
|
|
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;
|
|
info.instanced = mInstanced;
|
|
info.vertexDomainDeviceJittered = mVertexDomainDeviceJittered;
|
|
info.featureLevel = mFeatureLevel;
|
|
info.groupSize = mGroupSize;
|
|
}
|
|
|
|
bool MaterialBuilder::findProperties(backend::ShaderStage type,
|
|
MaterialBuilder::PropertyList& allProperties,
|
|
CodeGenParams const& semanticCodeGenParams) noexcept {
|
|
GLSLTools glslTools;
|
|
std::string shaderCodeAllProperties = peek(type, semanticCodeGenParams, allProperties);
|
|
// Populate mProperties with the properties set in the shader.
|
|
if (!glslTools.findProperties(type, shaderCodeAllProperties, mProperties,
|
|
semanticCodeGenParams.targetApi,
|
|
semanticCodeGenParams.targetLanguage,
|
|
semanticCodeGenParams.shaderModel)) {
|
|
if (mPrintShaders) {
|
|
slog.e << shaderCodeAllProperties << io::endl;
|
|
}
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool MaterialBuilder::findAllProperties(CodeGenParams const& semanticCodeGenParams) noexcept {
|
|
if (mMaterialDomain != MaterialDomain::SURFACE) {
|
|
return true;
|
|
}
|
|
|
|
using namespace backend;
|
|
|
|
// 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);
|
|
if (!findProperties(ShaderStage::FRAGMENT, allProperties, semanticCodeGenParams)) {
|
|
return false;
|
|
}
|
|
if (!findProperties(ShaderStage::VERTEX, allProperties, semanticCodeGenParams)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool MaterialBuilder::runSemanticAnalysis(MaterialInfo* inOutInfo,
|
|
CodeGenParams const& semanticCodeGenParams) noexcept {
|
|
using namespace backend;
|
|
|
|
TargetApi targetApi = semanticCodeGenParams.targetApi;
|
|
TargetLanguage const targetLanguage = semanticCodeGenParams.targetLanguage;
|
|
assertSingleTargetApi(targetApi);
|
|
|
|
if (mEnableFramebufferFetch) {
|
|
// framebuffer fetch is only available with vulkan semantics
|
|
targetApi = TargetApi::VULKAN;
|
|
}
|
|
|
|
bool success = false;
|
|
std::string shaderCode;
|
|
ShaderModel const model = semanticCodeGenParams.shaderModel;
|
|
if (mMaterialDomain == filament::MaterialDomain::COMPUTE) {
|
|
shaderCode = peek(ShaderStage::COMPUTE, semanticCodeGenParams, mProperties);
|
|
success = GLSLTools::analyzeComputeShader(shaderCode, model,
|
|
targetApi, targetLanguage);
|
|
} else {
|
|
shaderCode = peek(ShaderStage::VERTEX, semanticCodeGenParams, mProperties);
|
|
success = GLSLTools::analyzeVertexShader(shaderCode, model, mMaterialDomain,
|
|
targetApi, targetLanguage);
|
|
if (success) {
|
|
shaderCode = peek(ShaderStage::FRAGMENT, semanticCodeGenParams, mProperties);
|
|
auto result = GLSLTools::analyzeFragmentShader(shaderCode, model, mMaterialDomain,
|
|
targetApi, targetLanguage, mCustomSurfaceShading);
|
|
success = result.has_value();
|
|
if (success) {
|
|
inOutInfo->userMaterialHasCustomDepth = result->userMaterialHasCustomDepth;
|
|
}
|
|
}
|
|
}
|
|
if (!success && mPrintShaders) {
|
|
slog.e << shaderCode << io::endl;
|
|
}
|
|
return success;
|
|
}
|
|
|
|
bool MaterialBuilder::ShaderCode::resolveIncludes(IncludeCallback callback,
|
|
const CString& fileName) noexcept {
|
|
if (!mCode.empty()) {
|
|
ResolveOptions options {
|
|
.insertLineDirectives = true,
|
|
.insertLineDirectiveCheck = true
|
|
};
|
|
IncludeResult source {
|
|
.includeName = fileName,
|
|
.text = mCode,
|
|
.lineNumberOffset = getLineOffset(),
|
|
.name = 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, backend::ShaderStage shaderType,
|
|
MaterialBuilder::FeatureLevel featureLevel,
|
|
const std::string& shaderCode) {
|
|
using ShaderStage = backend::ShaderStage;
|
|
using TargetApi = MaterialBuilder::TargetApi;
|
|
|
|
const char* targetApiString;
|
|
switch (targetApi) {
|
|
case TargetApi::OPENGL:
|
|
targetApiString = (featureLevel == MaterialBuilder::FeatureLevel::FEATURE_LEVEL_0)
|
|
? "GLES 2.0.\n" : "OpenGL.\n";
|
|
break;
|
|
case TargetApi::VULKAN:
|
|
targetApiString = "Vulkan.\n";
|
|
break;
|
|
case TargetApi::METAL:
|
|
targetApiString = "Metal.\n";
|
|
break;
|
|
case TargetApi::ALL:
|
|
assert(0); // Unreachable.
|
|
break;
|
|
}
|
|
|
|
const char* shaderStageString;
|
|
switch (shaderType) {
|
|
case ShaderStage::VERTEX:
|
|
shaderStageString = "Vertex Shader\n";
|
|
break;
|
|
case ShaderStage::FRAGMENT:
|
|
shaderStageString = "Fragment Shader\n";
|
|
break;
|
|
case ShaderStage::COMPUTE:
|
|
shaderStageString = "Compute Shader\n";
|
|
break;
|
|
}
|
|
|
|
slog.e
|
|
<< "Error in \"" << materialName << "\""
|
|
<< ", Variant 0x" << io::hex << +variant.key
|
|
<< ", " << targetApiString
|
|
<< "=========================\n"
|
|
<< "Generated " << shaderStageString
|
|
<< "=========================\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.
|
|
|
|
uint32_t flags = 0;
|
|
flags |= mPrintShaders ? GLSLPostProcessor::PRINT_SHADERS : 0;
|
|
flags |= mGenerateDebugInfo ? GLSLPostProcessor::GENERATE_DEBUG_INFO : 0;
|
|
GLSLPostProcessor postProcessor(mOptimization, flags);
|
|
|
|
// Start: must be protected by lock
|
|
Mutex entriesLock;
|
|
std::vector<TextEntry> glslEntries;
|
|
std::vector<TextEntry> essl1Entries;
|
|
std::vector<SpirvEntry> spirvEntries;
|
|
std::vector<TextEntry> metalEntries;
|
|
LineDictionary textDictionary;
|
|
BlobDictionary spirvDictionary;
|
|
// End: must be protected by lock
|
|
|
|
ShaderGenerator sg(mProperties, mVariables, mOutputs, mDefines, mConstants,
|
|
mMaterialFragmentCode.getResolved(), mMaterialFragmentCode.getLineOffset(),
|
|
mMaterialVertexCode.getResolved(), mMaterialVertexCode.getLineOffset(),
|
|
mMaterialDomain);
|
|
|
|
container.emplace<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;
|
|
const FeatureLevel featureLevel = params.featureLevel;
|
|
|
|
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{};
|
|
SpirvEntry spirvEntry{};
|
|
TextEntry metalEntry{};
|
|
|
|
glslEntry.shaderModel = params.shaderModel;
|
|
spirvEntry.shaderModel = params.shaderModel;
|
|
metalEntry.shaderModel = params.shaderModel;
|
|
|
|
glslEntry.variant = v.variant;
|
|
spirvEntry.variant = v.variant;
|
|
metalEntry.variant = v.variant;
|
|
|
|
// 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 == backend::ShaderStage::VERTEX) {
|
|
shader = sg.createVertexProgram(
|
|
shaderModel, targetApi, targetLanguage, featureLevel, info, v.variant,
|
|
mInterpolation, mVertexDomain);
|
|
} else if (v.stage == backend::ShaderStage::FRAGMENT) {
|
|
shader = sg.createFragmentProgram(
|
|
shaderModel, targetApi, targetLanguage, featureLevel, info, v.variant,
|
|
mInterpolation);
|
|
} else if (v.stage == backend::ShaderStage::COMPUTE) {
|
|
shader = sg.createComputeProgram(
|
|
shaderModel, targetApi, targetLanguage, featureLevel, info);
|
|
}
|
|
|
|
std::string* pGlsl = nullptr;
|
|
if (targetApiNeedsGlsl) {
|
|
pGlsl = &shader;
|
|
}
|
|
|
|
GLSLPostProcessor::Config config{
|
|
.variant = v.variant,
|
|
.targetApi = targetApi,
|
|
.targetLanguage = targetLanguage,
|
|
.shaderType = v.stage,
|
|
.shaderModel = shaderModel,
|
|
.featureLevel = featureLevel,
|
|
.domain = mMaterialDomain,
|
|
.materialInfo = &info,
|
|
.hasFramebufferFetch = mEnableFramebufferFetch,
|
|
.usesClipDistance = v.variant.hasInstancedStereo(),
|
|
.glsl = {},
|
|
};
|
|
|
|
if (mEnableFramebufferFetch) {
|
|
config.glsl.subpassInputToColorLocation.emplace_back(0, 0);
|
|
}
|
|
|
|
bool const ok = postProcessor.process(shader, config, pGlsl, pSpirv, pMsl);
|
|
if (!ok) {
|
|
showErrorMessage(mMaterialName.c_str_safe(), v.variant, targetApi, v.stage,
|
|
featureLevel, shader);
|
|
cancelJobs = true;
|
|
if (mPrintShaders) {
|
|
slog.e << shader << io::endl;
|
|
}
|
|
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<Mutex> const lock(entriesLock);
|
|
|
|
// below we rely on casting ShaderStage to uint8_t
|
|
static_assert(sizeof(filament::backend::ShaderStage) == 1);
|
|
|
|
|
|
switch (targetApi) {
|
|
case TargetApi::ALL:
|
|
// should never happen
|
|
break;
|
|
case TargetApi::OPENGL:
|
|
glslEntry.stage = v.stage;
|
|
glslEntry.shader = shader;
|
|
if (featureLevel == FeatureLevel::FEATURE_LEVEL_0) {
|
|
essl1Entries.push_back(glslEntry);
|
|
} else {
|
|
glslEntries.push_back(glslEntry);
|
|
}
|
|
break;
|
|
case TargetApi::VULKAN:
|
|
assert(!spirv.empty());
|
|
spirvEntry.stage = v.stage;
|
|
spirvEntry.spirv = std::move(spirv);
|
|
spirvEntries.push_back(spirvEntry);
|
|
break;
|
|
case TargetApi::METAL:
|
|
assert(!spirv.empty());
|
|
assert(msl.length() > 0);
|
|
metalEntry.stage = v.stage;
|
|
metalEntry.shader = msl;
|
|
metalEntries.push_back(metalEntry);
|
|
break;
|
|
}
|
|
});
|
|
|
|
// 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.variant.key)) == 1);
|
|
static_assert(sizeof(decltype(b.variant.key)) == 1);
|
|
const uint32_t akey = (uint32_t(a.shaderModel) << 16) | (uint32_t(a.variant.key) << 8) | uint32_t(a.stage);
|
|
const uint32_t bkey = (uint32_t(b.shaderModel) << 16) | (uint32_t(b.variant.key) << 8) | uint32_t(b.stage);
|
|
return akey < bkey;
|
|
};
|
|
std::sort(glslEntries.begin(), glslEntries.end(), compare);
|
|
std::sort(essl1Entries.begin(), essl1Entries.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);
|
|
}
|
|
for (const auto& s : essl1Entries) {
|
|
textDictionary.addText(s.shader);
|
|
}
|
|
for (auto& s : spirvEntries) {
|
|
std::vector<uint32_t> spirv = std::move(s.spirv);
|
|
s.dictionaryIndex = spirvDictionary.addBlob(spirv);
|
|
}
|
|
for (const auto& s : metalEntries) {
|
|
textDictionary.addText(s.shader);
|
|
}
|
|
|
|
// Emit dictionary chunk (TextDictionaryReader and DictionaryTextChunk)
|
|
const auto& dictionaryChunk = container.push<filamat::DictionaryTextChunk>(
|
|
std::move(textDictionary), ChunkType::DictionaryText);
|
|
|
|
// Emit GLSL chunk (MaterialTextChunk).
|
|
if (!glslEntries.empty()) {
|
|
container.push<MaterialTextChunk>(std::move(glslEntries),
|
|
dictionaryChunk.getDictionary(), ChunkType::MaterialGlsl);
|
|
}
|
|
|
|
// Emit ESSL1 chunk (MaterialTextChunk).
|
|
if (!essl1Entries.empty()) {
|
|
container.push<MaterialTextChunk>(std::move(essl1Entries),
|
|
dictionaryChunk.getDictionary(), ChunkType::MaterialEssl1);
|
|
}
|
|
|
|
// Emit SPIRV chunks (SpirvDictionaryReader and MaterialSpirvChunk).
|
|
if (!spirvEntries.empty()) {
|
|
const bool stripInfo = !mGenerateDebugInfo;
|
|
container.push<filamat::DictionarySpirvChunk>(std::move(spirvDictionary), stripInfo);
|
|
container.push<MaterialSpirvChunk>(std::move(spirvEntries));
|
|
}
|
|
|
|
// Emit Metal chunk (MaterialTextChunk).
|
|
if (!metalEntries.empty()) {
|
|
container.push<MaterialTextChunk>(std::move(metalEntries),
|
|
dictionaryChunk.getDictionary(), ChunkType::MaterialMetal);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
MaterialBuilder& MaterialBuilder::output(VariableQualifier qualifier, OutputTarget target,
|
|
Precision precision, 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, precision, 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::vertexDomainDeviceJittered(bool enabled) noexcept {
|
|
mVertexDomainDeviceJittered = enabled;
|
|
return *this;
|
|
}
|
|
|
|
MaterialBuilder& MaterialBuilder::useLegacyMorphing() noexcept {
|
|
mUseLegacyMorphing = true;
|
|
return *this;
|
|
}
|
|
|
|
Package MaterialBuilder::build(JobSystem& jobSystem) noexcept {
|
|
bool success;
|
|
if (materialBuilderClients == 0) {
|
|
slog.e << "Error: MaterialBuilder::init() must be called before build()." << io::endl;
|
|
// Return an empty package to signal a failure to build the material.
|
|
error:
|
|
return Package::invalidPackage();
|
|
}
|
|
|
|
// Add a default color output.
|
|
if (mMaterialDomain == MaterialDomain::POST_PROCESS && mOutputs.empty()) {
|
|
output(VariableQualifier::OUT,
|
|
OutputTarget::COLOR, Precision::DEFAULT, OutputType::FLOAT4, "color");
|
|
}
|
|
|
|
// TODO: maybe check MaterialDomain::COMPUTE has outputs
|
|
|
|
// Resolve all the #include directives within user code.
|
|
if (!mMaterialFragmentCode.resolveIncludes(mIncludeCallback, mFileName) ||
|
|
!mMaterialVertexCode.resolveIncludes(mIncludeCallback, mFileName)) {
|
|
goto error;
|
|
}
|
|
|
|
if (mCustomSurfaceShading && mShading != Shading::LIT) {
|
|
slog.e << "Error: customSurfaceShading can only be used with lit materials." << io::endl;
|
|
goto error;
|
|
}
|
|
|
|
// prepareToBuild must be called first, to populate mCodeGenPermutations.
|
|
MaterialInfo info{};
|
|
prepareToBuild(info);
|
|
|
|
// check level features
|
|
if (!checkMaterialLevelFeatures(info)) {
|
|
goto error;
|
|
}
|
|
|
|
// Run checks, in order.
|
|
// The call to findProperties populates mProperties and must come before runSemanticAnalysis.
|
|
// Return an empty package to signal a failure to build the material.
|
|
|
|
// For finding properties and running semantic analysis, we always use the same code gen
|
|
// permutation. This is the first permutation generated with default arguments passed to matc.
|
|
CodeGenParams const semanticCodeGenParams = {
|
|
.shaderModel = ShaderModel::MOBILE,
|
|
.targetApi = TargetApi::OPENGL,
|
|
.targetLanguage = (info.featureLevel == FeatureLevel::FEATURE_LEVEL_0) ?
|
|
TargetLanguage::GLSL : TargetLanguage::SPIRV,
|
|
.featureLevel = info.featureLevel,
|
|
};
|
|
|
|
if (!findAllProperties(semanticCodeGenParams)) {
|
|
goto error;
|
|
}
|
|
|
|
if (!runSemanticAnalysis(&info, semanticCodeGenParams)) {
|
|
goto error;
|
|
}
|
|
|
|
info.samplerBindings.init(mMaterialDomain, info.sib);
|
|
|
|
// adjust variant-filter for feature level *before* we start writing into the container
|
|
if (mFeatureLevel == filament::backend::FeatureLevel::FEATURE_LEVEL_0) {
|
|
// at feature level 0, many variants are not supported
|
|
mVariantFilter |= uint32_t(UserVariantFilterBit::DIRECTIONAL_LIGHTING);
|
|
mVariantFilter |= uint32_t(UserVariantFilterBit::DYNAMIC_LIGHTING);
|
|
mVariantFilter |= uint32_t(UserVariantFilterBit::SHADOW_RECEIVER);
|
|
mVariantFilter |= uint32_t(UserVariantFilterBit::SKINNING);
|
|
mVariantFilter |= uint32_t(UserVariantFilterBit::VSM);
|
|
mVariantFilter |= uint32_t(UserVariantFilterBit::SSR);
|
|
mVariantFilter |= uint32_t(UserVariantFilterBit::STE);
|
|
}
|
|
|
|
// 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.
|
|
|
|
std::vector<Variant> variants;
|
|
switch (mMaterialDomain) {
|
|
case MaterialDomain::SURFACE:
|
|
variants = determineSurfaceVariants(mVariantFilter, isLit(), mShadowMultiplier);
|
|
break;
|
|
case MaterialDomain::POST_PROCESS:
|
|
variants = determinePostProcessVariants();
|
|
break;
|
|
case MaterialDomain::COMPUTE:
|
|
variants = determineComputeVariants();
|
|
break;
|
|
}
|
|
|
|
success = generateShaders(jobSystem, variants, container, info);
|
|
if (!success) {
|
|
// Return an empty package to signal a failure to build the material.
|
|
goto error;
|
|
}
|
|
|
|
// Flatten all chunks in the container into a Package.
|
|
Package package(container.getSize());
|
|
Flattener f{ package.getData() };
|
|
container.flatten(f);
|
|
|
|
return package;
|
|
}
|
|
|
|
using namespace backend;
|
|
static const char* to_string(ShaderStageFlags stageFlags) noexcept {
|
|
switch (stageFlags) {
|
|
case ShaderStageFlags::NONE: return "{ }";
|
|
case ShaderStageFlags::VERTEX: return "{ vertex }";
|
|
case ShaderStageFlags::FRAGMENT: return "{ fragment }";
|
|
case ShaderStageFlags::COMPUTE: return "{ compute }";
|
|
case ShaderStageFlags::ALL_SHADER_STAGE_FLAGS: return "{ vertex | fragment | COMPUTE }";
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
bool MaterialBuilder::checkMaterialLevelFeatures(MaterialInfo const& info) const noexcept {
|
|
|
|
auto logSamplerOverflow = [](SamplerInterfaceBlock const& sib) {
|
|
auto const& samplers = sib.getSamplerInfoList();
|
|
auto const* stage = to_string(sib.getStageFlags());
|
|
for (auto const& sampler: samplers) {
|
|
slog.e << "\"" << sampler.name.c_str() << "\" "
|
|
<< Enums::toString(sampler.type).c_str() << " " << stage << '\n';
|
|
}
|
|
flush(slog.e);
|
|
};
|
|
|
|
auto userSamplerCount = info.sib.getSize();
|
|
for (auto const& sampler: info.sib.getSamplerInfoList()) {
|
|
if (sampler.type == SamplerInterfaceBlock::Type::SAMPLER_EXTERNAL) {
|
|
userSamplerCount += 1;
|
|
}
|
|
}
|
|
|
|
switch (info.featureLevel) {
|
|
case FeatureLevel::FEATURE_LEVEL_0:
|
|
// TODO: check FEATURE_LEVEL_0 features (e.g. unlit only, no texture arrays, etc...)
|
|
if (info.isLit) {
|
|
slog.e << "Error: material \"" << mMaterialName.c_str()
|
|
<< "\" has feature level " << +info.featureLevel
|
|
<< " and is not 'unlit'." << io::endl;
|
|
return false;
|
|
}
|
|
return true;
|
|
case FeatureLevel::FEATURE_LEVEL_1:
|
|
case FeatureLevel::FEATURE_LEVEL_2: {
|
|
if (mNoSamplerValidation) {
|
|
break;
|
|
}
|
|
|
|
auto const maxTextureCount = backend::FEATURE_LEVEL_CAPS[1].MAX_FRAGMENT_SAMPLER_COUNT;
|
|
|
|
// count how many samplers filament uses based on the material properties
|
|
// note: currently SSAO is not used with unlit, but we want to keep that possibility.
|
|
uint32_t textureUsedByFilamentCount = 4; // shadowMap, structure, ssao, fog texture
|
|
if (info.isLit) {
|
|
textureUsedByFilamentCount += 3; // froxels, dfg, specular
|
|
}
|
|
if (info.reflectionMode == ReflectionMode::SCREEN_SPACE ||
|
|
info.refractionMode == RefractionMode::SCREEN_SPACE) {
|
|
textureUsedByFilamentCount += 1; // ssr
|
|
}
|
|
if (mVariantFilter & (uint32_t)UserVariantFilterBit::FOG) {
|
|
textureUsedByFilamentCount -= 1; // fog texture
|
|
}
|
|
|
|
// TODO: we need constants somewhere for these values
|
|
if (userSamplerCount > maxTextureCount - textureUsedByFilamentCount) {
|
|
slog.e << "Error: material \"" << mMaterialName.c_str()
|
|
<< "\" has feature level " << +info.featureLevel
|
|
<< " and is using more than " << maxTextureCount - textureUsedByFilamentCount
|
|
<< " samplers." << io::endl;
|
|
logSamplerOverflow(info.sib);
|
|
return false;
|
|
}
|
|
auto const& samplerList = info.sib.getSamplerInfoList();
|
|
using SamplerInfo = SamplerInterfaceBlock::SamplerInfo;
|
|
if (std::any_of(samplerList.begin(), samplerList.end(),
|
|
[](const SamplerInfo& sampler) {
|
|
return sampler.type == SamplerType::SAMPLER_CUBEMAP_ARRAY;
|
|
})) {
|
|
slog.e << "Error: material \"" << mMaterialName.c_str()
|
|
<< "\" has feature level " << +info.featureLevel
|
|
<< " and uses a samplerCubemapArray." << io::endl;
|
|
logSamplerOverflow(info.sib);
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
case FeatureLevel::FEATURE_LEVEL_3: {
|
|
// TODO: we need constants somewhere for these values
|
|
// TODO: 16 is artificially low for now, until we have a better idea of what we want
|
|
if (userSamplerCount > 16) {
|
|
slog.e << "Error: material \"" << mMaterialName.c_str()
|
|
<< "\" has feature level " << +info.featureLevel
|
|
<< " and is using more than 16 samplers" << io::endl;
|
|
logSamplerOverflow(info.sib);
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
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(backend::ShaderStage stage,
|
|
const CodeGenParams& params, const PropertyList& properties) noexcept {
|
|
|
|
ShaderGenerator sg(properties, mVariables, mOutputs, mDefines, mConstants,
|
|
mMaterialFragmentCode.getResolved(), mMaterialFragmentCode.getLineOffset(),
|
|
mMaterialVertexCode.getResolved(), mMaterialVertexCode.getLineOffset(),
|
|
mMaterialDomain);
|
|
|
|
MaterialInfo info;
|
|
prepareToBuild(info);
|
|
info.samplerBindings.init(mMaterialDomain, info.sib);
|
|
|
|
switch (stage) {
|
|
case backend::ShaderStage::VERTEX:
|
|
return sg.createVertexProgram(
|
|
params.shaderModel, params.targetApi, params.targetLanguage,
|
|
params.featureLevel, info, {}, mInterpolation, mVertexDomain);
|
|
case backend::ShaderStage::FRAGMENT:
|
|
return sg.createFragmentProgram(
|
|
params.shaderModel, params.targetApi, params.targetLanguage,
|
|
params.featureLevel, info, {}, mInterpolation);
|
|
case backend::ShaderStage::COMPUTE:
|
|
return sg.createComputeProgram(
|
|
params.shaderModel, params.targetApi, params.targetLanguage,
|
|
params.featureLevel, info);
|
|
}
|
|
}
|
|
|
|
static Program::UniformInfo extractUniforms(BufferInterfaceBlock const& uib) noexcept {
|
|
auto list = uib.getFieldInfoList();
|
|
Program::UniformInfo uniforms = Program::UniformInfo::with_capacity(list.size());
|
|
|
|
char const firstLetter = std::tolower( uib.getName().at(0) );
|
|
std::string_view const nameAfterFirstLetter{
|
|
uib.getName().data() + 1, uib.getName().size() - 1 };
|
|
|
|
for (auto const& item : list) {
|
|
// construct the fully qualified name
|
|
std::string qualified;
|
|
qualified.reserve(uib.getName().size() + item.name.size() + 1u);
|
|
qualified.append({ &firstLetter, 1u });
|
|
qualified.append(nameAfterFirstLetter);
|
|
qualified.append(".");
|
|
qualified.append({ item.name.data(), item.name.size() });
|
|
|
|
uniforms.push_back({
|
|
{ qualified.data(), qualified.size() },
|
|
item.offset,
|
|
uint8_t(item.size < 1u ? 1u : item.size),
|
|
item.type
|
|
});
|
|
}
|
|
return uniforms;
|
|
}
|
|
|
|
void MaterialBuilder::writeCommonChunks(ChunkContainer& container, MaterialInfo& info) const noexcept {
|
|
container.emplace<uint32_t>(ChunkType::MaterialVersion, MATERIAL_VERSION);
|
|
container.emplace<uint8_t>(ChunkType::MaterialFeatureLevel, (uint8_t)info.featureLevel);
|
|
container.emplace<const char*>(ChunkType::MaterialName, mMaterialName.c_str_safe());
|
|
container.emplace<uint32_t>(ChunkType::MaterialShaderModels, mShaderModels.getValue());
|
|
container.emplace<uint8_t>(ChunkType::MaterialDomain, static_cast<uint8_t>(mMaterialDomain));
|
|
|
|
// if that ever needed to change, this would require a material version bump
|
|
static_assert(sizeof(uint32_t) >= sizeof(UserVariantFilterMask));
|
|
|
|
container.emplace<uint32_t>(ChunkType::MaterialVariantFilterMask, mVariantFilter);
|
|
|
|
using namespace filament;
|
|
|
|
if (info.featureLevel == FeatureLevel::FEATURE_LEVEL_0) {
|
|
FixedCapacityVector<std::pair<UniformBindingPoints, Program::UniformInfo>> list({
|
|
{ UniformBindingPoints::PER_VIEW,
|
|
extractUniforms(UibGenerator::getPerViewUib()) },
|
|
{ UniformBindingPoints::PER_RENDERABLE,
|
|
extractUniforms(UibGenerator::getPerRenderableUib()) },
|
|
{ UniformBindingPoints::PER_MATERIAL_INSTANCE,
|
|
extractUniforms(info.uib) },
|
|
});
|
|
|
|
// FIXME: don't hardcode this
|
|
auto& uniforms = list[1].second;
|
|
uniforms.clear();
|
|
uniforms.reserve(6);
|
|
uniforms.push_back({
|
|
"objectUniforms.data[0].worldFromModelMatrix",
|
|
offsetof(PerRenderableUib, data[0].worldFromModelMatrix), 1,
|
|
UniformType::MAT4 });
|
|
uniforms.push_back({
|
|
"objectUniforms.data[0].worldFromModelNormalMatrix",
|
|
offsetof(PerRenderableUib, data[0].worldFromModelNormalMatrix), 1,
|
|
UniformType::MAT3 });
|
|
uniforms.push_back({
|
|
"objectUniforms.data[0].morphTargetCount",
|
|
offsetof(PerRenderableUib, data[0].morphTargetCount), 1,
|
|
UniformType::UINT });
|
|
uniforms.push_back({
|
|
"objectUniforms.data[0].flagsChannels",
|
|
offsetof(PerRenderableUib, data[0].flagsChannels), 1,
|
|
UniformType::UINT });
|
|
uniforms.push_back({
|
|
"objectUniforms.data[0].objectId",
|
|
offsetof(PerRenderableUib, data[0].objectId), 1,
|
|
UniformType::UINT });
|
|
uniforms.push_back({
|
|
"objectUniforms.data[0].userData",
|
|
offsetof(PerRenderableUib, data[0].userData), 1,
|
|
UniformType::FLOAT });
|
|
|
|
container.push<MaterialBindingUniformInfoChunk>(std::move(list));
|
|
|
|
using Container = utils::FixedCapacityVector<std::pair<utils::CString, uint8_t>>;
|
|
auto attributes = Container::with_capacity(sAttributeDatabase.size());
|
|
for (auto const& attribute: sAttributeDatabase) {
|
|
std::string name("mesh_");
|
|
name.append(attribute.name);
|
|
attributes.emplace_back(utils::CString{ name.data(), name.size() }, attribute.location);
|
|
}
|
|
container.push<MaterialAttributesInfoChunk>(std::move(attributes));
|
|
}
|
|
|
|
// TODO: currently, the feature level used is determined by the material because we
|
|
// don't have "feature level" variants. In other words, a feature level 0 material
|
|
// won't work with a feature level 1 engine. However, we do embed the feature level 1
|
|
// meta-data, as it should.
|
|
|
|
if (info.featureLevel <= FeatureLevel::FEATURE_LEVEL_1) {
|
|
// note: this chunk is only needed for OpenGL backends, which don't all support layout(binding=)
|
|
FixedCapacityVector<std::pair<std::string_view, UniformBindingPoints>> list = {
|
|
{ PerViewUib::_name, UniformBindingPoints::PER_VIEW },
|
|
{ PerRenderableUib::_name, UniformBindingPoints::PER_RENDERABLE },
|
|
{ LightsUib::_name, UniformBindingPoints::LIGHTS },
|
|
{ ShadowUib::_name, UniformBindingPoints::SHADOW },
|
|
{ FroxelRecordUib::_name, UniformBindingPoints::FROXEL_RECORDS },
|
|
{ FroxelsUib::_name, UniformBindingPoints::FROXELS },
|
|
{ PerRenderableBoneUib::_name, UniformBindingPoints::PER_RENDERABLE_BONES },
|
|
{ PerRenderableMorphingUib::_name, UniformBindingPoints::PER_RENDERABLE_MORPHING },
|
|
{ info.uib.getName(), UniformBindingPoints::PER_MATERIAL_INSTANCE }
|
|
};
|
|
container.push<MaterialUniformBlockBindingsChunk>(std::move(list));
|
|
}
|
|
|
|
// note: this chunk is needed for Vulkan and GL backends. Metal shouldn't need it (but
|
|
// still does as of now).
|
|
container.push<MaterialSamplerBlockBindingChunk>(info.samplerBindings);
|
|
|
|
// User Material UIB
|
|
container.push<MaterialUniformInterfaceBlockChunk>(info.uib);
|
|
|
|
// 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.
|
|
|
|
if (mMaterialDomain != MaterialDomain::COMPUTE) {
|
|
// User Subpass
|
|
container.push<MaterialSubpassInterfaceBlockChunk>(info.subpass);
|
|
|
|
container.emplace<bool>(ChunkType::MaterialDoubleSidedSet, mDoubleSidedCapability);
|
|
container.emplace<bool>(ChunkType::MaterialDoubleSided, mDoubleSided);
|
|
container.emplace<uint8_t>(ChunkType::MaterialBlendingMode,
|
|
static_cast<uint8_t>(mBlendingMode));
|
|
container.emplace<uint8_t>(ChunkType::MaterialTransparencyMode,
|
|
static_cast<uint8_t>(mTransparencyMode));
|
|
container.emplace<uint8_t>(ChunkType::MaterialReflectionMode,
|
|
static_cast<uint8_t>(mReflectionMode));
|
|
container.emplace<bool>(ChunkType::MaterialColorWrite, mColorWrite);
|
|
container.emplace<bool>(ChunkType::MaterialDepthWriteSet, mDepthWriteSet);
|
|
container.emplace<bool>(ChunkType::MaterialDepthWrite, mDepthWrite);
|
|
container.emplace<bool>(ChunkType::MaterialDepthTest, mDepthTest);
|
|
container.emplace<bool>(ChunkType::MaterialInstanced, mInstanced);
|
|
container.emplace<bool>(ChunkType::MaterialAlphaToCoverageSet, mAlphaToCoverageSet);
|
|
container.emplace<bool>(ChunkType::MaterialAlphaToCoverage, mAlphaToCoverage);
|
|
container.emplace<uint8_t>(ChunkType::MaterialCullingMode,
|
|
static_cast<uint8_t>(mCullingMode));
|
|
|
|
uint64_t properties = 0;
|
|
UTILS_NOUNROLL
|
|
for (size_t i = 0; i < MATERIAL_PROPERTIES_COUNT; i++) {
|
|
if (mProperties[i]) {
|
|
properties |= uint64_t(1u) << i;
|
|
}
|
|
}
|
|
container.emplace<uint64_t>(ChunkType::MaterialProperties, properties);
|
|
}
|
|
|
|
// create a unique material id
|
|
auto const& vert = mMaterialVertexCode.getResolved();
|
|
auto const& frag = mMaterialFragmentCode.getResolved();
|
|
std::hash<std::string_view> const hasher;
|
|
size_t const materialId = utils::hash::combine(
|
|
MATERIAL_VERSION,
|
|
utils::hash::combine(
|
|
hasher({ vert.data(), vert.size() }),
|
|
hasher({ frag.data(), frag.size() })));
|
|
|
|
container.emplace<uint64_t>(ChunkType::MaterialCacheId, materialId);
|
|
}
|
|
|
|
void MaterialBuilder::writeSurfaceChunks(ChunkContainer& container) const noexcept {
|
|
if (mBlendingMode == BlendingMode::MASKED) {
|
|
container.emplace<float>(ChunkType::MaterialMaskThreshold, mMaskThreshold);
|
|
}
|
|
|
|
container.emplace<uint8_t>(ChunkType::MaterialShading, static_cast<uint8_t>(mShading));
|
|
|
|
if (mShading == Shading::UNLIT) {
|
|
container.emplace<bool>(ChunkType::MaterialShadowMultiplier, mShadowMultiplier);
|
|
}
|
|
|
|
container.emplace<uint8_t>(ChunkType::MaterialRefraction, static_cast<uint8_t>(mRefractionMode));
|
|
container.emplace<uint8_t>(ChunkType::MaterialRefractionType,
|
|
static_cast<uint8_t>(mRefractionType));
|
|
container.emplace<bool>(ChunkType::MaterialClearCoatIorChange, mClearCoatIorChange);
|
|
container.emplace<uint32_t>(ChunkType::MaterialRequiredAttributes,
|
|
mRequiredAttributes.getValue());
|
|
container.emplace<bool>(ChunkType::MaterialSpecularAntiAliasing, mSpecularAntiAliasing);
|
|
container.emplace<float>(ChunkType::MaterialSpecularAntiAliasingVariance,
|
|
mSpecularAntiAliasingVariance);
|
|
container.emplace<float>(ChunkType::MaterialSpecularAntiAliasingThreshold,
|
|
mSpecularAntiAliasingThreshold);
|
|
container.emplace<uint8_t>(ChunkType::MaterialVertexDomain, static_cast<uint8_t>(mVertexDomain));
|
|
container.emplace<uint8_t>(ChunkType::MaterialInterpolation,
|
|
static_cast<uint8_t>(mInterpolation));
|
|
}
|
|
|
|
MaterialBuilder& MaterialBuilder::noSamplerValidation(bool enabled) noexcept {
|
|
mNoSamplerValidation = enabled;
|
|
return *this;
|
|
}
|
|
|
|
} // namespace filamat
|