Flutter Engine
The Flutter Engine
spirv_sksl.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
7
8using namespace spv;
9using namespace SPIRV_CROSS_NAMESPACE;
10
11namespace impeller {
12namespace compiler {
13
14// This replaces the SPIRV_CROSS_THROW which aborts and drops the
15// error message in non-debug modes.
16void report_and_exit(const std::string& msg) {
17 fprintf(stderr, "There was a compiler error: %s\n", msg.c_str());
18 fflush(stderr);
19 exit(1);
20}
21
22#define FLUTTER_CROSS_THROW(x) report_and_exit(x)
23
24std::string CompilerSkSL::compile() {
25 ir.fixup_reserved_names();
26
27 if (get_execution_model() != ExecutionModelFragment) {
28 FLUTTER_CROSS_THROW("Only fragment shaders are supported.'");
29 return "";
30 }
31
32 options.es = false;
33 options.version = 100;
34 options.vulkan_semantics = false;
35 options.enable_420pack_extension = false;
36 options.flatten_multidimensional_arrays = true;
37
38 backend.allow_precision_qualifiers = false;
39 backend.basic_int16_type = "short";
40 backend.basic_int_type = "int";
41 backend.basic_uint16_type = "ushort";
42 backend.basic_uint_type = "uint";
43 backend.double_literal_suffix = false;
44 backend.float_literal_suffix = false;
45 backend.long_long_literal_suffix = false;
46 backend.needs_row_major_load_workaround = true;
47 backend.nonuniform_qualifier = "";
48 backend.support_precise_qualifier = false;
49 backend.uint32_t_literal_suffix = false;
50 backend.use_array_constructor = true;
51 backend.workgroup_size_is_hidden = true;
52
53 fixup_user_functions();
54
55 fixup_anonymous_struct_names();
56 fixup_type_alias();
57 reorder_type_alias();
58 build_function_control_flow_graphs_and_analyze();
59 fixup_image_load_store_access();
60 update_active_builtins();
61 analyze_image_and_sampler_usage();
62 analyze_interlocked_resource_usage();
63
64 uint32_t pass_count = 0;
65 do {
66 reset(pass_count);
67
68 // Move constructor for this type is broken on GCC 4.9 ...
69 buffer.reset();
70
71 emit_header();
72 emit_resources();
73
74 emit_function(get<SPIRFunction>(ir.default_entry_point), Bitset());
75
76 pass_count++;
77 } while (is_forcing_recompilation());
78
79 statement("half4 main(float2 iFragCoord)");
80 begin_scope();
81 statement(" flutter_FragCoord = float4(iFragCoord, 0, 0);");
82 statement(" FLT_main();");
83 statement(" return " + output_name_ + ";");
84 end_scope();
85
86 return buffer.str();
87}
88
89void CompilerSkSL::fixup_user_functions() {
90 const std::string prefix = "FLT_flutter_local_";
91 ir.for_each_typed_id<SPIRFunction>([&](uint32_t, const SPIRFunction& func) {
92 const auto& original_name = get_name(func.self);
93 // Just in case. Don't add the prefix a second time.
94 if (original_name.rfind(prefix, 0) == 0) {
95 return;
96 }
97 std::string new_name = prefix + original_name;
98 set_name(func.self, new_name);
99 });
100
101 ir.for_each_typed_id<SPIRFunctionPrototype>(
102 [&](uint32_t, const SPIRFunctionPrototype& func) {
103 const auto& original_name = get_name(func.self);
104 // Just in case. Don't add the prefix a second time.
105 if (original_name.rfind(prefix, 0) == 0) {
106 return;
107 }
108 std::string new_name = prefix + original_name;
109 set_name(func.self, new_name);
110 });
111}
112
113void CompilerSkSL::emit_header() {
114 statement("// This SkSL shader is autogenerated by spirv-cross.");
115 statement("");
116 statement("float4 flutter_FragCoord;");
117 statement("");
118}
119
120void CompilerSkSL::emit_uniform(const SPIRVariable& var) {
121 auto& type = get<SPIRType>(var.basetype);
122 if (type.basetype == SPIRType::UInt && is_legacy()) {
123 FLUTTER_CROSS_THROW("SkSL does not support unsigned integers: '" +
124 get_name(var.self) + "'");
125 }
126
127 add_resource_name(var.self);
128 statement(variable_decl(var), ";");
129
130 // The Flutter FragmentProgram implementation passes additional uniforms along
131 // with shader uniforms that encode the shader width and height.
132 if (type.basetype == SPIRType::SampledImage) {
133 std::string name = to_name(var.self);
134 statement("uniform half2 " + name + "_size;");
135 }
136}
137
138bool CompilerSkSL::emit_constant_resources() {
139 bool emitted = false;
140
141 for (auto& id : ir.ids) {
142 if (id.get_type() == TypeConstant) {
143 auto& c = id.get<SPIRConstant>();
144 bool needs_declaration = c.specialization || c.is_used_as_lut;
145 if (needs_declaration) {
146 if (!options.vulkan_semantics && c.specialization) {
147 c.specialization_constant_macro_name = constant_value_macro_name(
148 get_decoration(c.self, DecorationSpecId));
149 }
150 emit_constant(c);
151 emitted = true;
152 }
153 } else if (id.get_type() == TypeConstantOp) {
154 emit_specialization_constant_op(id.get<SPIRConstantOp>());
155 emitted = true;
156 }
157 }
158
159 return emitted;
160}
161
162bool CompilerSkSL::emit_struct_resources() {
163 bool emitted = false;
164
165 // Output all basic struct types which are not Block or BufferBlock as these
166 // are declared inplace when such variables are instantiated.
167 for (auto& id : ir.ids) {
168 if (id.get_type() == TypeType) {
169 auto& type = id.get<SPIRType>();
170 if (type.basetype == SPIRType::Struct && type.array.empty() &&
171 !type.pointer &&
172 (!ir.meta[type.self].decoration.decoration_flags.get(
173 DecorationBlock) &&
174 !ir.meta[type.self].decoration.decoration_flags.get(
175 DecorationBufferBlock))) {
176 emit_struct(type);
177 emitted = true;
178 }
179 }
180 }
181
182 return emitted;
183}
184
185void CompilerSkSL::detect_unsupported_resources() {
186 for (auto& id : ir.ids) {
187 if (id.get_type() == TypeVariable) {
188 auto& var = id.get<SPIRVariable>();
189 auto& type = get<SPIRType>(var.basetype);
190
191 // UBOs and SSBOs are not supported.
192 if (var.storage != StorageClassFunction && type.pointer &&
193 type.storage == StorageClassUniform && !is_hidden_variable(var) &&
194 (ir.meta[type.self].decoration.decoration_flags.get(
195 DecorationBlock) ||
196 ir.meta[type.self].decoration.decoration_flags.get(
197 DecorationBufferBlock))) {
198 FLUTTER_CROSS_THROW("SkSL does not support UBOs or SSBOs: '" +
199 get_name(var.self) + "'");
200 }
201
202 // Push constant blocks are not supported.
203 if (!is_hidden_variable(var) && var.storage != StorageClassFunction &&
204 type.pointer && type.storage == StorageClassPushConstant) {
205 FLUTTER_CROSS_THROW("SkSL does not support push constant blocks: '" +
206 get_name(var.self) + "'");
207 }
208
209 // User specified inputs are not supported.
210 if (!is_hidden_variable(var) && var.storage != StorageClassFunction &&
211 type.pointer && type.storage == StorageClassInput) {
212 FLUTTER_CROSS_THROW("SkSL does not support inputs: '" +
213 get_name(var.self) + "'");
214 }
215 }
216 }
217}
218
219bool CompilerSkSL::emit_uniform_resources() {
220 bool emitted = false;
221
222 // Output Uniform Constants (values, samplers, images, etc).
223 std::vector<ID> regular_uniforms =
224 SortUniforms(&ir, this, SPIRType::SampledImage, /*include=*/false);
225 std::vector<ID> shader_uniforms =
226 SortUniforms(&ir, this, SPIRType::SampledImage);
227 if (regular_uniforms.size() > 0 || shader_uniforms.size() > 0) {
228 emitted = true;
229 }
230
231 for (const auto& id : regular_uniforms) {
232 auto& var = get<SPIRVariable>(id);
233 emit_uniform(var);
234 }
235
236 for (const auto& id : shader_uniforms) {
237 auto& var = get<SPIRVariable>(id);
238 emit_uniform(var);
239 }
240
241 return emitted;
242}
243
244bool CompilerSkSL::emit_output_resources() {
245 bool emitted = false;
246
247 // Output 'out' variables. These are restricted to the cases handled by
248 // SkSL in 'emit_interface_block'.
249 for (auto& id : ir.ids) {
250 if (id.get_type() == TypeVariable) {
251 auto& var = id.get<SPIRVariable>();
252 auto& type = get<SPIRType>(var.basetype);
253 if (var.storage != StorageClassFunction && !is_hidden_variable(var) &&
254 type.pointer &&
255 (var.storage == StorageClassInput ||
256 var.storage == StorageClassOutput) &&
257 interface_variable_exists_in_entry_point(var.self)) {
258 emit_interface_block(var);
259 emitted = true;
260 }
261 }
262 }
263
264 return emitted;
265}
266
267bool CompilerSkSL::emit_global_variable_resources() {
268 bool emitted = false;
269
270 for (auto global : global_variables) {
271 auto& var = get<SPIRVariable>(global);
272 if (is_hidden_variable(var, true)) {
273 continue;
274 }
275 if (var.storage != StorageClassOutput) {
276 if (!variable_is_lut(var)) {
277 add_resource_name(var.self);
278 std::string initializer;
279 if (options.force_zero_initialized_variables &&
280 var.storage == StorageClassPrivate && !var.initializer &&
281 !var.static_expression &&
282 type_can_zero_initialize(get_variable_data_type(var))) {
283 initializer = join(" = ", to_zero_initialized_expression(
284 get_variable_data_type_id(var)));
285 }
286 statement(variable_decl(var), initializer, ";");
287 emitted = true;
288 }
289 } else if (var.initializer &&
290 maybe_get<SPIRConstant>(var.initializer) != nullptr) {
291 emit_output_variable_initializer(var);
292 }
293 }
294
295 return emitted;
296}
297
298bool CompilerSkSL::emit_undefined_values() {
299 bool emitted = false;
300
301 ir.for_each_typed_id<SPIRUndef>([&](uint32_t, const SPIRUndef& undef) {
302 auto& type = this->get<SPIRType>(undef.basetype);
303 // OpUndef can be void for some reason ...
304 if (type.basetype == SPIRType::Void) {
305 return;
306 }
307
308 std::string initializer;
309 if (options.force_zero_initialized_variables &&
310 type_can_zero_initialize(type)) {
311 initializer = join(" = ", to_zero_initialized_expression(undef.basetype));
312 }
313
314 statement(variable_decl(type, to_name(undef.self), undef.self), initializer,
315 ";");
316 emitted = true;
317 });
318
319 return emitted;
320}
321
322void CompilerSkSL::emit_resources() {
323 detect_unsupported_resources();
324
325 if (emit_constant_resources()) {
326 statement("");
327 }
328
329 if (emit_struct_resources()) {
330 statement("");
331 }
332
333 if (emit_uniform_resources()) {
334 statement("");
335 }
336
337 if (emit_output_resources()) {
338 statement("");
339 }
340
341 if (emit_global_variable_resources()) {
342 statement("");
343 }
344
345 if (emit_undefined_values()) {
346 statement("");
347 }
348}
349
350void CompilerSkSL::emit_interface_block(const SPIRVariable& var) {
351 auto& type = get<SPIRType>(var.basetype);
352 bool block =
353 ir.meta[type.self].decoration.decoration_flags.get(DecorationBlock);
354 if (block) {
355 FLUTTER_CROSS_THROW("Interface blocks are not supported: '" +
356 to_name(var.self) + "'");
357 }
358
359 // The output is emitted as a global variable, which is returned from the
360 // wrapper around the 'main' function. Only one output variable is allowed.
361 add_resource_name(var.self);
362 statement(variable_decl(type, to_name(var.self), var.self), ";");
363 if (output_name_.empty()) {
364 output_name_ = to_name(var.self);
365 } else if (to_name(var.self) != output_name_) {
366 FLUTTER_CROSS_THROW("Only one output variable is supported: '" +
367 to_name(var.self) + "'");
368 }
369}
370
371void CompilerSkSL::emit_function_prototype(SPIRFunction& func,
372 const Bitset& return_flags) {
373 // If this is not the entrypoint, then no special processsing for SkSL is
374 // required.
375 if (func.self != ir.default_entry_point) {
376 CompilerGLSL::emit_function_prototype(func, return_flags);
377 return;
378 }
379
380 auto& type = get<SPIRType>(func.return_type);
381 if (type.basetype != SPIRType::Void) {
383 "Return type of the entrypoint function must be 'void'");
384 }
385
386 if (func.arguments.size() != 0) {
388 "The entry point function should not acept any parameters.");
389 }
390
391 processing_entry_point = true;
392
393 // If this is the entrypoint of a fragment shader, then GLSL requires the
394 // prototype to be "void main()", and so it is safe to rewrite as
395 // "void FLT_main()".
396 statement("void FLT_main()");
397}
398
399std::string CompilerSkSL::image_type_glsl(const SPIRType& type,
400 uint32_t id,
401 bool member) {
402 if (type.basetype != SPIRType::SampledImage || type.image.dim != Dim2D) {
403 FLUTTER_CROSS_THROW("Only sampler2D uniform image types are supported.");
404 return "???";
405 }
406 return "shader";
407}
408
409std::string CompilerSkSL::builtin_to_glsl(BuiltIn builtin,
410 StorageClass storage) {
411 std::string gl_builtin = CompilerGLSL::builtin_to_glsl(builtin, storage);
412 switch (builtin) {
413 case BuiltInFragCoord:
414 return "flutter_FragCoord";
415 default:
416 FLUTTER_CROSS_THROW("Builtin '" + gl_builtin + "' is not supported.");
417 break;
418 }
419
420 return "???";
421}
422
423std::string CompilerSkSL::to_texture_op(
424 const Instruction& i,
425 bool sparse,
426 bool* forward,
427 SmallVector<uint32_t>& inherited_expressions) {
428 auto op = static_cast<Op>(i.op);
429 if (op != OpImageSampleImplicitLod) {
430 FLUTTER_CROSS_THROW("Only simple shader sampling is supported.");
431 return "???";
432 }
433 return CompilerGLSL::to_texture_op(i, sparse, forward, inherited_expressions);
434}
435
436std::string CompilerSkSL::to_function_name(
437 const CompilerGLSL::TextureFunctionNameArguments& args) {
438 std::string name = to_expression(args.base.img);
439 return name + ".eval";
440}
441
442std::string CompilerSkSL::to_function_args(const TextureFunctionArguments& args,
443 bool* p_forward) {
444 std::string name = to_expression(args.base.img);
445
446 std::string glsl_args = CompilerGLSL::to_function_args(args, p_forward);
447 // SkSL only supports coordinates. All other arguments to texture are
448 // unsupported and will generate invalid SkSL.
449 if (args.grad_x || args.grad_y || args.lod || args.offset || args.sample ||
450 args.min_lod || args.sparse_texel || args.bias || args.component) {
452 "Only sampler and position arguments are supported in texture() "
453 "calls.");
454 }
455
456 // GLSL puts the shader as the first argument, but in SkSL the shader is
457 // implicitly passed as the reciever of the 'eval' method. Therefore, the
458 // shader is removed from the GLSL argument list.
459 std::string no_shader;
460 auto npos = glsl_args.find(", "); // The first ','.
461 if (npos != std::string::npos) {
462 no_shader = glsl_args.substr(npos + 1); // The string after the first ','.
463 }
464
465 if (no_shader.empty()) {
466 FLUTTER_CROSS_THROW("Unexpected shader sampling arguments: '(" + glsl_args +
467 ")'");
468 return "()";
469 }
470
471 return name + "_size * (" + no_shader + ")";
472}
473
474} // namespace compiler
475} // namespace impeller
const char * options
const char * backend
m reset()
static struct Initializer initializer
GLenum type
std::string compile() override
Definition: spirv_sksl.cc:24
G_BEGIN_DECLS G_MODULE_EXPORT FlValue * args
exit(kErrorExitCode)
DEF_SWITCHES_START aot vmservice shared library name
Definition: switches.h:32
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir Path to the cache directory This is different from the persistent_cache_path in embedder which is used for Skia shader cache icu native lib Path to the library file that exports the ICU data vm service The hostname IP address on which the Dart VM Service should be served If not defaults to or::depending on whether ipv6 is specified vm service A custom Dart VM Service port The default is to pick a randomly available open port disable vm Disable the Dart VM Service The Dart VM Service is never available in release mode disable vm service Disable mDNS Dart VM Service publication Bind to the IPv6 localhost address for the Dart VM Service Ignored if vm service host is set endless trace buffer
Definition: switches.h:126
void report_and_exit(const std::string &msg)
Definition: spirv_sksl.cc:16
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.
static SkString join(const CommandLineFlags::StringArray &)
Definition: skpbench.cpp:741
#define FLUTTER_CROSS_THROW(x)
Definition: spirv_sksl.cc:22