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 <string_view>
14 #include <utility>
15 
16 #include "flutter/fml/paths.h"
26 #include "third_party/skia/include/core/SkString.h"
27 #include "third_party/skia/include/effects/SkRuntimeEffect.h"
28 
29 namespace impeller {
30 namespace compiler {
31 
32 namespace {
33 constexpr const char* kEGLImageExternalExtension = "GL_OES_EGL_image_external";
34 constexpr const char* kEGLImageExternalExtension300 =
35  "GL_OES_EGL_image_external_essl3";
36 constexpr int kVerboseErrorLineThreshold = 6;
37 
38 // Set per pass by RenderPassGLES: +1.0 swapchain, -1.0 offscreen FBO. See
39 // flutter/flutter#186554.
40 constexpr const char* kYFlipUniformName = "_impeller_y_flip";
41 constexpr const char* kUserMainName = "_impeller_user_main";
42 
43 bool IsGLTargetPlatform(TargetPlatform platform) {
44  return platform == TargetPlatform::kOpenGLES ||
45  platform == TargetPlatform::kOpenGLDesktop ||
48 }
49 
50 // Wraps the SPIRV-Cross-emitted entry point so `gl_Position.y *=
51 // _impeller_y_flip;` runs on every exit, including early returns. Renames
52 // `void main(` to `void _impeller_user_main(` and appends a wrapper that
53 // calls it then applies the flip.
54 std::string InjectYFlipForGLESVertexShader(std::string source) {
55  // Anchor on leading newline; spirv-cross emits the entry point at file
56  // scope, so the match is unambiguous in its comment-free output.
57  constexpr std::string_view kMainPattern = "\nvoid main(";
58  const size_t main_pos = source.find(kMainPattern);
59  if (main_pos == std::string::npos) {
60  return source;
61  }
62  const std::string user_main_decl =
63  std::string("\nvoid ") + kUserMainName + "(";
64  source.replace(main_pos, kMainPattern.size(), user_main_decl);
65 
66  std::string wrapper = "\nvoid main() {\n ";
67  wrapper += kUserMainName;
68  wrapper += "();\n gl_Position.y *= ";
69  wrapper += kYFlipUniformName;
70  wrapper += ";\n}\n";
71  source.append(wrapper);
72 
73  // Declare the uniform after the last `precision` directive, falling
74  // back to right after `#version`, falling back to the top.
75  const std::string declaration =
76  std::string("\nuniform float ") + kYFlipUniformName + ";\n";
77  size_t inject_at = std::string::npos;
78  for (size_t pos = source.find("\nprecision "); pos != std::string::npos;
79  pos = source.find("\nprecision ", pos + 1)) {
80  const size_t eol = source.find('\n', pos + 1);
81  if (eol == std::string::npos) {
82  break;
83  }
84  inject_at = eol;
85  }
86  if (inject_at == std::string::npos) {
87  const size_t version_pos = source.find("#version");
88  if (version_pos != std::string::npos) {
89  inject_at = source.find('\n', version_pos);
90  }
91  }
92  if (inject_at == std::string::npos) {
93  inject_at = 0;
94  }
95  source.insert(inject_at, declaration);
96  return source;
97 }
98 } // namespace
99 
100 // This value should be <= 7372. UBOs can be larger on some devices but a
101 // performance cost will be paid.
102 // https://docs.qualcomm.com/bundle/publicresource/topics/80-78185-2/best_practices.html?product=1601111740035277#buffer-best-practices
103 static const uint32_t kMaxUniformBufferSize = 6208;
104 
105 static uint32_t ParseMSLVersion(const std::string& msl_version) {
106  std::stringstream sstream(msl_version);
107  std::string version_part;
108  uint32_t major = 1;
109  uint32_t minor = 2;
110  uint32_t patch = 0;
111  if (std::getline(sstream, version_part, '.')) {
112  major = std::stoi(version_part);
113  if (std::getline(sstream, version_part, '.')) {
114  minor = std::stoi(version_part);
115  if (std::getline(sstream, version_part, '.')) {
116  patch = std::stoi(version_part);
117  }
118  }
119  }
120  if (major < 1 || (major == 1 && minor < 2)) {
121  std::cerr << "--metal-version version must be at least 1.2. Have "
122  << msl_version << std::endl;
123  }
124  return spirv_cross::CompilerMSL::Options::make_msl_version(major, minor,
125  patch);
126 }
127 
129  const spirv_cross::ParsedIR& ir,
130  const SourceOptions& source_options,
131  std::optional<uint32_t> msl_version_override = {}) {
132  auto sl_compiler = std::make_shared<spirv_cross::CompilerMSL>(ir);
133  spirv_cross::CompilerMSL::Options sl_options;
134  sl_options.platform =
136  sl_options.msl_version = msl_version_override.value_or(
137  ParseMSLVersion(source_options.metal_version));
138  sl_options.ios_use_simdgroup_functions =
139  sl_options.is_ios() &&
140  sl_options.msl_version >=
141  spirv_cross::CompilerMSL::Options::make_msl_version(2, 4, 0);
142  sl_options.use_framebuffer_fetch_subpasses = true;
143  sl_compiler->set_msl_options(sl_options);
144 
145  // Sort the float and sampler uniforms according to their declared/decorated
146  // order. For user authored fragment shaders, the API for setting uniform
147  // values uses the index of the uniform in the declared order. By default, the
148  // metal backend of spirv-cross will order uniforms according to usage. To fix
149  // this, we use the sorted order and the add_msl_resource_binding API to force
150  // the ordering to match the declared order. Note that while this code runs
151  // for all compiled shaders, it will only affect vertex and fragment shaders
152  // due to the specified stage.
153  auto floats =
154  SortUniforms(&ir, sl_compiler.get(), spirv_cross::SPIRType::Float);
155  auto images =
156  SortUniforms(&ir, sl_compiler.get(), spirv_cross::SPIRType::SampledImage);
157 
158  spv::ExecutionModel execution_model =
159  spv::ExecutionModel::ExecutionModelFragment;
160  if (source_options.type == SourceType::kVertexShader) {
161  execution_model = spv::ExecutionModel::ExecutionModelVertex;
162  }
163 
164  uint32_t buffer_offset = 0;
165  uint32_t sampler_offset = 0;
166  for (auto& float_id : floats) {
167  sl_compiler->add_msl_resource_binding(
168  {.stage = execution_model,
169  .basetype = spirv_cross::SPIRType::BaseType::Float,
170  .desc_set = sl_compiler->get_decoration(float_id,
171  spv::DecorationDescriptorSet),
172  .binding =
173  sl_compiler->get_decoration(float_id, spv::DecorationBinding),
174  .count = 1u,
175  .msl_buffer = buffer_offset});
176  buffer_offset++;
177  }
178  for (auto& image_id : images) {
179  sl_compiler->add_msl_resource_binding({
180  .stage = execution_model,
181  .basetype = spirv_cross::SPIRType::BaseType::SampledImage,
182  .desc_set =
183  sl_compiler->get_decoration(image_id, spv::DecorationDescriptorSet),
184  .binding =
185  sl_compiler->get_decoration(image_id, spv::DecorationBinding),
186  .count = 1u,
187  // A sampled image is both an image and a sampler, so both
188  // offsets need to be set or depending on the partiular shader
189  // the bindings may be incorrect.
190  .msl_texture = sampler_offset,
191  .msl_sampler = sampler_offset,
192  });
193  sampler_offset++;
194  }
195 
196  return CompilerBackend(sl_compiler);
197 }
198 
200  const spirv_cross::ParsedIR& ir,
201  const SourceOptions& source_options) {
202  auto gl_compiler = std::make_shared<spirv_cross::CompilerGLSL>(ir);
203  spirv_cross::CompilerGLSL::Options sl_options;
204  sl_options.force_zero_initialized_variables = true;
205  sl_options.vertex.fixup_clipspace = true;
206  sl_options.vulkan_semantics = true;
207  gl_compiler->set_common_options(sl_options);
208  return CompilerBackend(gl_compiler);
209 }
210 
211 static CompilerBackend CreateGLSLCompiler(const spirv_cross::ParsedIR& ir,
212  const SourceOptions& source_options) {
213  auto gl_compiler = std::make_shared<spirv_cross::CompilerGLSL>(ir);
214 
215  // Walk the variables and insert the external image extension if any of them
216  // begins with the external texture prefix. Unfortunately, we can't walk
217  // `gl_compiler->get_shader_resources().separate_samplers` until the compiler
218  // is further along.
219  //
220  // Unfortunately, we can't just let the shader author add this extension and
221  // use `samplerExternalOES` directly because compiling to spirv requires the
222  // source language profile to be at least 310 ES, but this extension is
223  // incompatible with ES 310+.
224  for (auto& id : ir.ids_for_constant_or_variable) {
225  if (StringStartsWith(ir.get_name(id), kExternalTexturePrefix)) {
226  if (source_options.gles_language_version >= 300) {
227  gl_compiler->require_extension(kEGLImageExternalExtension300);
228  } else {
229  gl_compiler->require_extension(kEGLImageExternalExtension);
230  }
231  break;
232  }
233  }
234 
235  spirv_cross::CompilerGLSL::Options sl_options;
236  sl_options.force_zero_initialized_variables = true;
237  sl_options.vertex.fixup_clipspace = true;
238  if (source_options.target_platform == TargetPlatform::kOpenGLES ||
241  sl_options.version = source_options.gles_language_version > 0
242  ? source_options.gles_language_version
243  : 100;
244  // If we have requested GLES3 and/or Compute Shaders,
245  // promote the language version accordingly
246  if (source_options.type == SourceType::kComputeShader &&
247  sl_options.version < 310) {
248  sl_options.version = 310;
249  } else if (source_options.target_platform ==
251  sl_options.version < 300) {
252  sl_options.version = 300;
253  }
254  sl_options.es = true;
255  if (source_options.require_framebuffer_fetch &&
256  source_options.type == SourceType::kFragmentShader) {
257  gl_compiler->remap_ext_framebuffer_fetch(0, 0, true);
258  }
259  gl_compiler->set_variable_type_remap_callback(
260  [&](const spirv_cross::SPIRType& type, const std::string& var_name,
261  std::string& name_of_type) {
262  if (StringStartsWith(var_name, kExternalTexturePrefix)) {
263  name_of_type = "samplerExternalOES";
264  }
265  });
266  } else {
267  sl_options.version = source_options.gles_language_version > 0
268  ? source_options.gles_language_version
269  : 120;
270  if (source_options.type == SourceType::kComputeShader &&
271  sl_options.version < 430) {
272  sl_options.version = 430;
273  }
274  sl_options.es = false;
275  }
276  gl_compiler->set_common_options(sl_options);
277  return CompilerBackend(gl_compiler);
278 }
279 
280 static CompilerBackend CreateSkSLCompiler(const spirv_cross::ParsedIR& ir,
281  const SourceOptions& source_options) {
282  auto sksl_compiler = std::make_shared<CompilerSkSL>(ir);
283  return CompilerBackend(sksl_compiler);
284 }
285 
287  switch (platform) {
289  FML_UNREACHABLE();
295  return false;
301  return true;
302  }
303  FML_UNREACHABLE();
304 }
305 
306 static CompilerBackend CreateCompiler(const spirv_cross::ParsedIR& ir,
307  const SourceOptions& source_options) {
308  CompilerBackend compiler;
309  switch (source_options.target_platform) {
313  compiler = CreateMSLCompiler(ir, source_options);
314  break;
317  compiler = CreateVulkanCompiler(ir, source_options);
318  break;
323  compiler = CreateGLSLCompiler(ir, source_options);
324  break;
326  compiler = CreateSkSLCompiler(ir, source_options);
327  break;
329  FML_UNREACHABLE();
330  }
331  if (!compiler) {
332  return {};
333  }
334  auto* backend = compiler.GetCompiler();
335  if (!EntryPointMustBeNamedMain(source_options.target_platform) &&
336  source_options.source_language == SourceLanguage::kGLSL) {
337  backend->rename_entry_point("main", source_options.entry_point_name,
338  ToExecutionModel(source_options.type));
339  }
340  return compiler;
341 }
342 
343 namespace {
344 uint32_t CalculateUBOSize(const spirv_cross::Compiler* compiler) {
345  spirv_cross::ShaderResources resources = compiler->get_shader_resources();
346  uint32_t result = 0;
347  for (const spirv_cross::Resource& ubo : resources.uniform_buffers) {
348  const spirv_cross::SPIRType& ubo_type =
349  compiler->get_type(ubo.base_type_id);
350  uint32_t size = compiler->get_declared_struct_size(ubo_type);
351  result += size;
352  }
353  return result;
354 }
355 
356 // Mirrors BufferBindingsGLES's uniform key normalization: ASCII uppercase
357 // and drop underscores. GLSL identifiers are ASCII, so this is locale-safe.
358 std::string StripUnderscoresAndUpper(std::string_view name) {
359  std::string result;
360  result.reserve(name.size());
361  for (char ch : name) {
362  if (ch == '_') {
363  continue;
364  }
365  if (ch >= 'a' && ch <= 'z') {
366  result.push_back(static_cast<char>(ch - 'a' + 'A'));
367  } else {
368  result.push_back(ch);
369  }
370  }
371  return result;
372 }
373 
374 // On pre-`#version 140` GL targets, SPIRV-Cross lowers each uniform block to a
375 // flat `uniform StructType instanceName;` and BufferBindingsGLES resolves
376 // members by the block name modulo `StripUnderscoresAndUpper`. A
377 // non-conforming instance name (e.g. `uniform ToonInfo { ... } toon;`) causes
378 // every member to silently bind to GL location `-1`. Rename the instance to
379 // `_<BlockName>`, which reduces to the block name under the same fold.
380 // See flutter/flutter#186393.
381 void CanonicalizeUniformBlockInstanceNamesForGL(
382  TargetPlatform target_platform,
383  spirv_cross::Compiler* compiler) {
384  if (target_platform != TargetPlatform::kOpenGLES &&
385  target_platform != TargetPlatform::kOpenGLDesktop) {
386  return;
387  }
388  for (const spirv_cross::Resource& ubo :
389  compiler->get_shader_resources().uniform_buffers) {
390  const std::string block_name = compiler->get_name(ubo.base_type_id);
391  const std::string instance_name = compiler->get_name(ubo.id);
392  if (StripUnderscoresAndUpper(instance_name) ==
393  StripUnderscoresAndUpper(block_name)) {
394  continue;
395  }
396  compiler->set_name(ubo.id, "_" + block_name);
397  }
398 }
399 
400 } // namespace
401 
402 Compiler::Compiler(const std::shared_ptr<const fml::Mapping>& source_mapping,
403  const SourceOptions& source_options,
404  Reflector::Options reflector_options)
405  : options_(source_options) {
406  if (!source_mapping || source_mapping->GetMapping() == nullptr) {
407  COMPILER_ERROR(error_stream_)
408  << "Could not read shader source or shader source was empty.";
409  return;
410  }
411 
412  if (source_options.target_platform == TargetPlatform::kUnknown) {
413  COMPILER_ERROR(error_stream_) << "Target platform not specified.";
414  return;
415  }
416 
417  SPIRVCompilerOptions spirv_options;
418 
419  // Make sure reflection is as effective as possible. The generated shaders
420  // will be processed later by backend specific compilers.
421  spirv_options.generate_debug_info = true;
422 
423  switch (options_.source_language) {
425  // Expects GLSL 4.60 (Core Profile).
426  // https://www.khronos.org/registry/OpenGL/specs/gl/GLSLangSpec.4.60.pdf
427  spirv_options.source_langauge =
428  shaderc_source_language::shaderc_source_language_glsl;
430  shaderc_profile::shaderc_profile_core, //
431  460, //
432  };
433  break;
435  spirv_options.source_langauge =
436  shaderc_source_language::shaderc_source_language_hlsl;
437  break;
439  COMPILER_ERROR(error_stream_) << "Source language invalid.";
440  return;
441  }
442 
443  switch (source_options.target_platform) {
446  SPIRVCompilerTargetEnv target;
447 
448  if (source_options.use_half_textures) {
449  target.env = shaderc_target_env::shaderc_target_env_opengl;
450  target.version = shaderc_env_version::shaderc_env_version_opengl_4_5;
451  target.spirv_version = shaderc_spirv_version::shaderc_spirv_version_1_0;
452  } else {
453  target.env = shaderc_target_env::shaderc_target_env_vulkan;
454  target.version = shaderc_env_version::shaderc_env_version_vulkan_1_1;
455  target.spirv_version = shaderc_spirv_version::shaderc_spirv_version_1_3;
456  }
457 
458  spirv_options.target = target;
459  } break;
464  SPIRVCompilerTargetEnv target;
465 
466  target.env = shaderc_target_env::shaderc_target_env_vulkan;
467  target.version = shaderc_env_version::shaderc_env_version_vulkan_1_1;
468  target.spirv_version = shaderc_spirv_version::shaderc_spirv_version_1_3;
469 
470  if (source_options.target_platform ==
472  spirv_options.macro_definitions.push_back("IMPELLER_GRAPHICS_BACKEND");
473  spirv_options.relaxed_vulkan_rules = true;
474  }
475  spirv_options.target = target;
476  } break;
480  SPIRVCompilerTargetEnv target;
481 
482  target.env = shaderc_target_env::shaderc_target_env_opengl;
483  target.version = shaderc_env_version::shaderc_env_version_opengl_4_5;
484  target.spirv_version = shaderc_spirv_version::shaderc_spirv_version_1_0;
485 
486  spirv_options.target = target;
487  spirv_options.macro_definitions.push_back("IMPELLER_GRAPHICS_BACKEND");
488  if (source_options.target_platform == TargetPlatform::kRuntimeStageGLES ||
489  source_options.target_platform ==
491  spirv_options.macro_definitions.push_back("IMPELLER_TARGET_OPENGLES");
492  // A temporary macro that allows fragment shader authors to target
493  // Flutter <= 3.44 before the OpenGLES flip was removed.
494  spirv_options.macro_definitions.push_back(
495  "IMPELLER_OPENGLES_UNFLIPPED_DEPRECATED");
496  }
497  } break;
498  case TargetPlatform::kSkSL: {
499  SPIRVCompilerTargetEnv target;
500 
501  target.env = shaderc_target_env::shaderc_target_env_opengl;
502  target.version = shaderc_env_version::shaderc_env_version_opengl_4_5;
503  target.spirv_version = shaderc_spirv_version::shaderc_spirv_version_1_0;
504 
505  // When any optimization level above 'zero' is enabled, the phi merges at
506  // loop continue blocks are rendered using syntax that is supported in
507  // GLSL, but not in SkSL.
508  // https://bugs.chromium.org/p/skia/issues/detail?id=13518.
509  spirv_options.optimization_level =
510  shaderc_optimization_level::shaderc_optimization_level_zero;
511  spirv_options.target = target;
512  spirv_options.macro_definitions.push_back("SKIA_GRAPHICS_BACKEND");
513  } break;
515  COMPILER_ERROR(error_stream_) << "Target platform invalid.";
516  return;
517  }
518 
519  // Implicit definition that indicates that this compilation is for the device
520  // (instead of the host).
521  spirv_options.macro_definitions.push_back("IMPELLER_DEVICE");
522  for (const auto& define : source_options.defines) {
523  spirv_options.macro_definitions.push_back(define);
524  }
525 
526  std::vector<std::string> included_file_names;
527  spirv_options.includer = std::make_shared<Includer>(
528  options_.working_directory, options_.include_dirs,
529  [&included_file_names](auto included_name) {
530  included_file_names.emplace_back(std::move(included_name));
531  });
532 
533  // SPIRV Generation.
534  SPIRVCompiler spv_compiler(source_options, source_mapping);
535 
536  spirv_assembly_ = spv_compiler.CompileToSPV(
537  error_stream_, spirv_options.BuildShadercOptions());
538 
539  if (!spirv_assembly_) {
540  return;
541  } else {
542  included_file_names_ = std::move(included_file_names);
543  }
544 
545  // SL Generation.
546  spirv_cross::Parser parser(
547  reinterpret_cast<const uint32_t*>(spirv_assembly_->GetMapping()),
548  spirv_assembly_->GetSize() / sizeof(uint32_t));
549  // The parser and compiler must be run separately because the parser contains
550  // meta information (like type member names) that are useful for reflection.
551  parser.parse();
552 
553  const auto parsed_ir =
554  std::make_shared<spirv_cross::ParsedIR>(parser.get_parsed_ir());
555 
556  auto sl_compiler = CreateCompiler(*parsed_ir, options_);
557 
558  if (!sl_compiler) {
559  COMPILER_ERROR(error_stream_)
560  << "Could not create compiler for target platform.";
561  return;
562  }
563 
564  CanonicalizeUniformBlockInstanceNamesForGL(source_options.target_platform,
565  sl_compiler.GetCompiler());
566 
567  uint32_t ubo_size = CalculateUBOSize(sl_compiler.GetCompiler());
568  if (ubo_size > kMaxUniformBufferSize) {
569  COMPILER_ERROR(error_stream_) << "Uniform buffer size exceeds max ("
570  << kMaxUniformBufferSize << "): " << ubo_size;
571  return;
572  }
573 
574  // We need to invoke the compiler even if we don't use the SL mapping later
575  // for Vulkan. The reflector needs information that is only valid after a
576  // successful compilation call.
577  auto sl_compilation_result_str = sl_compiler.GetCompiler()->compile();
578 
579  // GL vertex shaders get a y-flip epilogue; see
580  // https://github.com/flutter/flutter/issues/186554.
581  if (IsGLTargetPlatform(source_options.target_platform) &&
582  source_options.type == SourceType::kVertexShader) {
583  sl_compilation_result_str =
584  InjectYFlipForGLESVertexShader(std::move(sl_compilation_result_str));
585  }
586 
587  auto sl_compilation_result =
588  CreateMappingWithString(sl_compilation_result_str);
589 
590  // If the target is Vulkan, our shading language is SPIRV which we already
591  // have. We just need to strip it of debug information. If it isn't, we need
592  // to invoke the appropriate compiler to compile the SPIRV to the target SL.
593  if (source_options.target_platform == TargetPlatform::kVulkan ||
595  auto stripped_spirv_options = spirv_options;
596  stripped_spirv_options.generate_debug_info = false;
597  sl_mapping_ = spv_compiler.CompileToSPV(
598  error_stream_, stripped_spirv_options.BuildShadercOptions());
599  } else {
600  sl_mapping_ = sl_compilation_result;
601  }
602 
603  if (!sl_mapping_) {
604  COMPILER_ERROR(error_stream_) << "Could not generate SL from SPIRV";
605  return;
606  }
607 
608  if (sl_compiler.GetType() == CompilerBackend::Type::kSkSL &&
609  !ValidateSkSLResult(sl_compilation_result_str).ok()) {
610  return;
611  }
612 
613  reflector_ = std::make_unique<Reflector>(std::move(reflector_options), //
614  parsed_ir, //
615  GetSLShaderSource(), //
616  sl_compiler //
617  );
618 
619  if (!reflector_->IsValid()) {
620  COMPILER_ERROR(error_stream_)
621  << "Could not complete reflection on generated shader.";
622  return;
623  }
624 
625  is_valid_ = true;
626 }
627 
628 absl::Status Compiler::ValidateSkSLResult(const std::string& sksl) {
629  // Validate compiled SkSL by trying to create a SkRuntimeEffect.
630  SkRuntimeEffect::Result result =
631  SkRuntimeEffect::MakeForShader(SkString(sksl));
632 
633  if (result.effect != nullptr) {
634  return absl::OkStatus();
635  }
636 
637  // SkSL is invalid. Output the SkSL and the error.
638 
639  std::stringstream output;
640  bool is_truncated = false;
641 
642  // Lambda to append text to the output stream, truncating if needed.
643  auto append_and_truncate = [&](const std::string& text) {
644  std::stringstream text_stream(text);
645  std::string line;
646  int lines_outputted = 0;
647  while (std::getline(text_stream, line)) {
648  output << "\n " << line;
649  if (++lines_outputted == kVerboseErrorLineThreshold) {
650  break;
651  }
652  }
653  if (lines_outputted == kVerboseErrorLineThreshold) {
654  auto full_line_count = std::count(text.begin(), text.end(), '\n') + 1;
655  output << "\n... (truncated " << full_line_count - lines_outputted
656  << " lines)";
657  is_truncated = true;
658  }
659  };
660 
661  output << "\nCompiled to invalid SkSL:";
662  append_and_truncate(sksl);
663  output << "\nSkSL Error:";
664  std::string error_text(result.errorText.c_str());
665  append_and_truncate(error_text);
666 
667  // Output maybe-truncated SkSL and error to error_stream_.
668  COMPILER_ERROR(error_stream_) << output.str();
669 
670  // If the output was truncated, output the full SkSL and error to
671  // verbose_error_stream_.
672  if (is_truncated) {
673  COMPILER_ERROR(verbose_error_stream_) << "\nCompiled to invalid SkSL:\n"
674  << sksl << "\nSkSL Error:\n"
675  << error_text;
676  }
677 
678  return absl::InternalError("SkSL validation failed.");
679 }
680 
681 Compiler::~Compiler() = default;
682 
683 std::shared_ptr<fml::Mapping> Compiler::GetSPIRVAssembly() const {
684  return spirv_assembly_;
685 }
686 
687 std::shared_ptr<fml::Mapping> Compiler::GetSLShaderSource() const {
688  return sl_mapping_;
689 }
690 
691 bool Compiler::IsValid() const {
692  return is_valid_;
693 }
694 
695 std::string Compiler::GetSourcePrefix() const {
696  std::stringstream stream;
697  stream << options_.file_name << ": ";
698  return stream.str();
699 }
700 
701 std::string Compiler::GetErrorMessages() const {
702  return error_stream_.str();
703 }
704 
706  return verbose_error_stream_.str();
707 }
708 
709 const std::vector<std::string>& Compiler::GetIncludedFileNames() const {
710  return included_file_names_;
711 }
712 
713 static std::string JoinStrings(std::vector<std::string> items,
714  const std::string& separator) {
715  std::stringstream stream;
716  for (size_t i = 0, count = items.size(); i < count; i++) {
717  const auto is_last = (i == count - 1);
718 
719  stream << items[i];
720  if (!is_last) {
721  stream << separator;
722  }
723  }
724  return stream.str();
725 }
726 
727 std::string Compiler::GetDependencyNames(const std::string& separator) const {
728  std::vector<std::string> dependencies = included_file_names_;
729  dependencies.push_back(Utf8FromPath(options_.file_name));
730  return JoinStrings(dependencies, separator);
731 }
732 
733 std::unique_ptr<fml::Mapping> Compiler::CreateDepfileContents(
734  std::initializer_list<std::string> targets_names) const {
735  // https://github.com/ninja-build/ninja/blob/master/src/depfile_parser.cc#L28
736  const auto targets = JoinStrings(targets_names, " ");
737  const auto dependencies = GetDependencyNames(" ");
738 
739  std::stringstream stream;
740  stream << targets << ": " << dependencies << "\n";
741 
742  auto contents = std::make_shared<std::string>(stream.str());
743  return std::make_unique<fml::NonOwnedMapping>(
744  reinterpret_cast<const uint8_t*>(contents->data()), contents->size(),
745  [contents](auto, auto) {});
746 }
747 
749  return reflector_.get();
750 }
751 
752 } // namespace compiler
753 } // namespace impeller
std::string GetVerboseErrorMessages() const
Definition: compiler.cc:705
std::shared_ptr< fml::Mapping > GetSPIRVAssembly() const
Definition: compiler.cc:683
const Reflector * GetReflector() const
Definition: compiler.cc:748
Compiler(const std::shared_ptr< const fml::Mapping > &source_mapping, const SourceOptions &options, Reflector::Options reflector_options)
Definition: compiler.cc:402
const std::vector< std::string > & GetIncludedFileNames() const
Definition: compiler.cc:709
std::unique_ptr< fml::Mapping > CreateDepfileContents(std::initializer_list< std::string > targets) const
Definition: compiler.cc:733
std::shared_ptr< fml::Mapping > GetSLShaderSource() const
Definition: compiler.cc:687
std::string GetErrorMessages() const
Definition: compiler.cc:701
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:280
static const uint32_t kMaxUniformBufferSize
Definition: compiler.cc:103
static CompilerBackend CreateVulkanCompiler(const spirv_cross::ParsedIR &ir, const SourceOptions &source_options)
Definition: compiler.cc:199
constexpr char kExternalTexturePrefix[]
Definition: constants.h:11
static bool EntryPointMustBeNamedMain(TargetPlatform platform)
Definition: compiler.cc:286
static CompilerBackend CreateMSLCompiler(const spirv_cross::ParsedIR &ir, const SourceOptions &source_options, std::optional< uint32_t > msl_version_override={})
Definition: compiler.cc:128
static CompilerBackend CreateCompiler(const spirv_cross::ParsedIR &ir, const SourceOptions &source_options)
Definition: compiler.cc:306
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:713
spirv_cross::CompilerMSL::Options::Platform TargetPlatformToMSLPlatform(TargetPlatform platform)
Definition: types.cc:183
static uint32_t ParseMSLVersion(const std::string &msl_version)
Definition: compiler.cc:105
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:211
spv::ExecutionModel ToExecutionModel(SourceType type)
Definition: types.cc:169
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:121
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