| // Copyright 2013 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "impeller/compiler/compiler.h" |
| |
| #include <filesystem> |
| #include <memory> |
| #include <sstream> |
| |
| #include "flutter/fml/paths.h" |
| #include "impeller/base/allocation.h" |
| #include "impeller/compiler/compiler_backend.h" |
| #include "impeller/compiler/includer.h" |
| #include "impeller/compiler/logger.h" |
| |
| namespace impeller { |
| namespace compiler { |
| |
| static CompilerBackend CreateMSLCompiler(const spirv_cross::ParsedIR& ir, |
| const SourceOptions& source_options) { |
| auto sl_compiler = std::make_shared<spirv_cross::CompilerMSL>(ir); |
| spirv_cross::CompilerMSL::Options sl_options; |
| sl_options.platform = |
| TargetPlatformToMSLPlatform(source_options.target_platform); |
| // If this version specification changes, the GN rules that process the |
| // Metal to AIR must be updated as well. |
| sl_options.msl_version = |
| spirv_cross::CompilerMSL::Options::make_msl_version(1, 2); |
| sl_compiler->set_msl_options(sl_options); |
| return CompilerBackend(sl_compiler); |
| } |
| |
| static CompilerBackend CreateGLSLCompiler(const spirv_cross::ParsedIR& ir, |
| const SourceOptions& source_options) { |
| auto gl_compiler = std::make_shared<spirv_cross::CompilerGLSL>(ir); |
| spirv_cross::CompilerGLSL::Options sl_options; |
| sl_options.force_zero_initialized_variables = true; |
| sl_options.vertex.fixup_clipspace = true; |
| if (source_options.target_platform == TargetPlatform::kOpenGLES) { |
| sl_options.version = 100; |
| sl_options.es = true; |
| } else { |
| sl_options.version = 120; |
| sl_options.es = false; |
| } |
| gl_compiler->set_common_options(sl_options); |
| return CompilerBackend(gl_compiler); |
| } |
| |
| static CompilerBackend CreateSkSLCompiler(const spirv_cross::ParsedIR& ir, |
| const SourceOptions& source_options) { |
| auto sksl_compiler = std::make_shared<CompilerSkSL>(ir); |
| return CompilerBackend(sksl_compiler); |
| } |
| |
| static bool EntryPointMustBeNamedMain(TargetPlatform platform) { |
| switch (platform) { |
| case TargetPlatform::kUnknown: |
| FML_UNREACHABLE(); |
| case TargetPlatform::kMetalDesktop: |
| case TargetPlatform::kMetalIOS: |
| case TargetPlatform::kVulkan: |
| case TargetPlatform::kRuntimeStageMetal: |
| return false; |
| case TargetPlatform::kFlutterSPIRV: |
| case TargetPlatform::kSkSL: |
| case TargetPlatform::kOpenGLES: |
| case TargetPlatform::kOpenGLDesktop: |
| case TargetPlatform::kRuntimeStageGLES: |
| return true; |
| } |
| FML_UNREACHABLE(); |
| } |
| |
| static CompilerBackend CreateCompiler(const spirv_cross::ParsedIR& ir, |
| const SourceOptions& source_options) { |
| CompilerBackend compiler; |
| switch (source_options.target_platform) { |
| case TargetPlatform::kMetalDesktop: |
| case TargetPlatform::kMetalIOS: |
| case TargetPlatform::kRuntimeStageMetal: |
| case TargetPlatform::kRuntimeStageGLES: |
| case TargetPlatform::kVulkan: |
| compiler = CreateMSLCompiler(ir, source_options); |
| break; |
| case TargetPlatform::kUnknown: |
| case TargetPlatform::kFlutterSPIRV: |
| case TargetPlatform::kOpenGLES: |
| case TargetPlatform::kOpenGLDesktop: |
| compiler = CreateGLSLCompiler(ir, source_options); |
| break; |
| case TargetPlatform::kSkSL: |
| compiler = CreateSkSLCompiler(ir, source_options); |
| } |
| if (!compiler) { |
| return {}; |
| } |
| auto* backend = compiler.GetCompiler(); |
| if (!EntryPointMustBeNamedMain(source_options.target_platform)) { |
| backend->rename_entry_point("main", source_options.entry_point_name, |
| ToExecutionModel(source_options.type)); |
| } |
| return compiler; |
| } |
| |
| Compiler::Compiler(const fml::Mapping& source_mapping, |
| SourceOptions source_options, |
| Reflector::Options reflector_options) |
| : options_(source_options) { |
| if (source_mapping.GetMapping() == nullptr) { |
| COMPILER_ERROR |
| << "Could not read shader source or shader source was empty."; |
| return; |
| } |
| |
| if (source_options.target_platform == TargetPlatform::kUnknown) { |
| COMPILER_ERROR << "Target platform not specified."; |
| return; |
| } |
| |
| auto shader_kind = ToShaderCShaderKind(source_options.type); |
| |
| if (shader_kind == shaderc_shader_kind::shaderc_glsl_infer_from_source) { |
| COMPILER_ERROR << "Could not figure out shader stage."; |
| return; |
| } |
| |
| shaderc::CompileOptions spirv_options; |
| |
| // Make sure reflection is as effective as possible. The generated shaders |
| // will be processed later by backend specific compilers. So optimizations |
| // here are irrelevant and get in the way of generating reflection code. |
| spirv_options.SetGenerateDebugInfo(); |
| |
| // Expects GLSL 4.60 (Core Profile). |
| // https://www.khronos.org/registry/OpenGL/specs/gl/GLSLangSpec.4.60.pdf |
| spirv_options.SetSourceLanguage( |
| shaderc_source_language::shaderc_source_language_glsl); |
| spirv_options.SetForcedVersionProfile(460, |
| shaderc_profile::shaderc_profile_core); |
| switch (source_options.target_platform) { |
| case TargetPlatform::kMetalDesktop: |
| case TargetPlatform::kMetalIOS: |
| case TargetPlatform::kOpenGLES: |
| case TargetPlatform::kOpenGLDesktop: |
| spirv_options.SetOptimizationLevel( |
| shaderc_optimization_level::shaderc_optimization_level_performance); |
| spirv_options.SetTargetEnvironment( |
| shaderc_target_env::shaderc_target_env_vulkan, |
| shaderc_env_version::shaderc_env_version_vulkan_1_1); |
| spirv_options.SetTargetSpirv( |
| shaderc_spirv_version::shaderc_spirv_version_1_3); |
| break; |
| case TargetPlatform::kVulkan: |
| spirv_options.SetOptimizationLevel( |
| shaderc_optimization_level::shaderc_optimization_level_performance); |
| spirv_options.SetTargetEnvironment( |
| shaderc_target_env::shaderc_target_env_vulkan, |
| shaderc_env_version::shaderc_env_version_vulkan_1_0); |
| spirv_options.SetTargetSpirv( |
| shaderc_spirv_version::shaderc_spirv_version_1_0); |
| break; |
| case TargetPlatform::kRuntimeStageMetal: |
| case TargetPlatform::kRuntimeStageGLES: |
| spirv_options.SetOptimizationLevel( |
| shaderc_optimization_level::shaderc_optimization_level_performance); |
| spirv_options.SetTargetEnvironment( |
| shaderc_target_env::shaderc_target_env_opengl, |
| shaderc_env_version::shaderc_env_version_opengl_4_5); |
| spirv_options.SetTargetSpirv( |
| shaderc_spirv_version::shaderc_spirv_version_1_0); |
| break; |
| case TargetPlatform::kFlutterSPIRV: |
| // With any optimization level above 'zero' enabled, shaderc will emit |
| // ops that are not supported by the Engine's SPIR-V -> SkSL transpiler. |
| // In particular, with 'shaderc_optimization_level_size' enabled, it will |
| // generate OpPhi (opcode 245) for test 246_OpLoopMerge.frag instead of |
| // the OpLoopMerge op expected by that test. |
| // See: https://github.com/flutter/flutter/issues/105396. |
| spirv_options.SetOptimizationLevel( |
| shaderc_optimization_level::shaderc_optimization_level_zero); |
| spirv_options.SetTargetEnvironment( |
| shaderc_target_env::shaderc_target_env_opengl, |
| shaderc_env_version::shaderc_env_version_opengl_4_5); |
| spirv_options.SetTargetSpirv( |
| shaderc_spirv_version::shaderc_spirv_version_1_0); |
| break; |
| case TargetPlatform::kSkSL: |
| // When any optimization level above 'zero' is enabled, the phi merges at |
| // loop continue blocks are rendered using syntax that is supported in |
| // GLSL, but not in SkSL. |
| // https://bugs.chromium.org/p/skia/issues/detail?id=13518. |
| spirv_options.SetOptimizationLevel( |
| shaderc_optimization_level::shaderc_optimization_level_zero); |
| spirv_options.SetTargetEnvironment( |
| shaderc_target_env::shaderc_target_env_opengl, |
| shaderc_env_version::shaderc_env_version_opengl_4_5); |
| spirv_options.SetTargetSpirv( |
| shaderc_spirv_version::shaderc_spirv_version_1_0); |
| break; |
| case TargetPlatform::kUnknown: |
| COMPILER_ERROR << "Target platform invalid."; |
| return; |
| } |
| |
| // Implicit definition that indicates that this compilation is for the device |
| // (instead of the host). |
| spirv_options.AddMacroDefinition("IMPELLER_DEVICE"); |
| for (const auto& define : source_options.defines) { |
| spirv_options.AddMacroDefinition(define); |
| } |
| |
| spirv_options.SetAutoBindUniforms(true); |
| spirv_options.SetAutoMapLocations(true); |
| |
| std::vector<std::string> included_file_names; |
| spirv_options.SetIncluder(std::make_unique<Includer>( |
| options_.working_directory, options_.include_dirs, |
| [&included_file_names](auto included_name) { |
| included_file_names.emplace_back(std::move(included_name)); |
| })); |
| |
| shaderc::Compiler spv_compiler; |
| if (!spv_compiler.IsValid()) { |
| COMPILER_ERROR << "Could not initialize the GLSL to SPIRV compiler."; |
| return; |
| } |
| |
| // SPIRV Generation. |
| spv_result_ = std::make_shared<shaderc::SpvCompilationResult>( |
| spv_compiler.CompileGlslToSpv( |
| reinterpret_cast<const char*>( |
| source_mapping.GetMapping()), // source_text |
| source_mapping.GetSize(), // source_text_size |
| shader_kind, // shader_kind |
| source_options.file_name.c_str(), // input_file_name |
| source_options.entry_point_name.c_str(), // entry_point_name |
| spirv_options // options |
| )); |
| if (spv_result_->GetCompilationStatus() != |
| shaderc_compilation_status::shaderc_compilation_status_success) { |
| COMPILER_ERROR << "GLSL to SPIRV failed; " |
| << ShaderCErrorToString(spv_result_->GetCompilationStatus()) |
| << ". " << spv_result_->GetNumErrors() << " error(s) and " |
| << spv_result_->GetNumWarnings() << " warning(s)."; |
| if (spv_result_->GetNumErrors() > 0 || spv_result_->GetNumWarnings() > 0) { |
| COMPILER_ERROR_NO_PREFIX << spv_result_->GetErrorMessage(); |
| } |
| return; |
| } else { |
| included_file_names_ = std::move(included_file_names); |
| } |
| |
| if (!TargetPlatformNeedsSL(source_options.target_platform)) { |
| is_valid_ = true; |
| return; |
| } |
| |
| // SL Generation. |
| spirv_cross::Parser parser(spv_result_->cbegin(), |
| spv_result_->cend() - spv_result_->cbegin()); |
| // The parser and compiler must be run separately because the parser contains |
| // meta information (like type member names) that are useful for reflection. |
| parser.parse(); |
| |
| const auto parsed_ir = |
| std::make_shared<spirv_cross::ParsedIR>(parser.get_parsed_ir()); |
| |
| auto sl_compiler = CreateCompiler(*parsed_ir, options_); |
| |
| if (!sl_compiler) { |
| COMPILER_ERROR << "Could not create compiler for target platform."; |
| return; |
| } |
| |
| // We need to invoke the compiler even if we don't use the SL mapping later |
| // for Vulkan. The reflector needs information that is only valid after a |
| // successful compilation call. |
| auto sl_compilation_result = |
| CreateMappingWithString(sl_compiler.GetCompiler()->compile()); |
| |
| // If the target is Vulkan, our shading language is SPIRV which we already |
| // have. If it isn't, we need to invoke the appropriate compiler to compile |
| // the SPIRV to the target SL. |
| sl_mapping_ = source_options.target_platform == TargetPlatform::kVulkan |
| ? GetSPIRVAssembly() |
| : sl_compilation_result; |
| |
| if (!sl_mapping_) { |
| COMPILER_ERROR << "Could not generate SL from SPIRV"; |
| return; |
| } |
| |
| reflector_ = std::make_unique<Reflector>(std::move(reflector_options), // |
| parsed_ir, // |
| GetSLShaderSource(), // |
| sl_compiler // |
| ); |
| |
| if (!reflector_->IsValid()) { |
| COMPILER_ERROR << "Could not complete reflection on generated shader."; |
| return; |
| } |
| |
| is_valid_ = true; |
| } |
| |
| Compiler::~Compiler() = default; |
| |
| std::unique_ptr<fml::Mapping> Compiler::GetSPIRVAssembly() const { |
| if (!spv_result_) { |
| return nullptr; |
| } |
| const auto data_length = |
| (spv_result_->cend() - spv_result_->cbegin()) * |
| sizeof(decltype(spv_result_)::element_type::element_type); |
| |
| return std::make_unique<fml::NonOwnedMapping>( |
| reinterpret_cast<const uint8_t*>(spv_result_->cbegin()), data_length, |
| [result = spv_result_](auto, auto) mutable { result.reset(); }); |
| } |
| |
| std::shared_ptr<fml::Mapping> Compiler::GetSLShaderSource() const { |
| return sl_mapping_; |
| } |
| |
| bool Compiler::IsValid() const { |
| return is_valid_; |
| } |
| |
| std::string Compiler::GetSourcePrefix() const { |
| std::stringstream stream; |
| stream << options_.file_name << ": "; |
| return stream.str(); |
| } |
| |
| std::string Compiler::GetErrorMessages() const { |
| return error_stream_.str(); |
| } |
| |
| const std::vector<std::string>& Compiler::GetIncludedFileNames() const { |
| return included_file_names_; |
| } |
| |
| static std::string JoinStrings(std::vector<std::string> items, |
| std::string separator) { |
| std::stringstream stream; |
| for (size_t i = 0, count = items.size(); i < count; i++) { |
| const auto is_last = (i == count - 1); |
| |
| stream << items[i]; |
| if (!is_last) { |
| stream << separator; |
| } |
| } |
| return stream.str(); |
| } |
| |
| std::string Compiler::GetDependencyNames(std::string separator) const { |
| std::vector<std::string> dependencies = included_file_names_; |
| dependencies.push_back(options_.file_name); |
| return JoinStrings(dependencies, separator); |
| } |
| |
| std::unique_ptr<fml::Mapping> Compiler::CreateDepfileContents( |
| std::initializer_list<std::string> targets_names) const { |
| const auto targets = JoinStrings(targets_names, " "); |
| const auto dependencies = GetDependencyNames(" "); |
| |
| std::stringstream stream; |
| stream << targets << ": " << dependencies << "\n"; |
| |
| auto contents = std::make_shared<std::string>(stream.str()); |
| return std::make_unique<fml::NonOwnedMapping>( |
| reinterpret_cast<const uint8_t*>(contents->data()), contents->size(), |
| [contents](auto, auto) {}); |
| } |
| |
| const Reflector* Compiler::GetReflector() const { |
| return reflector_.get(); |
| } |
| |
| } // namespace compiler |
| } // namespace impeller |