From e41dd027ab77e9d5b0481dd6fc32f0a3ab95fbdf Mon Sep 17 00:00:00 2001 From: Romain Guy Date: Mon, 10 Dec 2018 12:02:59 -0800 Subject: [PATCH] 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 --- libs/filamat/src/GLSLPostProcessor.cpp | 4 +- libs/filamat/src/GLSLPostProcessor.h | 4 +- libs/filamat/src/MaterialBuilder.cpp | 21 ++++++-- .../src/PostprocessMaterialBuilder.cpp | 9 +++- libs/filamat/src/shaders/CodeGenerator.cpp | 49 ++++++++++++++++++- libs/filamat/src/shaders/CodeGenerator.h | 3 ++ libs/filamat/src/shaders/ShaderGenerator.cpp | 10 ++++ libs/filamat/src/shaders/ShaderGenerator.h | 11 +++++ tools/matc/src/matc/MaterialCompiler.cpp | 11 ----- tools/matc/src/matc/ParametersProcessor.cpp | 2 +- 10 files changed, 101 insertions(+), 23 deletions(-) diff --git a/libs/filamat/src/GLSLPostProcessor.cpp b/libs/filamat/src/GLSLPostProcessor.cpp index f4856df3f6..bbd4c61111 100644 --- a/libs/filamat/src/GLSLPostProcessor.cpp +++ b/libs/filamat/src/GLSLPostProcessor.cpp @@ -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 diff --git a/libs/filamat/src/GLSLPostProcessor.h b/libs/filamat/src/GLSLPostProcessor.h index 830edaa2d8..e47d10dd41 100644 --- a/libs/filamat/src/GLSLPostProcessor.h +++ b/libs/filamat/src/GLSLPostProcessor.h @@ -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; diff --git a/libs/filamat/src/MaterialBuilder.cpp b/libs/filamat/src/MaterialBuilder.cpp index c4e00fb461..648868f3b3 100644 --- a/libs/filamat/src/MaterialBuilder.cpp +++ b/libs/filamat/src/MaterialBuilder.cpp @@ -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; diff --git a/libs/filamat/src/PostprocessMaterialBuilder.cpp b/libs/filamat/src/PostprocessMaterialBuilder.cpp index 6c5143c82c..09fe4cd2f5 100644 --- a/libs/filamat/src/PostprocessMaterialBuilder.cpp +++ b/libs/filamat/src/PostprocessMaterialBuilder.cpp @@ -46,7 +46,7 @@ Package PostprocessMaterialBuilder::build() { // Create chunk tree. ChunkContainer container; - SimpleFieldChunk version(ChunkType::PostProcessVersion,1); + SimpleFieldChunk version(ChunkType::PostProcessVersion, 1); container.addChild(&version); std::vector 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); diff --git a/libs/filamat/src/shaders/CodeGenerator.cpp b/libs/filamat/src/shaders/CodeGenerator.cpp index 44d4d991f1..8c6a8bba3c 100644 --- a/libs/filamat/src/shaders/CodeGenerator.cpp +++ b/libs/filamat/src/shaders/CodeGenerator.cpp @@ -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"; diff --git a/libs/filamat/src/shaders/CodeGenerator.h b/libs/filamat/src/shaders/CodeGenerator.h index 48dec967c1..48858027b7 100644 --- a/libs/filamat/src/shaders/CodeGenerator.h +++ b/libs/filamat/src/shaders/CodeGenerator.h @@ -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; diff --git a/libs/filamat/src/shaders/ShaderGenerator.cpp b/libs/filamat/src/shaders/ShaderGenerator.cpp index cbfa6f2a6e..7d8606974a 100644 --- a/libs/filamat/src/shaders/ShaderGenerator.cpp +++ b/libs/filamat/src/shaders/ShaderGenerator.cpp @@ -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 diff --git a/libs/filamat/src/shaders/ShaderGenerator.h b/libs/filamat/src/shaders/ShaderGenerator.h index 2dbba9c364..2d5097d770 100644 --- a/libs/filamat/src/shaders/ShaderGenerator.h +++ b/libs/filamat/src/shaders/ShaderGenerator.h @@ -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; diff --git a/tools/matc/src/matc/MaterialCompiler.cpp b/tools/matc/src/matc/MaterialCompiler.cpp index 04ac8057c8..ea7e5cf234 100644 --- a/tools/matc/src/matc/MaterialCompiler.cpp +++ b/tools/matc/src/matc/MaterialCompiler.cpp @@ -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).setOptimizationLevel(Config::Optimization::PREPROCESSOR); - } - builder .platform(config.getPlatform()) .targetApi(config.getTargetApi()) diff --git a/tools/matc/src/matc/ParametersProcessor.cpp b/tools/matc/src/matc/ParametersProcessor.cpp index 728c729b86..25cb43fa84 100644 --- a/tools/matc/src/matc/ParametersProcessor.cpp +++ b/tools/matc/src/matc/ParametersProcessor.cpp @@ -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; }