// 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 #include #include #include #include #include #include #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 msl_version_override = {}) { auto sl_compiler = std::make_shared(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(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(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(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& 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 included_file_names; spirv_options.includer = std::make_shared( 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(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(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(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 Compiler::GetSPIRVAssembly() const { return spirv_assembly_; } std::shared_ptr 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& Compiler::GetIncludedFileNames() const { return included_file_names_; } static std::string JoinStrings(std::vector 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 dependencies = included_file_names_; dependencies.push_back(options_.file_name); return JoinStrings(dependencies, separator); } std::unique_ptr Compiler::CreateDepfileContents( std::initializer_list 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(stream.str()); return std::make_unique( reinterpret_cast(contents->data()), contents->size(), [contents](auto, auto) {}); } const Reflector* Compiler::GetReflector() const { return reflector_.get(); } } // namespace compiler } // namespace impeller