blob: 27e1f97dc97330666c8fbe11db6234a01804290c [file] [log] [blame]
// 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 <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/includer.h"
#include "impeller/compiler/logger.h"
#include "impeller/compiler/spirv_compiler.h"
#include "impeller/compiler/types.h"
#include "impeller/compiler/uniform_sorter.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 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);
uint32_t buffer_offset = 0;
uint32_t sampler_offset = 0;
for (auto& float_id : floats) {
sl_compiler->add_msl_resource_binding(
{.stage = spv::ExecutionModel::ExecutionModelFragment,
.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 = spv::ExecutionModel::ExecutionModelFragment,
.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) {
// TODO(dnfield): It seems like what we'd want is a CompilerGLSL with
// vulkan_semantics set to true, but that has regressed some things on GLES
// somehow. In the mean time, go back to using CompilerMSL, but set the Metal
// Language version to something really high so that we don't get weird
// complaints about using Metal features while trying to build Vulkan shaders.
// https://github.com/flutter/flutter/issues/123795
return CreateMSLCompiler(
ir, source_options,
spirv_cross::CompilerMSL::Options::make_msl_version(3, 0, 0));
}
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 ||
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;
} 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: {
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;
spirv_options.target = target;
} break;
case TargetPlatform::kRuntimeStageMetal:
case TargetPlatform::kRuntimeStageGLES:
case TargetPlatform::kRuntimeStageVulkan: {
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) {
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