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 
26 namespace impeller {
27 namespace compiler {
28 
29 namespace {
30 constexpr const char* kEGLImageExternalExtension = "GL_OES_EGL_image_external";
31 constexpr const char* kEGLImageExternalExtension300 =
32  "GL_OES_EGL_image_external_essl3";
33 } // namespace
34 
35 static uint32_t ParseMSLVersion(const std::string& msl_version) {
36  std::stringstream sstream(msl_version);
37  std::string version_part;
38  uint32_t major = 1;
39  uint32_t minor = 2;
40  uint32_t patch = 0;
41  if (std::getline(sstream, version_part, '.')) {
42  major = std::stoi(version_part);
43  if (std::getline(sstream, version_part, '.')) {
44  minor = std::stoi(version_part);
45  if (std::getline(sstream, version_part, '.')) {
46  patch = std::stoi(version_part);
47  }
48  }
49  }
50  if (major < 1 || (major == 1 && minor < 2)) {
51  std::cerr << "--metal-version version must be at least 1.2. Have "
52  << msl_version << std::endl;
53  }
54  return spirv_cross::CompilerMSL::Options::make_msl_version(major, minor,
55  patch);
56 }
57 
59  const spirv_cross::ParsedIR& ir,
60  const SourceOptions& source_options,
61  std::optional<uint32_t> msl_version_override = {}) {
62  auto sl_compiler = std::make_shared<spirv_cross::CompilerMSL>(ir);
63  spirv_cross::CompilerMSL::Options sl_options;
64  sl_options.platform =
66  sl_options.msl_version = msl_version_override.value_or(
67  ParseMSLVersion(source_options.metal_version));
68  sl_options.ios_use_simdgroup_functions =
69  sl_options.is_ios() &&
70  sl_options.msl_version >=
71  spirv_cross::CompilerMSL::Options::make_msl_version(2, 4, 0);
72  sl_options.use_framebuffer_fetch_subpasses = true;
73  sl_compiler->set_msl_options(sl_options);
74 
75  // Sort the float and sampler uniforms according to their declared/decorated
76  // order. For user authored fragment shaders, the API for setting uniform
77  // values uses the index of the uniform in the declared order. By default, the
78  // metal backend of spirv-cross will order uniforms according to usage. To fix
79  // this, we use the sorted order and the add_msl_resource_binding API to force
80  // the ordering to match the declared order. Note that while this code runs
81  // for all compiled shaders, it will only affect vertex and fragment shaders
82  // due to the specified stage.
83  auto floats =
84  SortUniforms(&ir, sl_compiler.get(), spirv_cross::SPIRType::Float);
85  auto images =
86  SortUniforms(&ir, sl_compiler.get(), spirv_cross::SPIRType::SampledImage);
87 
88  spv::ExecutionModel execution_model =
89  spv::ExecutionModel::ExecutionModelFragment;
90  if (source_options.type == SourceType::kVertexShader) {
91  execution_model = spv::ExecutionModel::ExecutionModelVertex;
92  }
93 
94  uint32_t buffer_offset = 0;
95  uint32_t sampler_offset = 0;
96  for (auto& float_id : floats) {
97  sl_compiler->add_msl_resource_binding(
98  {.stage = execution_model,
99  .basetype = spirv_cross::SPIRType::BaseType::Float,
100  .desc_set = sl_compiler->get_decoration(float_id,
101  spv::DecorationDescriptorSet),
102  .binding =
103  sl_compiler->get_decoration(float_id, spv::DecorationBinding),
104  .count = 1u,
105  .msl_buffer = buffer_offset});
106  buffer_offset++;
107  }
108  for (auto& image_id : images) {
109  sl_compiler->add_msl_resource_binding({
110  .stage = execution_model,
111  .basetype = spirv_cross::SPIRType::BaseType::SampledImage,
112  .desc_set =
113  sl_compiler->get_decoration(image_id, spv::DecorationDescriptorSet),
114  .binding =
115  sl_compiler->get_decoration(image_id, spv::DecorationBinding),
116  .count = 1u,
117  // A sampled image is both an image and a sampler, so both
118  // offsets need to be set or depending on the partiular shader
119  // the bindings may be incorrect.
120  .msl_texture = sampler_offset,
121  .msl_sampler = sampler_offset,
122  });
123  sampler_offset++;
124  }
125 
126  return CompilerBackend(sl_compiler);
127 }
128 
130  const spirv_cross::ParsedIR& ir,
131  const SourceOptions& source_options) {
132  auto gl_compiler = std::make_shared<spirv_cross::CompilerGLSL>(ir);
133  spirv_cross::CompilerGLSL::Options sl_options;
134  sl_options.force_zero_initialized_variables = true;
135  sl_options.vertex.fixup_clipspace = true;
136  sl_options.vulkan_semantics = true;
137  gl_compiler->set_common_options(sl_options);
138  return CompilerBackend(gl_compiler);
139 }
140 
141 static CompilerBackend CreateGLSLCompiler(const spirv_cross::ParsedIR& ir,
142  const SourceOptions& source_options) {
143  auto gl_compiler = std::make_shared<spirv_cross::CompilerGLSL>(ir);
144 
145  // Walk the variables and insert the external image extension if any of them
146  // begins with the external texture prefix. Unfortunately, we can't walk
147  // `gl_compiler->get_shader_resources().separate_samplers` until the compiler
148  // is further along.
149  //
150  // Unfortunately, we can't just let the shader author add this extension and
151  // use `samplerExternalOES` directly because compiling to spirv requires the
152  // source language profile to be at least 310 ES, but this extension is
153  // incompatible with ES 310+.
154  for (auto& id : ir.ids_for_constant_or_variable) {
155  if (StringStartsWith(ir.get_name(id), kExternalTexturePrefix)) {
156  if (source_options.gles_language_version >= 300) {
157  gl_compiler->require_extension(kEGLImageExternalExtension300);
158  } else {
159  gl_compiler->require_extension(kEGLImageExternalExtension);
160  }
161  break;
162  }
163  }
164 
165  spirv_cross::CompilerGLSL::Options sl_options;
166  sl_options.force_zero_initialized_variables = true;
167  sl_options.vertex.fixup_clipspace = true;
168  if (source_options.target_platform == TargetPlatform::kOpenGLES ||
171  sl_options.version = source_options.gles_language_version > 0
172  ? source_options.gles_language_version
173  : 100;
174  sl_options.es = true;
175  if (source_options.target_platform == TargetPlatform::kRuntimeStageGLES3) {
176  sl_options.version = 300;
177  }
178  if (source_options.require_framebuffer_fetch &&
179  source_options.type == SourceType::kFragmentShader) {
180  gl_compiler->remap_ext_framebuffer_fetch(0, 0, true);
181  }
182  gl_compiler->set_variable_type_remap_callback(
183  [&](const spirv_cross::SPIRType& type, const std::string& var_name,
184  std::string& name_of_type) {
185  if (StringStartsWith(var_name, kExternalTexturePrefix)) {
186  name_of_type = "samplerExternalOES";
187  }
188  });
189  } else {
190  sl_options.version = source_options.gles_language_version > 0
191  ? source_options.gles_language_version
192  : 120;
193  sl_options.es = false;
194  }
195  gl_compiler->set_common_options(sl_options);
196  return CompilerBackend(gl_compiler);
197 }
198 
199 static CompilerBackend CreateSkSLCompiler(const spirv_cross::ParsedIR& ir,
200  const SourceOptions& source_options) {
201  auto sksl_compiler = std::make_shared<CompilerSkSL>(ir);
202  return CompilerBackend(sksl_compiler);
203 }
204 
206  switch (platform) {
208  FML_UNREACHABLE();
214  return false;
220  return true;
221  }
222  FML_UNREACHABLE();
223 }
224 
225 static CompilerBackend CreateCompiler(const spirv_cross::ParsedIR& ir,
226  const SourceOptions& source_options) {
227  CompilerBackend compiler;
228  switch (source_options.target_platform) {
232  compiler = CreateMSLCompiler(ir, source_options);
233  break;
236  compiler = CreateVulkanCompiler(ir, source_options);
237  break;
243  compiler = CreateGLSLCompiler(ir, source_options);
244  break;
246  compiler = CreateSkSLCompiler(ir, source_options);
247  }
248  if (!compiler) {
249  return {};
250  }
251  auto* backend = compiler.GetCompiler();
252  if (!EntryPointMustBeNamedMain(source_options.target_platform) &&
253  source_options.source_language == SourceLanguage::kGLSL) {
254  backend->rename_entry_point("main", source_options.entry_point_name,
255  ToExecutionModel(source_options.type));
256  }
257  return compiler;
258 }
259 
260 Compiler::Compiler(const std::shared_ptr<const fml::Mapping>& source_mapping,
261  const SourceOptions& source_options,
262  Reflector::Options reflector_options)
263  : options_(source_options) {
264  if (!source_mapping || source_mapping->GetMapping() == nullptr) {
265  COMPILER_ERROR(error_stream_)
266  << "Could not read shader source or shader source was empty.";
267  return;
268  }
269 
270  if (source_options.target_platform == TargetPlatform::kUnknown) {
271  COMPILER_ERROR(error_stream_) << "Target platform not specified.";
272  return;
273  }
274 
275  SPIRVCompilerOptions spirv_options;
276 
277  // Make sure reflection is as effective as possible. The generated shaders
278  // will be processed later by backend specific compilers.
279  spirv_options.generate_debug_info = true;
280 
281  switch (options_.source_language) {
283  // Expects GLSL 4.60 (Core Profile).
284  // https://www.khronos.org/registry/OpenGL/specs/gl/GLSLangSpec.4.60.pdf
285  spirv_options.source_langauge =
286  shaderc_source_language::shaderc_source_language_glsl;
288  shaderc_profile::shaderc_profile_core, //
289  460, //
290  };
291  break;
293  spirv_options.source_langauge =
294  shaderc_source_language::shaderc_source_language_hlsl;
295  break;
297  COMPILER_ERROR(error_stream_) << "Source language invalid.";
298  return;
299  }
300 
301  switch (source_options.target_platform) {
304  SPIRVCompilerTargetEnv target;
305 
306  if (source_options.use_half_textures) {
307  target.env = shaderc_target_env::shaderc_target_env_opengl;
308  target.version = shaderc_env_version::shaderc_env_version_opengl_4_5;
309  target.spirv_version = shaderc_spirv_version::shaderc_spirv_version_1_0;
310  } else {
311  target.env = shaderc_target_env::shaderc_target_env_vulkan;
312  target.version = shaderc_env_version::shaderc_env_version_vulkan_1_1;
313  target.spirv_version = shaderc_spirv_version::shaderc_spirv_version_1_3;
314  }
315 
316  spirv_options.target = target;
317  } break;
322  SPIRVCompilerTargetEnv target;
323 
324  target.env = shaderc_target_env::shaderc_target_env_vulkan;
325  target.version = shaderc_env_version::shaderc_env_version_vulkan_1_1;
326  target.spirv_version = shaderc_spirv_version::shaderc_spirv_version_1_3;
327 
328  if (source_options.target_platform ==
330  spirv_options.macro_definitions.push_back("IMPELLER_GRAPHICS_BACKEND");
331  spirv_options.relaxed_vulkan_rules = true;
332  }
333  spirv_options.target = target;
334  } break;
338  SPIRVCompilerTargetEnv target;
339 
340  target.env = shaderc_target_env::shaderc_target_env_opengl;
341  target.version = shaderc_env_version::shaderc_env_version_opengl_4_5;
342  target.spirv_version = shaderc_spirv_version::shaderc_spirv_version_1_0;
343 
344  spirv_options.target = target;
345  spirv_options.macro_definitions.push_back("IMPELLER_GRAPHICS_BACKEND");
346  } break;
347  case TargetPlatform::kSkSL: {
348  SPIRVCompilerTargetEnv target;
349 
350  target.env = shaderc_target_env::shaderc_target_env_opengl;
351  target.version = shaderc_env_version::shaderc_env_version_opengl_4_5;
352  target.spirv_version = shaderc_spirv_version::shaderc_spirv_version_1_0;
353 
354  // When any optimization level above 'zero' is enabled, the phi merges at
355  // loop continue blocks are rendered using syntax that is supported in
356  // GLSL, but not in SkSL.
357  // https://bugs.chromium.org/p/skia/issues/detail?id=13518.
358  spirv_options.optimization_level =
359  shaderc_optimization_level::shaderc_optimization_level_zero;
360  spirv_options.target = target;
361  spirv_options.macro_definitions.push_back("SKIA_GRAPHICS_BACKEND");
362  } break;
364  COMPILER_ERROR(error_stream_) << "Target platform invalid.";
365  return;
366  }
367 
368  // Implicit definition that indicates that this compilation is for the device
369  // (instead of the host).
370  spirv_options.macro_definitions.push_back("IMPELLER_DEVICE");
371  for (const auto& define : source_options.defines) {
372  spirv_options.macro_definitions.push_back(define);
373  }
374 
375  std::vector<std::string> included_file_names;
376  spirv_options.includer = std::make_shared<Includer>(
377  options_.working_directory, options_.include_dirs,
378  [&included_file_names](auto included_name) {
379  included_file_names.emplace_back(std::move(included_name));
380  });
381 
382  // SPIRV Generation.
383  SPIRVCompiler spv_compiler(source_options, source_mapping);
384 
385  spirv_assembly_ = spv_compiler.CompileToSPV(
386  error_stream_, spirv_options.BuildShadercOptions());
387 
388  if (!spirv_assembly_) {
389  return;
390  } else {
391  included_file_names_ = std::move(included_file_names);
392  }
393 
394  // SL Generation.
395  spirv_cross::Parser parser(
396  reinterpret_cast<const uint32_t*>(spirv_assembly_->GetMapping()),
397  spirv_assembly_->GetSize() / sizeof(uint32_t));
398  // The parser and compiler must be run separately because the parser contains
399  // meta information (like type member names) that are useful for reflection.
400  parser.parse();
401 
402  const auto parsed_ir =
403  std::make_shared<spirv_cross::ParsedIR>(parser.get_parsed_ir());
404 
405  auto sl_compiler = CreateCompiler(*parsed_ir, options_);
406 
407  if (!sl_compiler) {
408  COMPILER_ERROR(error_stream_)
409  << "Could not create compiler for target platform.";
410  return;
411  }
412 
413  // We need to invoke the compiler even if we don't use the SL mapping later
414  // for Vulkan. The reflector needs information that is only valid after a
415  // successful compilation call.
416  auto sl_compilation_result =
417  CreateMappingWithString(sl_compiler.GetCompiler()->compile());
418 
419  // If the target is Vulkan, our shading language is SPIRV which we already
420  // have. We just need to strip it of debug information. If it isn't, we need
421  // to invoke the appropriate compiler to compile the SPIRV to the target SL.
422  if (source_options.target_platform == TargetPlatform::kVulkan ||
424  auto stripped_spirv_options = spirv_options;
425  stripped_spirv_options.generate_debug_info = false;
426  sl_mapping_ = spv_compiler.CompileToSPV(
427  error_stream_, stripped_spirv_options.BuildShadercOptions());
428  } else {
429  sl_mapping_ = sl_compilation_result;
430  }
431 
432  if (!sl_mapping_) {
433  COMPILER_ERROR(error_stream_) << "Could not generate SL from SPIRV";
434  return;
435  }
436 
437  reflector_ = std::make_unique<Reflector>(std::move(reflector_options), //
438  parsed_ir, //
439  GetSLShaderSource(), //
440  sl_compiler //
441  );
442 
443  if (!reflector_->IsValid()) {
444  COMPILER_ERROR(error_stream_)
445  << "Could not complete reflection on generated shader.";
446  return;
447  }
448 
449  is_valid_ = true;
450 }
451 
452 Compiler::~Compiler() = default;
453 
454 std::shared_ptr<fml::Mapping> Compiler::GetSPIRVAssembly() const {
455  return spirv_assembly_;
456 }
457 
458 std::shared_ptr<fml::Mapping> Compiler::GetSLShaderSource() const {
459  return sl_mapping_;
460 }
461 
462 bool Compiler::IsValid() const {
463  return is_valid_;
464 }
465 
466 std::string Compiler::GetSourcePrefix() const {
467  std::stringstream stream;
468  stream << options_.file_name << ": ";
469  return stream.str();
470 }
471 
472 std::string Compiler::GetErrorMessages() const {
473  return error_stream_.str();
474 }
475 
476 const std::vector<std::string>& Compiler::GetIncludedFileNames() const {
477  return included_file_names_;
478 }
479 
480 static std::string JoinStrings(std::vector<std::string> items,
481  const std::string& separator) {
482  std::stringstream stream;
483  for (size_t i = 0, count = items.size(); i < count; i++) {
484  const auto is_last = (i == count - 1);
485 
486  stream << items[i];
487  if (!is_last) {
488  stream << separator;
489  }
490  }
491  return stream.str();
492 }
493 
494 std::string Compiler::GetDependencyNames(const std::string& separator) const {
495  std::vector<std::string> dependencies = included_file_names_;
496  dependencies.push_back(options_.file_name);
497  return JoinStrings(dependencies, separator);
498 }
499 
500 std::unique_ptr<fml::Mapping> Compiler::CreateDepfileContents(
501  std::initializer_list<std::string> targets_names) const {
502  // https://github.com/ninja-build/ninja/blob/master/src/depfile_parser.cc#L28
503  const auto targets = JoinStrings(targets_names, " ");
504  const auto dependencies = GetDependencyNames(" ");
505 
506  std::stringstream stream;
507  stream << targets << ": " << dependencies << "\n";
508 
509  auto contents = std::make_shared<std::string>(stream.str());
510  return std::make_unique<fml::NonOwnedMapping>(
511  reinterpret_cast<const uint8_t*>(contents->data()), contents->size(),
512  [contents](auto, auto) {});
513 }
514 
516  return reflector_.get();
517 }
518 
519 } // namespace compiler
520 } // namespace impeller
GLenum type
std::shared_ptr< fml::Mapping > GetSPIRVAssembly() const
Definition: compiler.cc:454
const Reflector * GetReflector() const
Definition: compiler.cc:515
Compiler(const std::shared_ptr< const fml::Mapping > &source_mapping, const SourceOptions &options, Reflector::Options reflector_options)
Definition: compiler.cc:260
const std::vector< std::string > & GetIncludedFileNames() const
Definition: compiler.cc:476
std::unique_ptr< fml::Mapping > CreateDepfileContents(std::initializer_list< std::string > targets) const
Definition: compiler.cc:500
std::shared_ptr< fml::Mapping > GetSLShaderSource() const
Definition: compiler.cc:458
std::string GetErrorMessages() const
Definition: compiler.cc:472
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:199
static CompilerBackend CreateVulkanCompiler(const spirv_cross::ParsedIR &ir, const SourceOptions &source_options)
Definition: compiler.cc:129
constexpr char kExternalTexturePrefix[]
Definition: constants.h:11
static bool EntryPointMustBeNamedMain(TargetPlatform platform)
Definition: compiler.cc:205
static CompilerBackend CreateMSLCompiler(const spirv_cross::ParsedIR &ir, const SourceOptions &source_options, std::optional< uint32_t > msl_version_override={})
Definition: compiler.cc:58
static CompilerBackend CreateCompiler(const spirv_cross::ParsedIR &ir, const SourceOptions &source_options)
Definition: compiler.cc:225
static std::string JoinStrings(std::vector< std::string > items, const std::string &separator)
Definition: compiler.cc:480
spirv_cross::CompilerMSL::Options::Platform TargetPlatformToMSLPlatform(TargetPlatform platform)
Definition: types.cc:215
static uint32_t ParseMSLVersion(const std::string &msl_version)
Definition: compiler.cc:35
bool StringStartsWith(const std::string &target, const std::string &prefix)
Definition: utilities.cc:87
static CompilerBackend CreateGLSLCompiler(const spirv_cross::ParsedIR &ir, const SourceOptions &source_options)
Definition: compiler.cc:141
spv::ExecutionModel ToExecutionModel(SourceType type)
Definition: types.cc:201
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
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