479 lines
18 KiB
C++
479 lines
18 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 "GLSLPostProcessor.h"
|
|
|
|
#include <sstream>
|
|
#include <vector>
|
|
|
|
#include <GlslangToSpv.h>
|
|
#include <SPVRemapper.h>
|
|
#include <localintermediate.h>
|
|
|
|
#include <spirv_glsl.hpp>
|
|
#include <spirv_msl.hpp>
|
|
|
|
#include "sca/builtinResource.h"
|
|
#include "sca/GLSLTools.h"
|
|
|
|
#include <utils/Log.h>
|
|
|
|
using namespace glslang;
|
|
using namespace spirv_cross;
|
|
using namespace spvtools;
|
|
|
|
namespace filamat {
|
|
|
|
GLSLPostProcessor::GLSLPostProcessor(MaterialBuilder::Optimization optimization, uint32_t flags)
|
|
: mOptimization(optimization),
|
|
mPrintShaders(flags & PRINT_SHADERS),
|
|
mGenerateDebugInfo(flags & GENERATE_DEBUG_INFO) {
|
|
// SPIRV error handler registration needs to occur only once. To avoid a race we do it up here
|
|
// in the constructor, which gets invoked before MaterialBuilder kicks off jobs.
|
|
spv::spirvbin_t::registerErrorHandler([](const std::string& str) {
|
|
utils::slog.e << str << utils::io::endl;
|
|
});
|
|
}
|
|
|
|
GLSLPostProcessor::~GLSLPostProcessor() {
|
|
}
|
|
|
|
static uint32_t shaderVersionFromModel(filament::backend::ShaderModel model) {
|
|
switch (model) {
|
|
case filament::backend::ShaderModel::UNKNOWN:
|
|
case filament::backend::ShaderModel::GL_ES_30:
|
|
return 300;
|
|
case filament::backend::ShaderModel::GL_CORE_41:
|
|
return 410;
|
|
}
|
|
}
|
|
|
|
static bool filterSpvOptimizerMessage(spv_message_level_t level) {
|
|
#ifdef NDEBUG
|
|
// In release builds, only log errors.
|
|
if (level == SPV_MSG_WARNING ||
|
|
level == SPV_MSG_INFO ||
|
|
level == SPV_MSG_DEBUG) {
|
|
return false;
|
|
}
|
|
#endif
|
|
return true;
|
|
}
|
|
|
|
static std::string stringifySpvOptimizerMessage(spv_message_level_t level, const char* source,
|
|
const spv_position_t& position, const char* message) {
|
|
const char* levelString = nullptr;
|
|
switch (level) {
|
|
case SPV_MSG_FATAL:
|
|
levelString = "FATAL";
|
|
break;
|
|
case SPV_MSG_INTERNAL_ERROR:
|
|
levelString = "INTERNAL ERROR";
|
|
break;
|
|
case SPV_MSG_ERROR:
|
|
levelString = "ERROR";
|
|
break;
|
|
case SPV_MSG_WARNING:
|
|
levelString = "WARNING";
|
|
break;
|
|
case SPV_MSG_INFO:
|
|
levelString = "INFO";
|
|
break;
|
|
case SPV_MSG_DEBUG:
|
|
levelString = "DEBUG";
|
|
break;
|
|
}
|
|
|
|
std::ostringstream oss;
|
|
oss << levelString << ": ";
|
|
if (source) oss << source << ":";
|
|
oss << position.line << ":" << position.column << ":";
|
|
oss << position.index << ": ";
|
|
if (message) oss << message;
|
|
|
|
return oss.str();
|
|
}
|
|
|
|
void GLSLPostProcessor::spirvToToMsl(const SpirvBlob *spirv, std::string *outMsl,
|
|
const Config &config, ShaderMinifier& minifier) const {
|
|
|
|
CompilerMSL mslCompiler(*spirv);
|
|
CompilerGLSL::Options options;
|
|
mslCompiler.set_common_options(options);
|
|
|
|
const CompilerMSL::Options::Platform platform =
|
|
config.shaderModel == filament::backend::ShaderModel::GL_ES_30 ?
|
|
CompilerMSL::Options::Platform::iOS : CompilerMSL::Options::Platform::macOS;
|
|
|
|
CompilerMSL::Options mslOptions = {};
|
|
mslOptions.platform = platform,
|
|
mslOptions.msl_version = CompilerMSL::Options::make_msl_version(1, 1);
|
|
|
|
if (config.shaderModel == filament::backend::ShaderModel::GL_ES_30) {
|
|
mslOptions.use_framebuffer_fetch_subpasses = true;
|
|
}
|
|
|
|
mslCompiler.set_msl_options(mslOptions);
|
|
|
|
auto executionModel = mslCompiler.get_execution_model();
|
|
|
|
auto duplicateResourceBinding = [executionModel, &mslCompiler](const auto& resource) {
|
|
auto set = mslCompiler.get_decoration(resource.id, spv::DecorationDescriptorSet);
|
|
auto binding = mslCompiler.get_decoration(resource.id, spv::DecorationBinding);
|
|
MSLResourceBinding newBinding;
|
|
newBinding.stage = executionModel;
|
|
newBinding.desc_set = set;
|
|
newBinding.binding = binding;
|
|
newBinding.msl_texture = binding;
|
|
newBinding.msl_sampler = binding;
|
|
newBinding.msl_buffer = binding;
|
|
mslCompiler.add_msl_resource_binding(newBinding);
|
|
};
|
|
|
|
auto resources = mslCompiler.get_shader_resources();
|
|
for (const auto& resource : resources.sampled_images) {
|
|
duplicateResourceBinding(resource);
|
|
}
|
|
for (const auto& resource : resources.uniform_buffers) {
|
|
duplicateResourceBinding(resource);
|
|
}
|
|
|
|
*outMsl = mslCompiler.compile();
|
|
*outMsl = minifier.removeWhitespace(*outMsl);
|
|
}
|
|
|
|
bool GLSLPostProcessor::process(const std::string& inputShader, Config const& config,
|
|
std::string* outputGlsl, SpirvBlob* outputSpirv, std::string* outputMsl) {
|
|
|
|
// If TargetApi is Vulkan, then we need post-processing even if there's no optimization.
|
|
// If we're using framebuffer fetch, we also need to force our compilation target to Vulkan, as
|
|
// it is only supported in GLSL for Vulkan.
|
|
using TargetApi = MaterialBuilder::TargetApi;
|
|
const TargetApi targetApi = (outputSpirv || config.hasFramebufferFetch) ? TargetApi::VULKAN :
|
|
TargetApi::OPENGL;
|
|
if (targetApi == TargetApi::OPENGL && mOptimization == MaterialBuilder::Optimization::NONE) {
|
|
*outputGlsl = inputShader;
|
|
if (mPrintShaders) {
|
|
utils::slog.i << *outputGlsl << utils::io::endl;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
InternalConfig internalConfig;
|
|
|
|
internalConfig.glslOutput = outputGlsl;
|
|
internalConfig.spirvOutput = outputSpirv;
|
|
internalConfig.mslOutput = outputMsl;
|
|
|
|
if (config.shaderType == filament::backend::VERTEX) {
|
|
internalConfig.shLang = EShLangVertex;
|
|
} else {
|
|
internalConfig.shLang = EShLangFragment;
|
|
}
|
|
|
|
TProgram program;
|
|
TShader tShader(internalConfig.shLang);
|
|
|
|
// The cleaner must be declared after the TShader to prevent ASAN failures.
|
|
GLSLangCleaner cleaner;
|
|
|
|
const char* shaderCString = inputShader.c_str();
|
|
tShader.setStrings(&shaderCString, 1);
|
|
|
|
internalConfig.langVersion = GLSLTools::glslangVersionFromShaderModel(config.shaderModel);
|
|
GLSLTools::prepareShaderParser(targetApi, tShader, internalConfig.shLang,
|
|
internalConfig.langVersion, mOptimization);
|
|
EShMessages msg = GLSLTools::glslangFlagsFromTargetApi(targetApi);
|
|
bool ok = tShader.parse(&DefaultTBuiltInResource, internalConfig.langVersion, false, msg);
|
|
if (!ok) {
|
|
utils::slog.e << tShader.getInfoLog() << utils::io::endl;
|
|
return false;
|
|
}
|
|
|
|
program.addShader(&tShader);
|
|
// Even though we only have a single shader stage, linking is still necessary to finalize
|
|
// SPIR-V types
|
|
bool linkOk = program.link(msg);
|
|
if (!linkOk) {
|
|
utils::slog.e << tShader.getInfoLog() << utils::io::endl;
|
|
return false;
|
|
}
|
|
|
|
switch (mOptimization) {
|
|
case MaterialBuilder::Optimization::NONE:
|
|
if (internalConfig.spirvOutput) {
|
|
SpvOptions options;
|
|
options.generateDebugInfo = mGenerateDebugInfo;
|
|
GlslangToSpv(*program.getIntermediate(internalConfig.shLang),
|
|
*internalConfig.spirvOutput, &options);
|
|
if (internalConfig.mslOutput) {
|
|
spirvToToMsl(internalConfig.spirvOutput, internalConfig.mslOutput, config,
|
|
internalConfig.minifier);
|
|
}
|
|
} else {
|
|
utils::slog.e << "GLSL post-processor invoked with optimization level NONE"
|
|
<< utils::io::endl;
|
|
}
|
|
break;
|
|
case MaterialBuilder::Optimization::PREPROCESSOR:
|
|
preprocessOptimization(tShader, config, internalConfig);
|
|
break;
|
|
case MaterialBuilder::Optimization::SIZE:
|
|
case MaterialBuilder::Optimization::PERFORMANCE:
|
|
fullOptimization(tShader, config, internalConfig);
|
|
break;
|
|
}
|
|
|
|
if (internalConfig.glslOutput) {
|
|
*internalConfig.glslOutput =
|
|
internalConfig.minifier.removeWhitespace(*internalConfig.glslOutput);
|
|
|
|
// In theory this should only be enabled for SIZE, but in practice we often use PERFORMANCE.
|
|
if (mOptimization != MaterialBuilder::Optimization::NONE) {
|
|
*internalConfig.glslOutput =
|
|
internalConfig.minifier.renameStructFields(*internalConfig.glslOutput);
|
|
}
|
|
|
|
if (mPrintShaders) {
|
|
utils::slog.i << *internalConfig.glslOutput << utils::io::endl;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void GLSLPostProcessor::preprocessOptimization(glslang::TShader& tShader,
|
|
GLSLPostProcessor::Config const& config, InternalConfig& internalConfig) const {
|
|
|
|
using TargetApi = MaterialBuilder::TargetApi;
|
|
|
|
std::string glsl;
|
|
TShader::ForbidIncluder forbidIncluder;
|
|
|
|
int version = GLSLTools::glslangVersionFromShaderModel(config.shaderModel);
|
|
const TargetApi targetApi = internalConfig.spirvOutput ? TargetApi::VULKAN : TargetApi::OPENGL;
|
|
EShMessages msg = GLSLTools::glslangFlagsFromTargetApi(targetApi);
|
|
bool ok = tShader.preprocess(&DefaultTBuiltInResource, version, ENoProfile, false, false,
|
|
msg, &glsl, forbidIncluder);
|
|
|
|
if (!ok) {
|
|
utils::slog.e << tShader.getInfoLog() << utils::io::endl;
|
|
}
|
|
|
|
if (internalConfig.spirvOutput) {
|
|
TProgram program;
|
|
TShader spirvShader(internalConfig.shLang);
|
|
|
|
// The cleaner must be declared after the TShader/TProgram which are setting the current
|
|
// pool in the tls
|
|
GLSLangCleaner cleaner;
|
|
|
|
const char* shaderCString = glsl.c_str();
|
|
spirvShader.setStrings(&shaderCString, 1);
|
|
GLSLTools::prepareShaderParser(targetApi, spirvShader,
|
|
internalConfig.shLang, internalConfig.langVersion, mOptimization);
|
|
ok = spirvShader.parse(&DefaultTBuiltInResource, internalConfig.langVersion, false, msg);
|
|
program.addShader(&spirvShader);
|
|
// Even though we only have a single shader stage, linking is still necessary to finalize
|
|
// SPIR-V types
|
|
bool linkOk = program.link(msg);
|
|
if (!ok || !linkOk) {
|
|
utils::slog.e << spirvShader.getInfoLog() << utils::io::endl;
|
|
} else {
|
|
SpvOptions options;
|
|
options.generateDebugInfo = mGenerateDebugInfo;
|
|
GlslangToSpv(*program.getIntermediate(internalConfig.shLang),
|
|
*internalConfig.spirvOutput, &options);
|
|
}
|
|
}
|
|
|
|
if (internalConfig.mslOutput) {
|
|
spirvToToMsl(internalConfig.spirvOutput, internalConfig.mslOutput, config,
|
|
internalConfig.minifier);
|
|
}
|
|
|
|
if (internalConfig.glslOutput) {
|
|
*internalConfig.glslOutput = glsl;
|
|
}
|
|
}
|
|
|
|
void GLSLPostProcessor::fullOptimization(const TShader& tShader,
|
|
GLSLPostProcessor::Config const& config, InternalConfig& internalConfig) const {
|
|
SpirvBlob spirv;
|
|
|
|
// Compile GLSL to to SPIR-V
|
|
SpvOptions options;
|
|
options.generateDebugInfo = mGenerateDebugInfo;
|
|
GlslangToSpv(*tShader.getIntermediate(), spirv, &options);
|
|
|
|
// Run the SPIR-V optimizer
|
|
OptimizerPtr optimizer = createOptimizer(mOptimization, config);
|
|
optimizeSpirv(optimizer, spirv);
|
|
|
|
if (internalConfig.spirvOutput) {
|
|
*internalConfig.spirvOutput = spirv;
|
|
}
|
|
|
|
if (internalConfig.mslOutput) {
|
|
spirvToToMsl(&spirv, internalConfig.mslOutput, config, internalConfig.minifier);
|
|
}
|
|
|
|
// Transpile back to GLSL
|
|
if (internalConfig.glslOutput) {
|
|
CompilerGLSL::Options glslOptions;
|
|
glslOptions.es = config.shaderModel == filament::backend::ShaderModel::GL_ES_30;
|
|
glslOptions.version = shaderVersionFromModel(config.shaderModel);
|
|
glslOptions.enable_420pack_extension = glslOptions.version >= 420;
|
|
glslOptions.fragment.default_float_precision = glslOptions.es ?
|
|
CompilerGLSL::Options::Precision::Mediump : CompilerGLSL::Options::Precision::Highp;
|
|
glslOptions.fragment.default_int_precision = glslOptions.es ?
|
|
CompilerGLSL::Options::Precision::Mediump : CompilerGLSL::Options::Precision::Highp;
|
|
|
|
CompilerGLSL glslCompiler(move(spirv));
|
|
glslCompiler.set_common_options(glslOptions);
|
|
|
|
if (tShader.getStage() == EShLangFragment && glslOptions.es) {
|
|
for (auto i : config.glsl.subpassInputToColorLocation) {
|
|
glslCompiler.remap_ext_framebuffer_fetch(i.first, i.second);
|
|
}
|
|
}
|
|
|
|
*internalConfig.glslOutput = glslCompiler.compile();
|
|
}
|
|
}
|
|
|
|
std::shared_ptr<spvtools::Optimizer> GLSLPostProcessor::createOptimizer(
|
|
MaterialBuilder::Optimization optimization, Config const& config) {
|
|
auto optimizer = std::make_shared<spvtools::Optimizer>(SPV_ENV_UNIVERSAL_1_0);
|
|
|
|
optimizer->SetMessageConsumer([](spv_message_level_t level,
|
|
const char* source, const spv_position_t& position, const char* message) {
|
|
if (!filterSpvOptimizerMessage(level)) {
|
|
return;
|
|
}
|
|
utils::slog.e << stringifySpvOptimizerMessage(level, source, position, message)
|
|
<< utils::io::endl;
|
|
});
|
|
|
|
if (optimization == MaterialBuilder::Optimization::SIZE) {
|
|
registerSizePasses(*optimizer, config);
|
|
} else if (optimization == MaterialBuilder::Optimization::PERFORMANCE) {
|
|
registerPerformancePasses(*optimizer, config);
|
|
}
|
|
|
|
return optimizer;
|
|
}
|
|
|
|
void GLSLPostProcessor::optimizeSpirv(OptimizerPtr optimizer, SpirvBlob& spirv) const {
|
|
if (!optimizer->Run(spirv.data(), spirv.size(), &spirv)) {
|
|
utils::slog.e << "SPIR-V optimizer pass failed" << utils::io::endl;
|
|
return;
|
|
}
|
|
|
|
// Remove dead module-level objects: functions, types, vars
|
|
spv::spirvbin_t remapper(0);
|
|
remapper.remap(spirv, spv::spirvbin_base_t::DCE_ALL);
|
|
}
|
|
|
|
void GLSLPostProcessor::registerPerformancePasses(Optimizer& optimizer, Config const& config) {
|
|
optimizer
|
|
.RegisterPass(CreateWrapOpKillPass())
|
|
.RegisterPass(CreateDeadBranchElimPass());
|
|
|
|
if (config.shaderModel != filament::backend::ShaderModel::GL_CORE_41) {
|
|
// this triggers a segfault with AMD drivers on MacOS
|
|
optimizer.RegisterPass(CreateMergeReturnPass());
|
|
}
|
|
|
|
optimizer
|
|
.RegisterPass(CreateInlineExhaustivePass())
|
|
.RegisterPass(CreateAggressiveDCEPass())
|
|
.RegisterPass(CreatePrivateToLocalPass())
|
|
.RegisterPass(CreateLocalSingleBlockLoadStoreElimPass())
|
|
.RegisterPass(CreateLocalSingleStoreElimPass())
|
|
.RegisterPass(CreateAggressiveDCEPass())
|
|
.RegisterPass(CreateScalarReplacementPass())
|
|
.RegisterPass(CreateLocalAccessChainConvertPass())
|
|
.RegisterPass(CreateLocalSingleBlockLoadStoreElimPass())
|
|
.RegisterPass(CreateLocalSingleStoreElimPass())
|
|
.RegisterPass(CreateAggressiveDCEPass())
|
|
.RegisterPass(CreateLocalMultiStoreElimPass())
|
|
.RegisterPass(CreateAggressiveDCEPass())
|
|
.RegisterPass(CreateCCPPass())
|
|
.RegisterPass(CreateAggressiveDCEPass())
|
|
.RegisterPass(CreateRedundancyEliminationPass())
|
|
.RegisterPass(CreateCombineAccessChainsPass())
|
|
.RegisterPass(CreateSimplificationPass())
|
|
.RegisterPass(CreateVectorDCEPass())
|
|
.RegisterPass(CreateDeadInsertElimPass())
|
|
.RegisterPass(CreateDeadBranchElimPass())
|
|
.RegisterPass(CreateSimplificationPass())
|
|
.RegisterPass(CreateIfConversionPass())
|
|
.RegisterPass(CreateCopyPropagateArraysPass())
|
|
.RegisterPass(CreateReduceLoadSizePass())
|
|
.RegisterPass(CreateAggressiveDCEPass())
|
|
.RegisterPass(CreateBlockMergePass())
|
|
.RegisterPass(CreateRedundancyEliminationPass())
|
|
.RegisterPass(CreateDeadBranchElimPass())
|
|
.RegisterPass(CreateBlockMergePass())
|
|
.RegisterPass(CreateSimplificationPass());
|
|
}
|
|
|
|
void GLSLPostProcessor::registerSizePasses(Optimizer& optimizer, Config const& config) {
|
|
optimizer
|
|
.RegisterPass(CreateWrapOpKillPass())
|
|
.RegisterPass(CreateDeadBranchElimPass());
|
|
|
|
if (config.shaderModel != filament::backend::ShaderModel::GL_CORE_41) {
|
|
// this triggers a segfault with AMD drivers on MacOS
|
|
optimizer.RegisterPass(CreateMergeReturnPass());
|
|
}
|
|
|
|
optimizer
|
|
.RegisterPass(CreateInlineExhaustivePass())
|
|
.RegisterPass(CreateEliminateDeadFunctionsPass())
|
|
.RegisterPass(CreatePrivateToLocalPass())
|
|
.RegisterPass(CreateScalarReplacementPass(0))
|
|
.RegisterPass(CreateLocalMultiStoreElimPass())
|
|
.RegisterPass(CreateCCPPass())
|
|
.RegisterPass(CreateLoopUnrollPass(true))
|
|
.RegisterPass(CreateDeadBranchElimPass())
|
|
.RegisterPass(CreateSimplificationPass())
|
|
.RegisterPass(CreateScalarReplacementPass(0))
|
|
.RegisterPass(CreateLocalSingleStoreElimPass())
|
|
.RegisterPass(CreateIfConversionPass())
|
|
.RegisterPass(CreateSimplificationPass())
|
|
.RegisterPass(CreateAggressiveDCEPass())
|
|
.RegisterPass(CreateDeadBranchElimPass())
|
|
.RegisterPass(CreateBlockMergePass())
|
|
.RegisterPass(CreateLocalAccessChainConvertPass())
|
|
.RegisterPass(CreateLocalSingleBlockLoadStoreElimPass())
|
|
.RegisterPass(CreateAggressiveDCEPass())
|
|
.RegisterPass(CreateCopyPropagateArraysPass())
|
|
.RegisterPass(CreateVectorDCEPass())
|
|
.RegisterPass(CreateDeadInsertElimPass())
|
|
// this breaks UBO layout
|
|
//.RegisterPass(CreateEliminateDeadMembersPass())
|
|
.RegisterPass(CreateLocalSingleStoreElimPass())
|
|
.RegisterPass(CreateBlockMergePass())
|
|
.RegisterPass(CreateLocalMultiStoreElimPass())
|
|
.RegisterPass(CreateRedundancyEliminationPass())
|
|
.RegisterPass(CreateSimplificationPass())
|
|
.RegisterPass(CreateAggressiveDCEPass())
|
|
.RegisterPass(CreateCFGCleanupPass());
|
|
}
|
|
|
|
} // namespace filamat
|