Flutter Impeller
compiler.cc
Go to the documentation of this file.
1 // Copyright 2013 The Flutter Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
6 
7 #include <cstdint>
8 #include <filesystem>
9 #include <memory>
10 #include <optional>
11 #include <sstream>
12 #include <string>
13 #include <utility>
14 
15 #include "flutter/fml/paths.h"
25 #include "third_party/skia/include/core/SkString.h"
26 #include "third_party/skia/include/effects/SkRuntimeEffect.h"
27 
28 namespace impeller {
29 namespace compiler {
30 
31 namespace {
32 constexpr const char* kEGLImageExternalExtension = "GL_OES_EGL_image_external";
33 constexpr const char* kEGLImageExternalExtension300 =
34  "GL_OES_EGL_image_external_essl3";
35 } // namespace
36 
37 // This value should be <= 7372. UBOs can be larger on some devices but a
38 // performance cost will be paid.
39 // https://docs.qualcomm.com/bundle/publicresource/topics/80-78185-2/best_practices.html?product=1601111740035277#buffer-best-practices
40 static const uint32_t kMaxUniformBufferSize = 6208;
41 
42 static uint32_t ParseMSLVersion(const std::string& msl_version) {
43  std::stringstream sstream(msl_version);
44  std::string version_part;
45  uint32_t major = 1;
46  uint32_t minor = 2;
47  uint32_t patch = 0;
48  if (std::getline(sstream, version_part, '.')) {
49  major = std::stoi(version_part);
50  if (std::getline(sstream, version_part, '.')) {
51  minor = std::stoi(version_part);
52  if (std::getline(sstream, version_part, '.')) {
53  patch = std::stoi(version_part);
54  }
55  }
56  }
57  if (major < 1 || (major == 1 && minor < 2)) {
58  std::cerr << "--metal-version version must be at least 1.2. Have "
59  << msl_version << std::endl;
60  }
61  return spirv_cross::CompilerMSL::Options::make_msl_version(major, minor,
62  patch);
63 }
64 
66  const spirv_cross::ParsedIR& ir,
67  const SourceOptions& source_options,
68  std::optional<uint32_t> msl_version_override = {}) {
69  auto sl_compiler = std::make_shared<spirv_cross::CompilerMSL>(ir);
70  spirv_cross::CompilerMSL::Options sl_options;
71  sl_options.platform =
73  sl_options.msl_version = msl_version_override.value_or(
74  ParseMSLVersion(source_options.metal_version));
75  sl_options.ios_use_simdgroup_functions =
76  sl_options.is_ios() &&
77  sl_options.msl_version >=
78  spirv_cross::CompilerMSL::Options::make_msl_version(2, 4, 0);
79  sl_options.use_framebuffer_fetch_subpasses = true;
80  sl_compiler->set_msl_options(sl_options);
81 
82  // Sort the float and sampler uniforms according to their declared/decorated
83  // order. For user authored fragment shaders, the API for setting uniform
84  // values uses the index of the uniform in the declared order. By default, the
85  // metal backend of spirv-cross will order uniforms according to usage. To fix
86  // this, we use the sorted order and the add_msl_resource_binding API to force
87  // the ordering to match the declared order. Note that while this code runs
88  // for all compiled shaders, it will only affect vertex and fragment shaders
89  // due to the specified stage.
90  auto floats =
91  SortUniforms(&ir, sl_compiler.get(), spirv_cross::SPIRType::Float);
92  auto images =
93  SortUniforms(&ir, sl_compiler.get(), spirv_cross::SPIRType::SampledImage);
94 
95  spv::ExecutionModel execution_model =
96  spv::ExecutionModel::ExecutionModelFragment;
97  if (source_options.type == SourceType::kVertexShader) {
98  execution_model = spv::ExecutionModel::ExecutionModelVertex;
99  }
100 
101  uint32_t buffer_offset = 0;
102  uint32_t sampler_offset = 0;
103  for (auto& float_id : floats) {
104  sl_compiler->add_msl_resource_binding(
105  {.stage = execution_model,
106  .basetype = spirv_cross::SPIRType::BaseType::Float,
107  .desc_set = sl_compiler->get_decoration(float_id,
108  spv::DecorationDescriptorSet),
109  .binding =
110  sl_compiler->get_decoration(float_id, spv::DecorationBinding),
111  .count = 1u,
112  .msl_buffer = buffer_offset});
113  buffer_offset++;
114  }
115  for (auto& image_id : images) {
116  sl_compiler->add_msl_resource_binding({
117  .stage = execution_model,
118  .basetype = spirv_cross::SPIRType::BaseType::SampledImage,
119  .desc_set =
120  sl_compiler->get_decoration(image_id, spv::DecorationDescriptorSet),
121  .binding =
122  sl_compiler->get_decoration(image_id, spv::DecorationBinding),
123  .count = 1u,
124  // A sampled image is both an image and a sampler, so both
125  // offsets need to be set or depending on the partiular shader
126  // the bindings may be incorrect.
127  .msl_texture = sampler_offset,
128  .msl_sampler = sampler_offset,
129  });
130  sampler_offset++;
131  }
132 
133  return CompilerBackend(sl_compiler);
134 }
135 
137  const spirv_cross::ParsedIR& ir,
138  const SourceOptions& source_options) {
139  auto gl_compiler = std::make_shared<spirv_cross::CompilerGLSL>(ir);
140  spirv_cross::CompilerGLSL::Options sl_options;
141  sl_options.force_zero_initialized_variables = true;
142  sl_options.vertex.fixup_clipspace = true;
143  sl_options.vulkan_semantics = true;
144  gl_compiler->set_common_options(sl_options);
145  return CompilerBackend(gl_compiler);
146 }
147 
148 static CompilerBackend CreateGLSLCompiler(const spirv_cross::ParsedIR& ir,
149  const SourceOptions& source_options) {
150  auto gl_compiler = std::make_shared<spirv_cross::CompilerGLSL>(ir);
151 
152  // Walk the variables and insert the external image extension if any of them
153  // begins with the external texture prefix. Unfortunately, we can't walk
154  // `gl_compiler->get_shader_resources().separate_samplers` until the compiler
155  // is further along.
156  //
157  // Unfortunately, we can't just let the shader author add this extension and
158  // use `samplerExternalOES` directly because compiling to spirv requires the
159  // source language profile to be at least 310 ES, but this extension is
160  // incompatible with ES 310+.
161  for (auto& id : ir.ids_for_constant_or_variable) {
162  if (StringStartsWith(ir.get_name(id), kExternalTexturePrefix)) {
163  if (source_options.gles_language_version >= 300) {
164  gl_compiler->require_extension(kEGLImageExternalExtension300);
165  } else {
166  gl_compiler->require_extension(kEGLImageExternalExtension);
167  }
168  break;
169  }
170  }
171 
172  spirv_cross::CompilerGLSL::Options sl_options;
173  sl_options.force_zero_initialized_variables = true;
174  sl_options.vertex.fixup_clipspace = true;
175  if (source_options.target_platform == TargetPlatform::kOpenGLES ||
178  sl_options.version = source_options.gles_language_version > 0
179  ? source_options.gles_language_version
180  : 100;
181  sl_options.es = true;
182  if (source_options.target_platform == TargetPlatform::kRuntimeStageGLES3) {
183  sl_options.version = 300;
184  }
185  if (source_options.require_framebuffer_fetch &&
186  source_options.type == SourceType::kFragmentShader) {
187  gl_compiler->remap_ext_framebuffer_fetch(0, 0, true);
188  }
189  gl_compiler->set_variable_type_remap_callback(
190  [&](const spirv_cross::SPIRType& type, const std::string& var_name,
191  std::string& name_of_type) {
192  if (StringStartsWith(var_name, kExternalTexturePrefix)) {
193  name_of_type = "samplerExternalOES";
194  }
195  });
196  } else {
197  sl_options.version = source_options.gles_language_version > 0
198  ? source_options.gles_language_version
199  : 120;
200  sl_options.es = false;
201  }
202  gl_compiler->set_common_options(sl_options);
203  return CompilerBackend(gl_compiler);
204 }
205 
206 static CompilerBackend CreateSkSLCompiler(const spirv_cross::ParsedIR& ir,
207  const SourceOptions& source_options) {
208  auto sksl_compiler = std::make_shared<CompilerSkSL>(ir);
209  return CompilerBackend(sksl_compiler);
210 }
211 
213  switch (platform) {
215  FML_UNREACHABLE();
221  return false;
227  return true;
228  }
229  FML_UNREACHABLE();
230 }
231 
232 static CompilerBackend CreateCompiler(const spirv_cross::ParsedIR& ir,
233  const SourceOptions& source_options) {
234  CompilerBackend compiler;
235  switch (source_options.target_platform) {
239  compiler = CreateMSLCompiler(ir, source_options);
240  break;
243  compiler = CreateVulkanCompiler(ir, source_options);
244  break;
250  compiler = CreateGLSLCompiler(ir, source_options);
251  break;
253  compiler = CreateSkSLCompiler(ir, source_options);
254  }
255  if (!compiler) {
256  return {};
257  }
258  auto* backend = compiler.GetCompiler();
259  if (!EntryPointMustBeNamedMain(source_options.target_platform) &&
260  source_options.source_language == SourceLanguage::kGLSL) {
261  backend->rename_entry_point("main", source_options.entry_point_name,
262  ToExecutionModel(source_options.type));
263  }
264  return compiler;
265 }
266 
267 namespace {
268 uint32_t CalculateUBOSize(const spirv_cross::Compiler* compiler) {
269  spirv_cross::ShaderResources resources = compiler->get_shader_resources();
270  uint32_t result = 0;
271  for (const spirv_cross::Resource& ubo : resources.uniform_buffers) {
272  const spirv_cross::SPIRType& ubo_type =
273  compiler->get_type(ubo.base_type_id);
274  uint32_t size = compiler->get_declared_struct_size(ubo_type);
275  result += size;
276  }
277  return result;
278 }
279 
280 } // namespace
281 
282 Compiler::Compiler(const std::shared_ptr<const fml::Mapping>& source_mapping,
283  const SourceOptions& source_options,
284  Reflector::Options reflector_options)
285  : options_(source_options) {
286  if (!source_mapping || source_mapping->GetMapping() == nullptr) {
287  COMPILER_ERROR(error_stream_)
288  << "Could not read shader source or shader source was empty.";
289  return;
290  }
291 
292  if (source_options.target_platform == TargetPlatform::kUnknown) {
293  COMPILER_ERROR(error_stream_) << "Target platform not specified.";
294  return;
295  }
296 
297  SPIRVCompilerOptions spirv_options;
298 
299  // Make sure reflection is as effective as possible. The generated shaders
300  // will be processed later by backend specific compilers.
301  spirv_options.generate_debug_info = true;
302 
303  switch (options_.source_language) {
305  // Expects GLSL 4.60 (Core Profile).
306  // https://www.khronos.org/registry/OpenGL/specs/gl/GLSLangSpec.4.60.pdf
307  spirv_options.source_langauge =
308  shaderc_source_language::shaderc_source_language_glsl;
310  shaderc_profile::shaderc_profile_core, //
311  460, //
312  };
313  break;
315  spirv_options.source_langauge =
316  shaderc_source_language::shaderc_source_language_hlsl;
317  break;
319  COMPILER_ERROR(error_stream_) << "Source language invalid.";
320  return;
321  }
322 
323  switch (source_options.target_platform) {
326  SPIRVCompilerTargetEnv target;
327 
328  if (source_options.use_half_textures) {
329  target.env = shaderc_target_env::shaderc_target_env_opengl;
330  target.version = shaderc_env_version::shaderc_env_version_opengl_4_5;
331  target.spirv_version = shaderc_spirv_version::shaderc_spirv_version_1_0;
332  } else {
333  target.env = shaderc_target_env::shaderc_target_env_vulkan;
334  target.version = shaderc_env_version::shaderc_env_version_vulkan_1_1;
335  target.spirv_version = shaderc_spirv_version::shaderc_spirv_version_1_3;
336  }
337 
338  spirv_options.target = target;
339  } break;
344  SPIRVCompilerTargetEnv target;
345 
346  target.env = shaderc_target_env::shaderc_target_env_vulkan;
347  target.version = shaderc_env_version::shaderc_env_version_vulkan_1_1;
348  target.spirv_version = shaderc_spirv_version::shaderc_spirv_version_1_3;
349 
350  if (source_options.target_platform ==
352  spirv_options.macro_definitions.push_back("IMPELLER_GRAPHICS_BACKEND");
353  spirv_options.relaxed_vulkan_rules = true;
354  }
355  spirv_options.target = target;
356  } break;
360  SPIRVCompilerTargetEnv target;
361 
362  target.env = shaderc_target_env::shaderc_target_env_opengl;
363  target.version = shaderc_env_version::shaderc_env_version_opengl_4_5;
364  target.spirv_version = shaderc_spirv_version::shaderc_spirv_version_1_0;
365 
366  spirv_options.target = target;
367  spirv_options.macro_definitions.push_back("IMPELLER_GRAPHICS_BACKEND");
368  if (source_options.target_platform == TargetPlatform::kRuntimeStageGLES ||
369  source_options.target_platform ==
371  spirv_options.macro_definitions.push_back("IMPELLER_TARGET_OPENGLES");
372  }
373  } break;
374  case TargetPlatform::kSkSL: {
375  SPIRVCompilerTargetEnv target;
376 
377  target.env = shaderc_target_env::shaderc_target_env_opengl;
378  target.version = shaderc_env_version::shaderc_env_version_opengl_4_5;
379  target.spirv_version = shaderc_spirv_version::shaderc_spirv_version_1_0;
380 
381  // When any optimization level above 'zero' is enabled, the phi merges at
382  // loop continue blocks are rendered using syntax that is supported in
383  // GLSL, but not in SkSL.
384  // https://bugs.chromium.org/p/skia/issues/detail?id=13518.
385  spirv_options.optimization_level =
386  shaderc_optimization_level::shaderc_optimization_level_zero;
387  spirv_options.target = target;
388  spirv_options.macro_definitions.push_back("SKIA_GRAPHICS_BACKEND");
389  } break;
391  COMPILER_ERROR(error_stream_) << "Target platform invalid.";
392  return;
393  }
394 
395  // Implicit definition that indicates that this compilation is for the device
396  // (instead of the host).
397  spirv_options.macro_definitions.push_back("IMPELLER_DEVICE");
398  for (const auto& define : source_options.defines) {
399  spirv_options.macro_definitions.push_back(define);
400  }
401 
402  std::vector<std::string> included_file_names;
403  spirv_options.includer = std::make_shared<Includer>(
404  options_.working_directory, options_.include_dirs,
405  [&included_file_names](auto included_name) {
406  included_file_names.emplace_back(std::move(included_name));
407  });
408 
409  // SPIRV Generation.
410  SPIRVCompiler spv_compiler(source_options, source_mapping);
411 
412  spirv_assembly_ = spv_compiler.CompileToSPV(
413  error_stream_, spirv_options.BuildShadercOptions());
414 
415  if (!spirv_assembly_) {
416  return;
417  } else {
418  included_file_names_ = std::move(included_file_names);
419  }
420 
421  // SL Generation.
422  spirv_cross::Parser parser(
423  reinterpret_cast<const uint32_t*>(spirv_assembly_->GetMapping()),
424  spirv_assembly_->GetSize() / sizeof(uint32_t));
425  // The parser and compiler must be run separately because the parser contains
426  // meta information (like type member names) that are useful for reflection.
427  parser.parse();
428 
429  const auto parsed_ir =
430  std::make_shared<spirv_cross::ParsedIR>(parser.get_parsed_ir());
431 
432  auto sl_compiler = CreateCompiler(*parsed_ir, options_);
433 
434  if (!sl_compiler) {
435  COMPILER_ERROR(error_stream_)
436  << "Could not create compiler for target platform.";
437  return;
438  }
439 
440  uint32_t ubo_size = CalculateUBOSize(sl_compiler.GetCompiler());
441  if (ubo_size > kMaxUniformBufferSize) {
442  COMPILER_ERROR(error_stream_) << "Uniform buffer size exceeds max ("
443  << kMaxUniformBufferSize << "): " << ubo_size;
444  return;
445  }
446 
447  // We need to invoke the compiler even if we don't use the SL mapping later
448  // for Vulkan. The reflector needs information that is only valid after a
449  // successful compilation call.
450  auto sl_compilation_result_str = sl_compiler.GetCompiler()->compile();
451  auto sl_compilation_result =
452  CreateMappingWithString(sl_compilation_result_str);
453 
454  // If the target is Vulkan, our shading language is SPIRV which we already
455  // have. We just need to strip it of debug information. If it isn't, we need
456  // to invoke the appropriate compiler to compile the SPIRV to the target SL.
457  if (source_options.target_platform == TargetPlatform::kVulkan ||
459  auto stripped_spirv_options = spirv_options;
460  stripped_spirv_options.generate_debug_info = false;
461  sl_mapping_ = spv_compiler.CompileToSPV(
462  error_stream_, stripped_spirv_options.BuildShadercOptions());
463  } else {
464  sl_mapping_ = sl_compilation_result;
465  }
466 
467  if (!sl_mapping_) {
468  COMPILER_ERROR(error_stream_) << "Could not generate SL from SPIRV";
469  return;
470  }
471 
472  if (sl_compiler.GetType() == CompilerBackend::Type::kSkSL) {
473  // Validate compiled SkSL by trying to create a SkRuntimeEffect.
474  SkRuntimeEffect::Result result =
475  SkRuntimeEffect::MakeForShader(SkString(sl_compilation_result_str));
476  if (result.effect == nullptr) {
477  COMPILER_ERROR(error_stream_)
478  << "Compiled to invalid SkSL:\n"
479  << sl_compilation_result_str << "\nSkSL Error:\n"
480  << result.errorText.c_str();
481  return;
482  }
483  }
484 
485  reflector_ = std::make_unique<Reflector>(std::move(reflector_options), //
486  parsed_ir, //
487  GetSLShaderSource(), //
488  sl_compiler //
489  );
490 
491  if (!reflector_->IsValid()) {
492  COMPILER_ERROR(error_stream_)
493  << "Could not complete reflection on generated shader.";
494  return;
495  }
496 
497  is_valid_ = true;
498 }
499 
500 Compiler::~Compiler() = default;
501 
502 std::shared_ptr<fml::Mapping> Compiler::GetSPIRVAssembly() const {
503  return spirv_assembly_;
504 }
505 
506 std::shared_ptr<fml::Mapping> Compiler::GetSLShaderSource() const {
507  return sl_mapping_;
508 }
509 
510 bool Compiler::IsValid() const {
511  return is_valid_;
512 }
513 
514 std::string Compiler::GetSourcePrefix() const {
515  std::stringstream stream;
516  stream << options_.file_name << ": ";
517  return stream.str();
518 }
519 
520 std::string Compiler::GetErrorMessages() const {
521  return error_stream_.str();
522 }
523 
524 const std::vector<std::string>& Compiler::GetIncludedFileNames() const {
525  return included_file_names_;
526 }
527 
528 static std::string JoinStrings(std::vector<std::string> items,
529  const std::string& separator) {
530  std::stringstream stream;
531  for (size_t i = 0, count = items.size(); i < count; i++) {
532  const auto is_last = (i == count - 1);
533 
534  stream << items[i];
535  if (!is_last) {
536  stream << separator;
537  }
538  }
539  return stream.str();
540 }
541 
542 std::string Compiler::GetDependencyNames(const std::string& separator) const {
543  std::vector<std::string> dependencies = included_file_names_;
544  dependencies.push_back(Utf8FromPath(options_.file_name));
545  return JoinStrings(dependencies, separator);
546 }
547 
548 std::unique_ptr<fml::Mapping> Compiler::CreateDepfileContents(
549  std::initializer_list<std::string> targets_names) const {
550  // https://github.com/ninja-build/ninja/blob/master/src/depfile_parser.cc#L28
551  const auto targets = JoinStrings(targets_names, " ");
552  const auto dependencies = GetDependencyNames(" ");
553 
554  std::stringstream stream;
555  stream << targets << ": " << dependencies << "\n";
556 
557  auto contents = std::make_shared<std::string>(stream.str());
558  return std::make_unique<fml::NonOwnedMapping>(
559  reinterpret_cast<const uint8_t*>(contents->data()), contents->size(),
560  [contents](auto, auto) {});
561 }
562 
564  return reflector_.get();
565 }
566 
567 } // namespace compiler
568 } // namespace impeller
GLenum type
std::shared_ptr< fml::Mapping > GetSPIRVAssembly() const
Definition: compiler.cc:502
const Reflector * GetReflector() const
Definition: compiler.cc:563
Compiler(const std::shared_ptr< const fml::Mapping > &source_mapping, const SourceOptions &options, Reflector::Options reflector_options)
Definition: compiler.cc:282
const std::vector< std::string > & GetIncludedFileNames() const
Definition: compiler.cc:524
std::unique_ptr< fml::Mapping > CreateDepfileContents(std::initializer_list< std::string > targets) const
Definition: compiler.cc:548
std::shared_ptr< fml::Mapping > GetSLShaderSource() const
Definition: compiler.cc:506
std::string GetErrorMessages() const
Definition: compiler.cc:520
std::shared_ptr< fml::Mapping > CompileToSPV(std::stringstream &error_stream, const shaderc::CompileOptions &spirv_options) const
#define COMPILER_ERROR(stream)
Definition: logger.h:39
static CompilerBackend CreateSkSLCompiler(const spirv_cross::ParsedIR &ir, const SourceOptions &source_options)
Definition: compiler.cc:206
static const uint32_t kMaxUniformBufferSize
Definition: compiler.cc:40
static CompilerBackend CreateVulkanCompiler(const spirv_cross::ParsedIR &ir, const SourceOptions &source_options)
Definition: compiler.cc:136
constexpr char kExternalTexturePrefix[]
Definition: constants.h:11
static bool EntryPointMustBeNamedMain(TargetPlatform platform)
Definition: compiler.cc:212
static CompilerBackend CreateMSLCompiler(const spirv_cross::ParsedIR &ir, const SourceOptions &source_options, std::optional< uint32_t > msl_version_override={})
Definition: compiler.cc:65
static CompilerBackend CreateCompiler(const spirv_cross::ParsedIR &ir, const SourceOptions &source_options)
Definition: compiler.cc:232
std::string Utf8FromPath(const std::filesystem::path &path)
Converts a native format path to a utf8 string.
Definition: utilities.cc:30
static std::string JoinStrings(std::vector< std::string > items, const std::string &separator)
Definition: compiler.cc:528
spirv_cross::CompilerMSL::Options::Platform TargetPlatformToMSLPlatform(TargetPlatform platform)
Definition: types.cc:202
static uint32_t ParseMSLVersion(const std::string &msl_version)
Definition: compiler.cc:42
bool StringStartsWith(const std::string &target, const std::string &prefix)
Definition: utilities.cc:86
static CompilerBackend CreateGLSLCompiler(const spirv_cross::ParsedIR &ir, const SourceOptions &source_options)
Definition: compiler.cc:148
spv::ExecutionModel ToExecutionModel(SourceType type)
Definition: types.cc:188
std::vector< spirv_cross::ID > SortUniforms(const spirv_cross::ParsedIR *ir, const spirv_cross::Compiler *compiler, std::optional< spirv_cross::SPIRType::BaseType > type_filter, bool include)
Sorts uniform declarations in an IR according to decoration order.
std::shared_ptr< fml::Mapping > CreateMappingWithString(std::string string)
Creates a mapping with string data.
Definition: allocation.cc:111
spirv_cross::Compiler * GetCompiler()
std::optional< shaderc_source_language > source_langauge
std::vector< std::string > macro_definitions
shaderc_optimization_level optimization_level
std::optional< SPIRVCompilerSourceProfile > source_profile
std::shared_ptr< Includer > includer
shaderc::CompileOptions BuildShadercOptions() const
std::optional< SPIRVCompilerTargetEnv > target
bool use_half_textures
Whether half-precision textures should be supported, requiring opengl semantics. Only used on metal t...
std::vector< IncludeDir > include_dirs
std::filesystem::path file_name
bool require_framebuffer_fetch
Whether the GLSL framebuffer fetch extension will be required.
std::shared_ptr< fml::UniqueFD > working_directory
std::vector< std::string > defines