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