Enable optimization passes on materials with external samplers (#605)
* Enable optimization passes on materials with external samplers Because external samplers are not properly supported by SPIRV and associated library (spirv-cross and spirv-tools) we currently disable all optimizations when we encounter a material with external samplers. This however causes issues on some misbehaved drivers (not running the optimizations has a side effect which causes a crash). To enable the optimization pass we simply rely on the Vulkan codegen target to substitute samplerExternal with sampler2D. We then analyze the output GLSL (post-optimization) and revert the relevants sampler2D declarations to samplerExternal declarations. This fixup only occurs after optimization and for mobile targets and if external samplers were declared. * Address review comments
This commit is contained in:
@@ -206,7 +206,7 @@ bool GLSLPostProcessor::process(const std::string& inputShader,
|
||||
}
|
||||
|
||||
void GLSLPostProcessor::preprocessOptimization(glslang::TShader& tShader,
|
||||
const filament::driver::ShaderModel shaderModel) const {
|
||||
filament::driver::ShaderModel shaderModel) const {
|
||||
using TargetApi = MaterialBuilder::TargetApi;
|
||||
|
||||
std::string glsl;
|
||||
@@ -246,7 +246,7 @@ void GLSLPostProcessor::preprocessOptimization(glslang::TShader& tShader,
|
||||
}
|
||||
|
||||
void GLSLPostProcessor::fullOptimization(const TShader& tShader,
|
||||
const filament::driver::ShaderModel shaderModel) const {
|
||||
filament::driver::ShaderModel shaderModel) const {
|
||||
SpirvBlob spirv;
|
||||
|
||||
// Compile GLSL to to SPIR-V
|
||||
|
||||
@@ -44,9 +44,9 @@ public:
|
||||
|
||||
private:
|
||||
void fullOptimization(const glslang::TShader& tShader,
|
||||
const filament::driver::ShaderModel shaderModel) const;
|
||||
filament::driver::ShaderModel shaderModel) const;
|
||||
void preprocessOptimization(glslang::TShader& tShader,
|
||||
const filament::driver::ShaderModel shaderModel) const;
|
||||
filament::driver::ShaderModel shaderModel) const;
|
||||
|
||||
void registerSizePasses(spvtools::Optimizer& optimizer) const;
|
||||
void registerPerformancePasses(spvtools::Optimizer& optimizer) const;
|
||||
|
||||
@@ -471,13 +471,15 @@ Package MaterialBuilder::build() noexcept {
|
||||
spirvEntry.variant = k;
|
||||
|
||||
// Remove variants for unlit materials
|
||||
uint8_t v = filament::Variant::filterVariant(k & variantMask, isLit() || mShadowMultiplier);
|
||||
uint8_t v = filament::Variant::filterVariant(
|
||||
k & variantMask, isLit() || mShadowMultiplier);
|
||||
|
||||
if (filament::Variant::filterVariantVertex(v) == k) {
|
||||
// Vertex Shader
|
||||
std::string vs = sg.createVertexProgram(
|
||||
shaderModel, targetApi, codeGenTargetApi, info, k,
|
||||
mInterpolation, mVertexDomain);
|
||||
|
||||
bool ok = postProcessor.process(vs, filament::driver::ShaderType::VERTEX,
|
||||
shaderModel, &vs, pSpirv);
|
||||
if (!ok) {
|
||||
@@ -486,14 +488,20 @@ Package MaterialBuilder::build() noexcept {
|
||||
errorOccured = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (targetApi == TargetApi::OPENGL) {
|
||||
if (codeGenTargetApi == TargetApi::VULKAN) {
|
||||
sg.fixupExternalSamplers(shaderModel, vs, info);
|
||||
}
|
||||
|
||||
glslEntry.stage = filament::driver::ShaderType::VERTEX;
|
||||
glslEntry.shaderSize = vs.size();
|
||||
glslEntry.shader = (char*)malloc(glslEntry.shaderSize + 1);
|
||||
glslEntry.shader = (char*) malloc(glslEntry.shaderSize + 1);
|
||||
strcpy(glslEntry.shader, vs.c_str());
|
||||
glslDictionary.addText(glslEntry.shader);
|
||||
glslEntries.push_back(glslEntry);
|
||||
}
|
||||
|
||||
if (targetApi == TargetApi::VULKAN) {
|
||||
assert(spirv.size() > 0);
|
||||
spirvEntry.stage = filament::driver::ShaderType::VERTEX;
|
||||
@@ -507,6 +515,7 @@ Package MaterialBuilder::build() noexcept {
|
||||
// Fragment Shader
|
||||
std::string fs = sg.createFragmentProgram(
|
||||
shaderModel, targetApi, codeGenTargetApi, info, k, mInterpolation);
|
||||
|
||||
bool ok = postProcessor.process(fs, filament::driver::ShaderType::FRAGMENT,
|
||||
shaderModel, &fs, pSpirv);
|
||||
if (!ok) {
|
||||
@@ -515,14 +524,20 @@ Package MaterialBuilder::build() noexcept {
|
||||
errorOccured = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (targetApi == TargetApi::OPENGL) {
|
||||
if (codeGenTargetApi == TargetApi::VULKAN) {
|
||||
sg.fixupExternalSamplers(shaderModel, fs, info);
|
||||
}
|
||||
|
||||
glslEntry.stage = filament::driver::ShaderType::FRAGMENT;
|
||||
glslEntry.shaderSize = fs.size();
|
||||
glslEntry.shader = (char*)malloc(glslEntry.shaderSize + 1);
|
||||
glslEntry.shader = (char*) malloc(glslEntry.shaderSize + 1);
|
||||
strcpy(glslEntry.shader, fs.c_str());
|
||||
glslDictionary.addText(glslEntry.shader);
|
||||
glslEntries.push_back(glslEntry);
|
||||
}
|
||||
|
||||
if (targetApi == TargetApi::VULKAN) {
|
||||
assert(spirv.size() > 0);
|
||||
spirvEntry.stage = filament::driver::ShaderType::FRAGMENT;
|
||||
|
||||
@@ -46,7 +46,7 @@ Package PostprocessMaterialBuilder::build() {
|
||||
// Create chunk tree.
|
||||
ChunkContainer container;
|
||||
|
||||
SimpleFieldChunk<uint32_t> version(ChunkType::PostProcessVersion,1);
|
||||
SimpleFieldChunk<uint32_t> version(ChunkType::PostProcessVersion, 1);
|
||||
container.addChild(&version);
|
||||
|
||||
std::vector<TextEntry> glslEntries;
|
||||
@@ -62,6 +62,7 @@ Package PostprocessMaterialBuilder::build() {
|
||||
const uint8_t firstSampler =
|
||||
samplerBindingMap.getBlockOffset(filament::BindingPoints::POST_PROCESS);
|
||||
bool errorOccured = false;
|
||||
|
||||
for (const auto& params : mCodeGenPermutations) {
|
||||
const ShaderModel shaderModel = ShaderModel(params.shaderModel);
|
||||
const TargetApi targetApi = params.targetApi;
|
||||
@@ -99,6 +100,7 @@ Package PostprocessMaterialBuilder::build() {
|
||||
glslDictionary.addText(glslEntry.shader);
|
||||
glslEntries.push_back(glslEntry);
|
||||
}
|
||||
|
||||
if (targetApi == TargetApi::VULKAN) {
|
||||
spirvEntry.stage = filament::driver::ShaderType::VERTEX;
|
||||
spirvEntry.dictionaryIndex = spirvDictionary.addBlob(spirv);
|
||||
@@ -110,6 +112,7 @@ Package PostprocessMaterialBuilder::build() {
|
||||
std::string fs = ShaderPostProcessGenerator::createPostProcessFragmentProgram(
|
||||
shaderModel, targetApi, codeGenTargetApi,
|
||||
filament::PostProcessStage(k), firstSampler);
|
||||
|
||||
ok = postProcessor.process(fs, filament::driver::ShaderType::FRAGMENT, shaderModel, &fs,
|
||||
pSpirv);
|
||||
if (!ok) {
|
||||
@@ -117,14 +120,16 @@ Package PostprocessMaterialBuilder::build() {
|
||||
errorOccured = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (targetApi == TargetApi::OPENGL) {
|
||||
glslEntry.stage = filament::driver::ShaderType::FRAGMENT;
|
||||
glslEntry.shaderSize = fs.size();
|
||||
glslEntry.shader = (char*)malloc(glslEntry.shaderSize + 1);
|
||||
glslEntry.shader = (char*) malloc(glslEntry.shaderSize + 1);
|
||||
strcpy(glslEntry.shader, fs.c_str());
|
||||
glslDictionary.addText(glslEntry.shader);
|
||||
glslEntries.push_back(glslEntry);
|
||||
}
|
||||
|
||||
if (targetApi == TargetApi::VULKAN) {
|
||||
spirvEntry.stage = filament::driver::ShaderType::FRAGMENT;
|
||||
spirvEntry.dictionaryIndex = spirvDictionary.addBlob(spirv);
|
||||
|
||||
@@ -279,11 +279,11 @@ std::ostream& CodeGenerator::generateSamplers(
|
||||
|
||||
const CString& blockName = sib.getName();
|
||||
std::string instanceName(blockName.c_str());
|
||||
instanceName.front() = char(std::tolower((unsigned char)instanceName.front()));
|
||||
instanceName.front() = char(std::tolower((unsigned char) instanceName.front()));
|
||||
|
||||
for (auto const& info : infos) {
|
||||
auto type = info.type;
|
||||
if (info.type == SamplerType::SAMPLER_EXTERNAL && mShaderModel != ShaderModel::GL_ES_30) {
|
||||
if (type == SamplerType::SAMPLER_EXTERNAL && mShaderModel != ShaderModel::GL_ES_30) {
|
||||
// we're generating the shader for the desktop, where we assume external textures
|
||||
// are not supported, in which case we revert to texture2d
|
||||
type = SamplerType::SAMPLER_2D;
|
||||
@@ -303,6 +303,51 @@ std::ostream& CodeGenerator::generateSamplers(
|
||||
return out;
|
||||
}
|
||||
|
||||
void CodeGenerator::fixupExternalSamplers(
|
||||
std::string& shader, SamplerInterfaceBlock const& sib) noexcept {
|
||||
auto const& infos = sib.getSamplerInfoList();
|
||||
if (infos.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const CString& blockName = sib.getName();
|
||||
std::string instanceName(blockName.c_str());
|
||||
instanceName.front() = char(std::tolower((unsigned char) instanceName.front()));
|
||||
|
||||
bool hasExternalSampler = false;
|
||||
|
||||
// Replace sampler2D declarations by samplerExternal declarations as they may have
|
||||
// been swapped during a previous optimization step
|
||||
for (auto const& info : infos) {
|
||||
if (info.type == SamplerType::SAMPLER_EXTERNAL) {
|
||||
auto name = std::string("sampler2D ") + instanceName + '_' + info.name.c_str();
|
||||
size_t index = shader.find(name);
|
||||
|
||||
if (index != std::string::npos) {
|
||||
hasExternalSampler = true;
|
||||
auto newName =
|
||||
std::string("samplerExternalOES ") + instanceName + '_' + info.name.c_str();
|
||||
shader.replace(index, name.size(), newName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This method should only be called on shaders that have external samplers but since
|
||||
// they may have been removed by previous optimization steps, we check again here
|
||||
if (hasExternalSampler) {
|
||||
// Find the #version line so we can insert the #extension directive
|
||||
size_t index = shader.find("#version");
|
||||
index += 8;
|
||||
|
||||
// Find the end of the line and skip the line return
|
||||
while (shader[index] != '\n') index++;
|
||||
index++;
|
||||
|
||||
shader.insert(index, "#extension GL_OES_EGL_image_external_essl3 : require\n");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
std::ostream& CodeGenerator::generateDefine(std::ostream& out, const char* name, bool value) const {
|
||||
if (value) {
|
||||
out << "#define " << name << "\n";
|
||||
|
||||
@@ -109,6 +109,9 @@ public:
|
||||
std::ostream& generateGetters(std::ostream& out, ShaderType type) const;
|
||||
std::ostream& generateParameters(std::ostream& out, ShaderType type) const;
|
||||
|
||||
static void fixupExternalSamplers(
|
||||
std::string& shader, filament::SamplerInterfaceBlock const& sib) noexcept;
|
||||
|
||||
private:
|
||||
filament::driver::Precision getDefaultPrecision(ShaderType type) const;
|
||||
filament::driver::Precision getDefaultUniformPrecision() const;
|
||||
|
||||
@@ -337,6 +337,15 @@ const std::string ShaderGenerator::createFragmentProgram(filament::driver::Shade
|
||||
return fs.str();
|
||||
}
|
||||
|
||||
void ShaderGenerator::fixupExternalSamplers(filament::driver::ShaderModel sm,
|
||||
std::string& shader, MaterialInfo const& material) const noexcept {
|
||||
// External samplers are only supported on GL ES at the moment, we must
|
||||
// skip the fixup on desktop targets
|
||||
if (material.hasExternalSamplers && sm == ShaderModel::GL_ES_30) {
|
||||
CodeGenerator::fixupExternalSamplers(shader, material.sib);
|
||||
}
|
||||
}
|
||||
|
||||
const std::string ShaderPostProcessGenerator::createPostProcessVertexProgram(
|
||||
filament::driver::ShaderModel sm, MaterialBuilder::TargetApi targetApi,
|
||||
MaterialBuilder::TargetApi codeGenTargetApi, filament::PostProcessStage variant,
|
||||
@@ -420,4 +429,5 @@ void ShaderPostProcessGenerator::generatePostProcessStageDefines(std::stringstre
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // namespace filament
|
||||
|
||||
@@ -51,8 +51,19 @@ public:
|
||||
MaterialBuilder::TargetApi targetApi, MaterialBuilder::TargetApi codeGenTargetApi,
|
||||
MaterialInfo const& material, uint8_t variantKey,
|
||||
filament::Interpolation interpolation) const noexcept;
|
||||
|
||||
bool hasCustomDepthShader() const noexcept;
|
||||
|
||||
/**
|
||||
* When a GLSL shader is optimized we run it through an intermediate SPIR-V
|
||||
* representation. Unfortunately external samplers cannot be used with SPIR-V
|
||||
* at this time, so we must transform them into regular 2D samplers. This
|
||||
* fixup step can be used to turn the samplers back into external samplers after
|
||||
* the optimizations have been applied.
|
||||
*/
|
||||
void fixupExternalSamplers(filament::driver::ShaderModel sm, std::string& shader,
|
||||
MaterialInfo const& material) const noexcept;
|
||||
|
||||
private:
|
||||
MaterialBuilder::PropertyList mProperties;
|
||||
MaterialBuilder::VariableList mVariables;
|
||||
|
||||
@@ -266,17 +266,6 @@ bool MaterialCompiler::run(const Config& config) {
|
||||
return reflectParameters(builder);
|
||||
}
|
||||
|
||||
Config::Optimization optimizationLevel = config.getOptimizationLevel();
|
||||
// Drop the optimization level to preprocessor when the material uses external samplers
|
||||
// samplerExternalOES in GLSL is currently not fully supported in SPIR-V/Vulkan and
|
||||
// proper handling is lacking in glslang and spirv-cross
|
||||
// TODO: remove when external samplers are fully supported in SPIR-V
|
||||
if (builder.hasExternalSampler() && optimizationLevel != Config::Optimization::PREPROCESSOR) {
|
||||
std::cerr << "Warning: external sampler detected, lowering optimizations "
|
||||
"to preprocessor only." << std::endl;
|
||||
const_cast<Config&>(config).setOptimizationLevel(Config::Optimization::PREPROCESSOR);
|
||||
}
|
||||
|
||||
builder
|
||||
.platform(config.getPlatform())
|
||||
.targetApi(config.getTargetApi())
|
||||
|
||||
@@ -129,7 +129,7 @@ bool ParametersProcessor::process(filamat::MaterialBuilder& builder, const Jsoni
|
||||
const std::string& key = entry.first;
|
||||
const JsonishValue* field = entry.second;
|
||||
if (mConfigProcessor.find(key) == mConfigProcessor.end()) {
|
||||
std::cerr << "Ignoring config entry (unknown key):\"" << key << "\"" << std::endl;
|
||||
std::cerr << "Ignoring config entry (unknown key): \"" << key << "\"" << std::endl;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user