blob: c8fe2f389040496cb41a47ccd5853d674c101673 [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 <array>
#include <filesystem>
#include <memory>
#include <sstream>
#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/types.h"
namespace impeller {
namespace compiler {
const uint32_t kFragBindingBase = 128;
const size_t kNumUniformKinds =
static_cast<int>(shaderc_uniform_kind::shaderc_uniform_kind_buffer) + 1;
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 = 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:
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:
case TargetPlatform::kRuntimeStageGLES:
case TargetPlatform::kVulkan:
compiler = CreateMSLCompiler(ir, source_options);
break;
case TargetPlatform::kUnknown:
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) &&
source_options.source_language == SourceLanguage::kGLSL) {
backend->rename_entry_point("main", source_options.entry_point_name,
ToExecutionModel(source_options.type));
}
return compiler;
}
static void SetLimitations(shaderc::CompileOptions& compiler_opts) {
using Limit = std::pair<shaderc_limit, int>;
static constexpr std::array<Limit, 83> limits = {
Limit{shaderc_limit::shaderc_limit_max_lights, 8},
Limit{shaderc_limit::shaderc_limit_max_clip_planes, 6},
Limit{shaderc_limit::shaderc_limit_max_texture_units, 2},
Limit{shaderc_limit::shaderc_limit_max_texture_coords, 8},
Limit{shaderc_limit::shaderc_limit_max_vertex_attribs, 16},
Limit{shaderc_limit::shaderc_limit_max_vertex_uniform_components, 4096},
Limit{shaderc_limit::shaderc_limit_max_varying_floats, 60},
Limit{shaderc_limit::shaderc_limit_max_vertex_texture_image_units, 16},
Limit{shaderc_limit::shaderc_limit_max_combined_texture_image_units, 80},
Limit{shaderc_limit::shaderc_limit_max_texture_image_units, 16},
Limit{shaderc_limit::shaderc_limit_max_fragment_uniform_components, 1024},
Limit{shaderc_limit::shaderc_limit_max_draw_buffers, 8},
Limit{shaderc_limit::shaderc_limit_max_vertex_uniform_vectors, 256},
Limit{shaderc_limit::shaderc_limit_max_varying_vectors, 15},
Limit{shaderc_limit::shaderc_limit_max_fragment_uniform_vectors, 256},
Limit{shaderc_limit::shaderc_limit_max_vertex_output_vectors, 16},
Limit{shaderc_limit::shaderc_limit_max_fragment_input_vectors, 15},
Limit{shaderc_limit::shaderc_limit_min_program_texel_offset, -8},
Limit{shaderc_limit::shaderc_limit_max_program_texel_offset, 7},
Limit{shaderc_limit::shaderc_limit_max_clip_distances, 8},
Limit{shaderc_limit::shaderc_limit_max_compute_work_group_count_x, 65535},
Limit{shaderc_limit::shaderc_limit_max_compute_work_group_count_y, 65535},
Limit{shaderc_limit::shaderc_limit_max_compute_work_group_count_z, 65535},
Limit{shaderc_limit::shaderc_limit_max_compute_work_group_size_x, 1024},
Limit{shaderc_limit::shaderc_limit_max_compute_work_group_size_y, 1024},
Limit{shaderc_limit::shaderc_limit_max_compute_work_group_size_z, 64},
Limit{shaderc_limit::shaderc_limit_max_compute_uniform_components, 512},
Limit{shaderc_limit::shaderc_limit_max_compute_texture_image_units, 16},
Limit{shaderc_limit::shaderc_limit_max_compute_image_uniforms, 8},
Limit{shaderc_limit::shaderc_limit_max_compute_atomic_counters, 8},
Limit{shaderc_limit::shaderc_limit_max_compute_atomic_counter_buffers, 1},
Limit{shaderc_limit::shaderc_limit_max_varying_components, 60},
Limit{shaderc_limit::shaderc_limit_max_vertex_output_components, 64},
Limit{shaderc_limit::shaderc_limit_max_geometry_input_components, 64},
Limit{shaderc_limit::shaderc_limit_max_geometry_output_components, 128},
Limit{shaderc_limit::shaderc_limit_max_fragment_input_components, 128},
Limit{shaderc_limit::shaderc_limit_max_image_units, 8},
Limit{shaderc_limit::
shaderc_limit_max_combined_image_units_and_fragment_outputs,
8},
Limit{shaderc_limit::shaderc_limit_max_combined_shader_output_resources,
8},
Limit{shaderc_limit::shaderc_limit_max_image_samples, 0},
Limit{shaderc_limit::shaderc_limit_max_vertex_image_uniforms, 0},
Limit{shaderc_limit::shaderc_limit_max_tess_control_image_uniforms, 0},
Limit{shaderc_limit::shaderc_limit_max_tess_evaluation_image_uniforms, 0},
Limit{shaderc_limit::shaderc_limit_max_geometry_image_uniforms, 0},
Limit{shaderc_limit::shaderc_limit_max_fragment_image_uniforms, 8},
Limit{shaderc_limit::shaderc_limit_max_combined_image_uniforms, 8},
Limit{shaderc_limit::shaderc_limit_max_geometry_texture_image_units, 16},
Limit{shaderc_limit::shaderc_limit_max_geometry_output_vertices, 256},
Limit{shaderc_limit::shaderc_limit_max_geometry_total_output_components,
1024},
Limit{shaderc_limit::shaderc_limit_max_geometry_uniform_components, 512},
Limit{shaderc_limit::shaderc_limit_max_geometry_varying_components, 60},
Limit{shaderc_limit::shaderc_limit_max_tess_control_input_components,
128},
Limit{shaderc_limit::shaderc_limit_max_tess_control_output_components,
128},
Limit{shaderc_limit::shaderc_limit_max_tess_control_texture_image_units,
16},
Limit{shaderc_limit::shaderc_limit_max_tess_control_uniform_components,
1024},
Limit{
shaderc_limit::shaderc_limit_max_tess_control_total_output_components,
4096},
Limit{shaderc_limit::shaderc_limit_max_tess_evaluation_input_components,
128},
Limit{shaderc_limit::shaderc_limit_max_tess_evaluation_output_components,
128},
Limit{
shaderc_limit::shaderc_limit_max_tess_evaluation_texture_image_units,
16},
Limit{shaderc_limit::shaderc_limit_max_tess_evaluation_uniform_components,
1024},
Limit{shaderc_limit::shaderc_limit_max_tess_patch_components, 120},
Limit{shaderc_limit::shaderc_limit_max_patch_vertices, 32},
Limit{shaderc_limit::shaderc_limit_max_tess_gen_level, 64},
Limit{shaderc_limit::shaderc_limit_max_viewports, 16},
Limit{shaderc_limit::shaderc_limit_max_vertex_atomic_counters, 0},
Limit{shaderc_limit::shaderc_limit_max_tess_control_atomic_counters, 0},
Limit{shaderc_limit::shaderc_limit_max_tess_evaluation_atomic_counters,
0},
Limit{shaderc_limit::shaderc_limit_max_geometry_atomic_counters, 0},
Limit{shaderc_limit::shaderc_limit_max_fragment_atomic_counters, 8},
Limit{shaderc_limit::shaderc_limit_max_combined_atomic_counters, 8},
Limit{shaderc_limit::shaderc_limit_max_atomic_counter_bindings, 1},
Limit{shaderc_limit::shaderc_limit_max_vertex_atomic_counter_buffers, 0},
Limit{
shaderc_limit::shaderc_limit_max_tess_control_atomic_counter_buffers,
0},
Limit{shaderc_limit::
shaderc_limit_max_tess_evaluation_atomic_counter_buffers,
0},
Limit{shaderc_limit::shaderc_limit_max_geometry_atomic_counter_buffers,
0},
Limit{shaderc_limit::shaderc_limit_max_fragment_atomic_counter_buffers,
0},
Limit{shaderc_limit::shaderc_limit_max_combined_atomic_counter_buffers,
1},
Limit{shaderc_limit::shaderc_limit_max_atomic_counter_buffer_size, 32},
Limit{shaderc_limit::shaderc_limit_max_transform_feedback_buffers, 4},
Limit{shaderc_limit::
shaderc_limit_max_transform_feedback_interleaved_components,
64},
Limit{shaderc_limit::shaderc_limit_max_cull_distances, 8},
Limit{shaderc_limit::shaderc_limit_max_combined_clip_and_cull_distances,
8},
Limit{shaderc_limit::shaderc_limit_max_samples, 4},
};
for (auto& [limit, value] : limits) {
compiler_opts.SetLimit(limit, value);
}
}
void Compiler::SetBindingBase(shaderc::CompileOptions& compiler_opts) const {
for (size_t uniform_kind = 0; uniform_kind < kNumUniformKinds;
uniform_kind++) {
compiler_opts.SetBindingBaseForStage(
ToShaderCShaderKind(SourceType::kFragmentShader),
static_cast<shaderc_uniform_kind>(uniform_kind), kFragBindingBase);
}
}
Compiler::Compiler(const fml::Mapping& source_mapping,
const 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();
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.SetSourceLanguage(
shaderc_source_language::shaderc_source_language_glsl);
spirv_options.SetForcedVersionProfile(
460, shaderc_profile::shaderc_profile_core);
break;
case SourceLanguage::kHLSL:
spirv_options.SetSourceLanguage(
shaderc_source_language::shaderc_source_language_hlsl);
break;
case SourceLanguage::kUnknown:
COMPILER_ERROR << "Source language invalid.";
return;
}
SetLimitations(spirv_options);
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);
spirv_options.AddMacroDefinition("IMPELLER_GRAPHICS_BACKEND");
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);
spirv_options.AddMacroDefinition("SKIA_GRAPHICS_BACKEND");
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);
#ifdef IMPELLER_ENABLE_VULKAN
SetBindingBase(spirv_options);
#endif
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 "
<< SourceLanguageToString(options_.source_language)
<< " 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 << SourceLanguageToString(options_.source_language)
<< " 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,
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