| // 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 <cstdint> |
| #include <filesystem> |
| #include <memory> |
| #include <optional> |
| #include <sstream> |
| #include <string> |
| #include <utility> |
| |
| #include "flutter/fml/paths.h" |
| #include "impeller/base/allocation.h" |
| #include "impeller/compiler/compiler_backend.h" |
| #include "impeller/compiler/constants.h" |
| #include "impeller/compiler/includer.h" |
| #include "impeller/compiler/logger.h" |
| #include "impeller/compiler/spirv_compiler.h" |
| #include "impeller/compiler/types.h" |
| #include "impeller/compiler/uniform_sorter.h" |
| #include "impeller/compiler/utilities.h" |
| |
| namespace impeller { |
| namespace compiler { |
| |
| static uint32_t ParseMSLVersion(const std::string& msl_version) { |
| std::stringstream sstream(msl_version); |
| std::string version_part; |
| uint32_t major = 1; |
| uint32_t minor = 2; |
| uint32_t patch = 0; |
| if (std::getline(sstream, version_part, '.')) { |
| major = std::stoi(version_part); |
| if (std::getline(sstream, version_part, '.')) { |
| minor = std::stoi(version_part); |
| if (std::getline(sstream, version_part, '.')) { |
| patch = std::stoi(version_part); |
| } |
| } |
| } |
| if (major < 1 || (major == 1 && minor < 2)) { |
| std::cerr << "--metal-version version must be at least 1.2. Have " |
| << msl_version << std::endl; |
| } |
| return spirv_cross::CompilerMSL::Options::make_msl_version(major, minor, |
| patch); |
| } |
| |
| static CompilerBackend CreateMSLCompiler( |
| const spirv_cross::ParsedIR& ir, |
| const SourceOptions& source_options, |
| std::optional<uint32_t> msl_version_override = {}) { |
| auto sl_compiler = std::make_shared<spirv_cross::CompilerMSL>(ir); |
| spirv_cross::CompilerMSL::Options sl_options; |
| sl_options.platform = |
| TargetPlatformToMSLPlatform(source_options.target_platform); |
| sl_options.msl_version = msl_version_override.value_or( |
| ParseMSLVersion(source_options.metal_version)); |
| sl_options.ios_use_simdgroup_functions = |
| sl_options.is_ios() && |
| sl_options.msl_version >= |
| spirv_cross::CompilerMSL::Options::make_msl_version(2, 4, 0); |
| sl_options.use_framebuffer_fetch_subpasses = true; |
| sl_compiler->set_msl_options(sl_options); |
| |
| // Sort the float and sampler uniforms according to their declared/decorated |
| // order. For user authored fragment shaders, the API for setting uniform |
| // values uses the index of the uniform in the declared order. By default, the |
| // metal backend of spirv-cross will order uniforms according to usage. To fix |
| // this, we use the sorted order and the add_msl_resource_binding API to force |
| // the ordering to match the declared order. Note that while this code runs |
| // for all compiled shaders, it will only affect vertex and fragment shaders |
| // due to the specified stage. |
| auto floats = |
| SortUniforms(&ir, sl_compiler.get(), spirv_cross::SPIRType::Float); |
| auto images = |
| SortUniforms(&ir, sl_compiler.get(), spirv_cross::SPIRType::SampledImage); |
| |
| spv::ExecutionModel execution_model = |
| spv::ExecutionModel::ExecutionModelFragment; |
| if (source_options.type == SourceType::kVertexShader) { |
| execution_model = spv::ExecutionModel::ExecutionModelVertex; |
| } |
| |
| uint32_t buffer_offset = 0; |
| uint32_t sampler_offset = 0; |
| for (auto& float_id : floats) { |
| sl_compiler->add_msl_resource_binding( |
| {.stage = execution_model, |
| .basetype = spirv_cross::SPIRType::BaseType::Float, |
| .desc_set = sl_compiler->get_decoration(float_id, |
| spv::DecorationDescriptorSet), |
| .binding = |
| sl_compiler->get_decoration(float_id, spv::DecorationBinding), |
| .count = 1u, |
| .msl_buffer = buffer_offset}); |
| buffer_offset++; |
| } |
| for (auto& image_id : images) { |
| sl_compiler->add_msl_resource_binding({ |
| .stage = execution_model, |
| .basetype = spirv_cross::SPIRType::BaseType::SampledImage, |
| .desc_set = |
| sl_compiler->get_decoration(image_id, spv::DecorationDescriptorSet), |
| .binding = |
| sl_compiler->get_decoration(image_id, spv::DecorationBinding), |
| .count = 1u, |
| // A sampled image is both an image and a sampler, so both |
| // offsets need to be set or depending on the partiular shader |
| // the bindings may be incorrect. |
| .msl_texture = sampler_offset, |
| .msl_sampler = sampler_offset, |
| }); |
| sampler_offset++; |
| } |
| |
| return CompilerBackend(sl_compiler); |
| } |
| |
| static CompilerBackend CreateVulkanCompiler( |
| 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; |
| sl_options.vulkan_semantics = true; |
| gl_compiler->set_common_options(sl_options); |
| return CompilerBackend(gl_compiler); |
| } |
| |
| static CompilerBackend CreateGLSLCompiler(const spirv_cross::ParsedIR& ir, |
| const SourceOptions& source_options) { |
| auto gl_compiler = std::make_shared<spirv_cross::CompilerGLSL>(ir); |
| |
| // Walk the variables and insert the external image extension if any of them |
| // begins with the external texture prefix. Unfortunately, we can't walk |
| // `gl_compiler->get_shader_resources().separate_samplers` until the compiler |
| // is further along. |
| // |
| // Unfortunately, we can't just let the shader author add this extension and |
| // use `samplerExternalOES` directly because compiling to spirv requires the |
| // source language profile to be at least 310 ES, but this extension is |
| // incompatible with ES 310+. |
| for (auto& id : ir.ids_for_constant_or_variable) { |
| if (StringStartsWith(ir.get_name(id), kExternalTexturePrefix)) { |
| gl_compiler->require_extension("GL_OES_EGL_image_external"); |
| break; |
| } |
| } |
| |
| 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 || |
| source_options.target_platform == TargetPlatform::kRuntimeStageGLES) { |
| sl_options.version = source_options.gles_language_version > 0 |
| ? source_options.gles_language_version |
| : 100; |
| sl_options.es = true; |
| if (source_options.require_framebuffer_fetch && |
| source_options.type == SourceType::kFragmentShader) { |
| gl_compiler->remap_ext_framebuffer_fetch(0, 0, true); |
| } |
| gl_compiler->set_variable_type_remap_callback( |
| [&](const spirv_cross::SPIRType& type, const std::string& var_name, |
| std::string& name_of_type) { |
| if (StringStartsWith(var_name, kExternalTexturePrefix)) { |
| name_of_type = "samplerExternalOES"; |
| } |
| }); |
| } else { |
| sl_options.version = source_options.gles_language_version > 0 |
| ? source_options.gles_language_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: |
| case TargetPlatform::kRuntimeStageVulkan: |
| return false; |
| 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: |
| compiler = CreateMSLCompiler(ir, source_options); |
| break; |
| case TargetPlatform::kVulkan: |
| case TargetPlatform::kRuntimeStageVulkan: |
| compiler = CreateVulkanCompiler(ir, source_options); |
| break; |
| case TargetPlatform::kUnknown: |
| case TargetPlatform::kOpenGLES: |
| case TargetPlatform::kOpenGLDesktop: |
| case TargetPlatform::kRuntimeStageGLES: |
| 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) && |
| source_options.source_language == SourceLanguage::kGLSL) { |
| backend->rename_entry_point("main", source_options.entry_point_name, |
| ToExecutionModel(source_options.type)); |
| } |
| return compiler; |
| } |
| |
| Compiler::Compiler(const std::shared_ptr<const fml::Mapping>& source_mapping, |
| const SourceOptions& source_options, |
| Reflector::Options reflector_options) |
| : options_(source_options) { |
| if (!source_mapping || source_mapping->GetMapping() == nullptr) { |
| COMPILER_ERROR(error_stream_) |
| << "Could not read shader source or shader source was empty."; |
| return; |
| } |
| |
| if (source_options.target_platform == TargetPlatform::kUnknown) { |
| COMPILER_ERROR(error_stream_) << "Target platform not specified."; |
| return; |
| } |
| |
| SPIRVCompilerOptions spirv_options; |
| |
| // Make sure reflection is as effective as possible. The generated shaders |
| // will be processed later by backend specific compilers. |
| spirv_options.generate_debug_info = true; |
| |
| switch (options_.source_language) { |
| case SourceLanguage::kGLSL: |
| // Expects GLSL 4.60 (Core Profile). |
| // https://www.khronos.org/registry/OpenGL/specs/gl/GLSLangSpec.4.60.pdf |
| spirv_options.source_langauge = |
| shaderc_source_language::shaderc_source_language_glsl; |
| spirv_options.source_profile = SPIRVCompilerSourceProfile{ |
| shaderc_profile::shaderc_profile_core, // |
| 460, // |
| }; |
| break; |
| case SourceLanguage::kHLSL: |
| spirv_options.source_langauge = |
| shaderc_source_language::shaderc_source_language_hlsl; |
| break; |
| case SourceLanguage::kUnknown: |
| COMPILER_ERROR(error_stream_) << "Source language invalid."; |
| return; |
| } |
| |
| switch (source_options.target_platform) { |
| case TargetPlatform::kMetalDesktop: |
| case TargetPlatform::kMetalIOS: { |
| SPIRVCompilerTargetEnv target; |
| |
| if (source_options.use_half_textures) { |
| target.env = shaderc_target_env::shaderc_target_env_opengl; |
| target.version = shaderc_env_version::shaderc_env_version_opengl_4_5; |
| target.spirv_version = shaderc_spirv_version::shaderc_spirv_version_1_0; |
| } else { |
| target.env = shaderc_target_env::shaderc_target_env_vulkan; |
| target.version = shaderc_env_version::shaderc_env_version_vulkan_1_1; |
| target.spirv_version = shaderc_spirv_version::shaderc_spirv_version_1_3; |
| } |
| |
| spirv_options.target = target; |
| } break; |
| case TargetPlatform::kOpenGLES: |
| case TargetPlatform::kOpenGLDesktop: |
| case TargetPlatform::kVulkan: |
| case TargetPlatform::kRuntimeStageVulkan: { |
| SPIRVCompilerTargetEnv target; |
| |
| target.env = shaderc_target_env::shaderc_target_env_vulkan; |
| target.version = shaderc_env_version::shaderc_env_version_vulkan_1_1; |
| target.spirv_version = shaderc_spirv_version::shaderc_spirv_version_1_3; |
| |
| if (source_options.target_platform == |
| TargetPlatform::kRuntimeStageVulkan) { |
| spirv_options.macro_definitions.push_back("IMPELLER_GRAPHICS_BACKEND"); |
| spirv_options.relaxed_vulkan_rules = true; |
| } |
| spirv_options.target = target; |
| } break; |
| case TargetPlatform::kRuntimeStageMetal: |
| case TargetPlatform::kRuntimeStageGLES: { |
| SPIRVCompilerTargetEnv target; |
| |
| target.env = shaderc_target_env::shaderc_target_env_opengl; |
| target.version = shaderc_env_version::shaderc_env_version_opengl_4_5; |
| target.spirv_version = shaderc_spirv_version::shaderc_spirv_version_1_0; |
| |
| spirv_options.target = target; |
| spirv_options.macro_definitions.push_back("IMPELLER_GRAPHICS_BACKEND"); |
| } break; |
| case TargetPlatform::kSkSL: { |
| SPIRVCompilerTargetEnv target; |
| |
| target.env = shaderc_target_env::shaderc_target_env_opengl; |
| target.version = shaderc_env_version::shaderc_env_version_opengl_4_5; |
| target.spirv_version = shaderc_spirv_version::shaderc_spirv_version_1_0; |
| |
| // 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.optimization_level = |
| shaderc_optimization_level::shaderc_optimization_level_zero; |
| spirv_options.target = target; |
| spirv_options.macro_definitions.push_back("SKIA_GRAPHICS_BACKEND"); |
| } break; |
| case TargetPlatform::kUnknown: |
| COMPILER_ERROR(error_stream_) << "Target platform invalid."; |
| return; |
| } |
| |
| // Implicit definition that indicates that this compilation is for the device |
| // (instead of the host). |
| spirv_options.macro_definitions.push_back("IMPELLER_DEVICE"); |
| for (const auto& define : source_options.defines) { |
| spirv_options.macro_definitions.push_back(define); |
| } |
| |
| std::vector<std::string> included_file_names; |
| spirv_options.includer = std::make_shared<Includer>( |
| options_.working_directory, options_.include_dirs, |
| [&included_file_names](auto included_name) { |
| included_file_names.emplace_back(std::move(included_name)); |
| }); |
| |
| // SPIRV Generation. |
| SPIRVCompiler spv_compiler(source_options, source_mapping); |
| |
| spirv_assembly_ = spv_compiler.CompileToSPV( |
| error_stream_, spirv_options.BuildShadercOptions()); |
| |
| if (!spirv_assembly_) { |
| return; |
| } else { |
| included_file_names_ = std::move(included_file_names); |
| } |
| |
| // SL Generation. |
| spirv_cross::Parser parser( |
| reinterpret_cast<const uint32_t*>(spirv_assembly_->GetMapping()), |
| spirv_assembly_->GetSize() / sizeof(uint32_t)); |
| // 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(error_stream_) |
| << "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. We just need to strip it of debug information. If it isn't, we need |
| // to invoke the appropriate compiler to compile the SPIRV to the target SL. |
| if (source_options.target_platform == TargetPlatform::kVulkan || |
| source_options.target_platform == TargetPlatform::kRuntimeStageVulkan) { |
| auto stripped_spirv_options = spirv_options; |
| stripped_spirv_options.generate_debug_info = false; |
| sl_mapping_ = spv_compiler.CompileToSPV( |
| error_stream_, stripped_spirv_options.BuildShadercOptions()); |
| } else { |
| sl_mapping_ = sl_compilation_result; |
| } |
| |
| if (!sl_mapping_) { |
| COMPILER_ERROR(error_stream_) << "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(error_stream_) |
| << "Could not complete reflection on generated shader."; |
| return; |
| } |
| |
| is_valid_ = true; |
| } |
| |
| Compiler::~Compiler() = default; |
| |
| std::shared_ptr<fml::Mapping> Compiler::GetSPIRVAssembly() const { |
| return spirv_assembly_; |
| } |
| |
| 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, |
| const 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(const 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 { |
| // https://github.com/ninja-build/ninja/blob/master/src/depfile_parser.cc#L28 |
| 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 |