Flutter Engine Uber Docs
Docs for the entire Flutter Engine repo.
 
Loading...
Searching...
No Matches
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
28namespace impeller {
29namespace compiler {
30
31namespace {
32constexpr const char* kEGLImageExternalExtension = "GL_OES_EGL_image_external";
33constexpr 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
40static const uint32_t kMaxUniformBufferSize = 6208;
41
42static 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
148static 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;
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) {
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
206static 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
231
232static 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
267namespace {
268uint32_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
282Compiler::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) {
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;
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;
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");
369 source_options.target_platform ==
371 spirv_options.macro_definitions.push_back("IMPELLER_TARGET_OPENGLES");
372 }
373 } break;
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, //
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
500Compiler::~Compiler() = default;
501
502std::shared_ptr<fml::Mapping> Compiler::GetSPIRVAssembly() const {
503 return spirv_assembly_;
504}
505
506std::shared_ptr<fml::Mapping> Compiler::GetSLShaderSource() const {
507 return sl_mapping_;
508}
509
510bool Compiler::IsValid() const {
511 return is_valid_;
512}
513
514std::string Compiler::GetSourcePrefix() const {
515 std::stringstream stream;
516 stream << options_.file_name << ": ";
517 return stream.str();
518}
519
520std::string Compiler::GetErrorMessages() const {
521 return error_stream_.str();
522}
523
524const std::vector<std::string>& Compiler::GetIncludedFileNames() const {
525 return included_file_names_;
526}
527
528static 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
542std::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
548std::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
uint32_t * target
#define FML_UNREACHABLE()
Definition logging.h:128
#define COMPILER_ERROR(stream)
Definition logger.h:39
std::array< MockImage, 3 > images
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.
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