| Flutter Impeller |

reflector.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
5 // FLUTTER_NOLINT: https://github.com/flutter/flutter/issues/105732
6
7 #include "impeller/compiler/reflector.h"
8
9 #include <atomic>
10 #include <format>
11 #include <optional>
12 #include <set>
13 #include <sstream>
14
15 #include "flutter/fml/logging.h"
16 #include "fml/backtrace.h"
17 #include "impeller/base/strings.h"
18 #include "impeller/base/validation.h"
19 #include "impeller/compiler/code_gen_template.h"
20 #include "impeller/compiler/shader_bundle_data.h"
21 #include "impeller/compiler/types.h"
22 #include "impeller/compiler/uniform_sorter.h"
23 #include "impeller/compiler/utilities.h"
24 #include "impeller/core/runtime_types.h"
25 #include "impeller/geometry/half.h"
26 #include "impeller/geometry/matrix.h"
27 #include "impeller/geometry/scalar.h"
28 #include "impeller/runtime_stage/runtime_stage.h"
29 #include "spirv_common.hpp"
30
31 namespace impeller {
32 namespace compiler {
33
34 static std::string ExecutionModelToString(spv::ExecutionModel model) {
35 switch (model) {
36 case spv::ExecutionModel::ExecutionModelVertex:
37 return "vertex";
38 case spv::ExecutionModel::ExecutionModelFragment:
39 return "fragment";
40 case spv::ExecutionModel::ExecutionModelGLCompute:
41 return "compute";
42 default:
43 return "unsupported";
44 }
45 }
46
47 static std::string StringToShaderStage(const std::string& str) {
48 if (str == "vertex") {
49 return "ShaderStage::kVertex";
50 }
51
52 if (str == "fragment") {
53 return "ShaderStage::kFragment";
54 }
55
56 if (str == "compute") {
57 return "ShaderStage::kCompute";
58 }
59
60 return "ShaderStage::kUnknown";
61 }
62
63 Reflector::Reflector(Options options,
64 const std::shared_ptr<const spirv_cross::ParsedIR>& ir,
65 const std::shared_ptr<fml::Mapping>& shader_data,
66 const CompilerBackend& compiler)
67 : options_(std::move(options)),
68 ir_(ir),
69 shader_data_(shader_data),
70 compiler_(compiler) {
71 if (!ir_ || !compiler_) {
72 return;
73 }
74
75 if (auto template_arguments = GenerateTemplateArguments();
76 template_arguments.has_value()) {
77 template_arguments_ =
78 std::make_unique<nlohmann::json>(std::move(template_arguments.value()));
79 } else {
80 return;
81 }
82
83 reflection_header_ = GenerateReflectionHeader();
84 if (!reflection_header_) {
85 return;
86 }
87
88 reflection_cc_ = GenerateReflectionCC();
89 if (!reflection_cc_) {
90 return;
91 }
92
93 runtime_stage_shader_ = GenerateRuntimeStageData();
94
95 shader_bundle_data_ = GenerateShaderBundleData();
96 if (!shader_bundle_data_) {
97 return;
98 }
99
100 is_valid_ = true;
101 }
102
103 Reflector::~Reflector() = default;
104
105 bool Reflector::IsValid() const {
106 return is_valid_;
107 }
108
109 std::shared_ptr<fml::Mapping> Reflector::GetReflectionJSON() const {
110 if (!is_valid_) {
111 return nullptr;
112 }
113
114 auto json_string =
115 std::make_shared<std::string>(template_arguments_->dump(2u));
116
117 return std::make_shared<fml::NonOwnedMapping>(
118 reinterpret_cast<const uint8_t*>(json_string->data()),
119 json_string->size(), [json_string](auto, auto) {});
120 }
121
122 std::shared_ptr<fml::Mapping> Reflector::GetReflectionHeader() const {
123 return reflection_header_;
124 }
125
126 std::shared_ptr<fml::Mapping> Reflector::GetReflectionCC() const {
127 return reflection_cc_;
128 }
129
130 std::shared_ptr<RuntimeStageData::Shader> Reflector::GetRuntimeStageShaderData()
131 const {
132 return runtime_stage_shader_;
133 }
134
135 std::shared_ptr<ShaderBundleData> Reflector::GetShaderBundleData() const {
136 return shader_bundle_data_;
137 }
138
139 std::optional<nlohmann::json> Reflector::GenerateTemplateArguments() const {
140 nlohmann::json root;
141
142 const auto& entrypoints = compiler_->get_entry_points_and_stages();
143 if (entrypoints.size() != 1) {
144 VALIDATION_LOG << "Incorrect number of entrypoints in the shader. Found "
145 << entrypoints.size() << " but expected 1.";
146 return std::nullopt;
147 }
148
149 auto execution_model = entrypoints.front().execution_model;
150 {
151 root["entrypoint"] = options_.entry_point_name;
152 root["shader_name"] = options_.shader_name;
153 root["shader_stage"] = ExecutionModelToString(execution_model);
154 root["header_file_name"] = options_.header_file_name;
155 }
156
157 const auto shader_resources = compiler_->get_shader_resources();
158
159 // Subpass Inputs.
160 {
161 auto& subpass_inputs = root["subpass_inputs"] = nlohmann::json::array_t{};
162 if (auto subpass_inputs_json =
163 ReflectResources(shader_resources.subpass_inputs);
164 subpass_inputs_json.has_value()) {
165 for (auto subpass_input : subpass_inputs_json.value()) {
166 subpass_input["descriptor_type"] = "DescriptorType::kInputAttachment";
167 subpass_inputs.emplace_back(std::move(subpass_input));
168 }
169 } else {
170 return std::nullopt;
171 }
172 }
173
174 // Uniform and storage buffers.
175 {
176 auto& buffers = root["buffers"] = nlohmann::json::array_t{};
177 if (auto uniform_buffers_json =
178 ReflectResources(shader_resources.uniform_buffers);
179 uniform_buffers_json.has_value()) {
180 for (auto uniform_buffer : uniform_buffers_json.value()) {
181 uniform_buffer["descriptor_type"] = "DescriptorType::kUniformBuffer";
182 buffers.emplace_back(std::move(uniform_buffer));
183 }
184 } else {
185 return std::nullopt;
186 }
187 if (auto storage_buffers_json =
188 ReflectResources(shader_resources.storage_buffers);
189 storage_buffers_json.has_value()) {
190 for (auto uniform_buffer : storage_buffers_json.value()) {
191 uniform_buffer["descriptor_type"] = "DescriptorType::kStorageBuffer";
192 buffers.emplace_back(std::move(uniform_buffer));
193 }
194 } else {
195 return std::nullopt;
196 }
197 }
198
199 {
200 auto& uniforms = root["uniforms"] = nlohmann::json::array_t{};
201 if (auto uniforms_json =
202 ReflectResources(shader_resources.gl_plain_uniforms);
203 uniforms_json.has_value()) {
204 for (auto uniform : uniforms_json.value()) {
205 uniform["descriptor_type"] = "DescriptorType::kUniform";
206 uniforms.emplace_back(std::move(uniform));
207 }
208 } else {
209 return std::nullopt;
210 }
211 }
212
213 {
214 auto& stage_inputs = root["stage_inputs"] = nlohmann::json::array_t{};
215 if (auto stage_inputs_json = ReflectResources(
216 shader_resources.stage_inputs,
217 /*compute_offsets=*/execution_model == spv::ExecutionModelVertex);
218 stage_inputs_json.has_value()) {
219 stage_inputs = std::move(stage_inputs_json.value());
220 } else {
221 return std::nullopt;
222 }
223 }
224
225 {
226 auto combined_sampled_images =
227 ReflectResources(shader_resources.sampled_images);
228 auto images = ReflectResources(shader_resources.separate_images);
229 auto samplers = ReflectResources(shader_resources.separate_samplers);
230 if (!combined_sampled_images.has_value() || !images.has_value() ||
231 !samplers.has_value()) {
232 return std::nullopt;
233 }
234 auto& sampled_images = root["sampled_images"] = nlohmann::json::array_t{};
235 for (auto value : combined_sampled_images.value()) {
236 value["descriptor_type"] = "DescriptorType::kSampledImage";
237 sampled_images.emplace_back(std::move(value));
238 }
239 for (auto value : images.value()) {
240 value["descriptor_type"] = "DescriptorType::kImage";
241 sampled_images.emplace_back(std::move(value));
242 }
243 for (auto value : samplers.value()) {
244 value["descriptor_type"] = "DescriptorType::kSampledSampler";
245 sampled_images.emplace_back(std::move(value));
246 }
247 }
248
249 if (auto stage_outputs = ReflectResources(shader_resources.stage_outputs);
250 stage_outputs.has_value()) {
251 root["stage_outputs"] = std::move(stage_outputs.value());
252 } else {
253 return std::nullopt;
254 }
255
256 {
257 auto& struct_definitions = root["struct_definitions"] =
258 nlohmann::json::array_t{};
259 if (entrypoints.front().execution_model ==
260 spv::ExecutionModel::ExecutionModelVertex &&
261 !shader_resources.stage_inputs.empty()) {
262 if (auto struc =
263 ReflectPerVertexStructDefinition(shader_resources.stage_inputs);
264 struc.has_value()) {
265 struct_definitions.emplace_back(EmitStructDefinition(struc.value()));
266 } else {
267 // If there are stage inputs, it is an error to not generate a per
268 // vertex data struct for a vertex like shader stage.
269 return std::nullopt;
270 }
271 }
272
273 std::set<spirv_cross::ID> known_structs;
274 ir_->for_each_typed_id<spirv_cross::SPIRType>(
275 [&](uint32_t, const spirv_cross::SPIRType& type) {
276 if (type.basetype != spirv_cross::SPIRType::BaseType::Struct) {
277 return;
278 }
279 // Skip structs that do not have layout offset decorations.
280 // These structs are used internally within the shader and are not
281 // part of the shader's interface.
282 for (size_t i = 0; i < type.member_types.size(); i++) {
283 if (!compiler_->has_member_decoration(type.self, i,
284 spv::DecorationOffset)) {
285 return;
286 }
287 }
288 if (known_structs.find(type.self) != known_structs.end()) {
289 // Iterating over types this way leads to duplicates which may cause
290 // duplicate struct definitions.
291 return;
292 }
293 known_structs.insert(type.self);
294 if (auto struc = ReflectStructDefinition(type.self);
295 struc.has_value()) {
296 struct_definitions.emplace_back(
297 EmitStructDefinition(struc.value()));
298 }
299 });
300 }
301
302 root["bind_prototypes"] =
303 EmitBindPrototypes(shader_resources, execution_model);
304
305 return root;
306 }
307
308 std::shared_ptr<fml::Mapping> Reflector::GenerateReflectionHeader() const {
309 return InflateTemplate(kReflectionHeaderTemplate);
310 }
311
312 std::shared_ptr<fml::Mapping> Reflector::GenerateReflectionCC() const {
313 return InflateTemplate(kReflectionCCTemplate);
314 }
315
316 static std::optional<RuntimeStageBackend> GetRuntimeStageBackend(
317 TargetPlatform target_platform) {
318 switch (target_platform) {
319 case TargetPlatform::kUnknown:
320 case TargetPlatform::kMetalDesktop:
321 case TargetPlatform::kMetalIOS:
322 case TargetPlatform::kOpenGLES:
323 case TargetPlatform::kOpenGLDesktop:
324 case TargetPlatform::kVulkan:
325 return std::nullopt;
326 case TargetPlatform::kRuntimeStageMetal:
327 return RuntimeStageBackend::kMetal;
328 case TargetPlatform::kRuntimeStageGLES:
329 return RuntimeStageBackend::kOpenGLES;
330 case TargetPlatform::kRuntimeStageGLES3:
331 return RuntimeStageBackend::kOpenGLES3;
332 case TargetPlatform::kRuntimeStageVulkan:
333 return RuntimeStageBackend::kVulkan;
334 case TargetPlatform::kSkSL:
335 return RuntimeStageBackend::kSkSL;
336 }
337 FML_UNREACHABLE();
338 }
339
340 std::shared_ptr<RuntimeStageData::Shader> Reflector::GenerateRuntimeStageData()
341 const {
342 auto backend = GetRuntimeStageBackend(options_.target_platform);
343 if (!backend.has_value()) {
344 return nullptr;
345 }
346
347 const auto& entrypoints = compiler_->get_entry_points_and_stages();
348 if (entrypoints.size() != 1u) {
349 VALIDATION_LOG << "Single entrypoint not found.";
350 return nullptr;
351 }
352 auto data = std::make_unique<RuntimeStageData::Shader>();
353 data->entrypoint = options_.entry_point_name;
354 data->stage = entrypoints.front().execution_model;
355 data->shader = shader_data_;
356 data->backend = backend.value();
357
358 // Sort the IR so that the uniforms are in declaration order.
359 std::vector<spirv_cross::ID> uniforms =
360 SortUniforms(ir_.get(), compiler_.GetCompiler());
361 for (auto& sorted_id : uniforms) {
362 auto var = ir_->ids[sorted_id].get<spirv_cross::SPIRVariable>();
363 const auto spir_type = compiler_->get_type(var.basetype);
364 UniformDescription uniform_description;
365 uniform_description.name = compiler_->get_name(var.self);
366 uniform_description.location = compiler_->get_decoration(
367 var.self, spv::Decoration::DecorationLocation);
368 uniform_description.binding =
369 compiler_->get_decoration(var.self, spv::Decoration::DecorationBinding);
370 uniform_description.type = spir_type.basetype;
371 uniform_description.rows = spir_type.vecsize;
372 uniform_description.columns = spir_type.columns;
373 uniform_description.bit_width = spir_type.width;
374 uniform_description.array_elements = GetArrayElements(spir_type);
375 FML_CHECK(data->backend != RuntimeStageBackend::kVulkan ||
376 spir_type.basetype ==
377 spirv_cross::SPIRType::BaseType::SampledImage)
378 << "Vulkan runtime effect had unexpected uniforms outside of the "
379 "uniform buffer object.";
380 data->uniforms.emplace_back(std::move(uniform_description));
381 }
382
383 const auto ubos = compiler_->get_shader_resources().uniform_buffers;
384 if (data->backend == RuntimeStageBackend::kVulkan && !ubos.empty()) {
385 if (ubos.size() != 1 && ubos[0].name != RuntimeStage::kVulkanUBOName) {
386 VALIDATION_LOG << "Expected a single UBO resource named "
387 "'"
388 << RuntimeStage::kVulkanUBOName
389 << "' "
390 "for Vulkan runtime stage backend.";
391 return nullptr;
392 }
393
394 const auto& ubo = ubos[0];
395
396 size_t binding =
397 compiler_->get_decoration(ubo.id, spv::Decoration::DecorationBinding);
398 auto members = ReadStructMembers(ubo.type_id);
399 std::vector<uint8_t> struct_layout;
400 size_t float_count = 0;
401
402 for (size_t i = 0; i < members.size(); i += 1) {
403 const auto& member = members[i];
404 std::vector<int> bytes;
405 switch (member.underlying_type) {
406 case StructMember::UnderlyingType::kPadding: {
407 size_t padding_count =
408 (member.size + sizeof(float) - 1) / sizeof(float);
409 while (padding_count > 0) {
410 struct_layout.push_back(0);
411 padding_count--;
412 }
413 break;
414 }
415 case StructMember::UnderlyingType::kFloat: {
416 if (member.array_elements > 1) {
417 // For each array element member, insert 1 layout property per byte
418 // and 0 layout property per byte of padding
419 for (auto i = 0; i < member.array_elements; i++) {
420 for (auto j = 0u; j < member.size / sizeof(float); j++) {
421 struct_layout.push_back(1);
422 }
423 for (auto j = 0u; j < member.element_padding / sizeof(float);
424 j++) {
425 struct_layout.push_back(0);
426 }
427 }
428 } else {
429 size_t member_float_count = member.byte_length / sizeof(float);
430 float_count += member_float_count;
431 while (member_float_count > 0) {
432 struct_layout.push_back(1);
433 member_float_count--;
434 }
435 }
436 break;
437 }
438 case StructMember::UnderlyingType::kOther:
439 VALIDATION_LOG << "Non-floating-type struct member " << member.name
440 << " is not supported.";
441 return nullptr;
442 }
443 }
444 data->uniforms.emplace_back(UniformDescription{
445 .name = ubo.name,
446 .location = binding,
447 .binding = binding,
448 .type = spirv_cross::SPIRType::Struct,
449 .struct_layout = std::move(struct_layout),
450 .struct_float_count = float_count,
451 });
452 }
453
454 // We only need to worry about storing vertex attributes.
455 if (entrypoints.front().execution_model == spv::ExecutionModelVertex) {
456 const auto inputs = compiler_->get_shader_resources().stage_inputs;
457 auto input_offsets = ComputeOffsets(inputs);
458 for (const auto& input : inputs) {
459 std::optional<size_t> offset = GetOffset(input.id, input_offsets);
460
461 const auto type = compiler_->get_type(input.type_id);
462
463 InputDescription input_description;
464 input_description.name = input.name;
465 input_description.location = compiler_->get_decoration(
466 input.id, spv::Decoration::DecorationLocation);
467 input_description.set = compiler_->get_decoration(
468 input.id, spv::Decoration::DecorationDescriptorSet);
469 input_description.binding = compiler_->get_decoration(
470 input.id, spv::Decoration::DecorationBinding);
471 input_description.type = type.basetype;
472 input_description.bit_width = type.width;
473 input_description.vec_size = type.vecsize;
474 input_description.columns = type.columns;
475 input_description.offset = offset.value_or(0u);
476 data->inputs.emplace_back(std::move(input_description));
477 }
478 }
479
480 return data;
481 }
482
483 std::shared_ptr<ShaderBundleData> Reflector::GenerateShaderBundleData() const {
484 const auto& entrypoints = compiler_->get_entry_points_and_stages();
485 if (entrypoints.size() != 1u) {
486 VALIDATION_LOG << "Single entrypoint not found.";
487 return nullptr;
488 }
489 auto data = std::make_shared<ShaderBundleData>(
490 options_.entry_point_name, //
491 entrypoints.front().execution_model, //
492 options_.target_platform //
493 );
494 data->SetShaderData(shader_data_);
495
496 const auto uniforms = compiler_->get_shader_resources().uniform_buffers;
497 for (const auto& uniform : uniforms) {
498 ShaderBundleData::ShaderUniformStruct uniform_struct;
499 uniform_struct.name = uniform.name;
500 uniform_struct.ext_res_0 = compiler_.GetExtendedMSLResourceBinding(
501 CompilerBackend::ExtendedResourceIndex::kPrimary, uniform.id);
502 uniform_struct.set = compiler_->get_decoration(
503 uniform.id, spv::Decoration::DecorationDescriptorSet);
504 uniform_struct.binding = compiler_->get_decoration(
505 uniform.id, spv::Decoration::DecorationBinding);
506
507 const auto type = compiler_->get_type(uniform.type_id);
508 if (type.basetype != spirv_cross::SPIRType::BaseType::Struct) {
509 std::cerr << "Error: Uniform \"" << uniform.name
510 << "\" is not a struct. All Flutter GPU shader uniforms must "
511 "be structs."
512 << std::endl;
513 return nullptr;
514 }
515
516 size_t size_in_bytes = 0;
517 for (const auto& struct_member : ReadStructMembers(uniform.type_id)) {
518 size_in_bytes += struct_member.byte_length;
519 if (StringStartsWith(struct_member.name, "_PADDING_")) {
520 continue;
521 }
522 ShaderBundleData::ShaderUniformStructField uniform_struct_field;
523 uniform_struct_field.name = struct_member.name;
524 uniform_struct_field.type = struct_member.base_type;
525 uniform_struct_field.offset_in_bytes = struct_member.offset;
526 uniform_struct_field.element_size_in_bytes = struct_member.size;
527 uniform_struct_field.total_size_in_bytes = struct_member.byte_length;
528 uniform_struct_field.array_elements = struct_member.array_elements;
529 uniform_struct.fields.push_back(uniform_struct_field);
530 }
531 uniform_struct.size_in_bytes = size_in_bytes;
532
533 data->AddUniformStruct(uniform_struct);
534 }
535
536 const auto sampled_images = compiler_->get_shader_resources().sampled_images;
537 for (const auto& image : sampled_images) {
538 ShaderBundleData::ShaderUniformTexture uniform_texture;
539 uniform_texture.name = image.name;
540 uniform_texture.ext_res_0 = compiler_.GetExtendedMSLResourceBinding(
541 CompilerBackend::ExtendedResourceIndex::kPrimary, image.id);
542 uniform_texture.set = compiler_->get_decoration(
543 image.id, spv::Decoration::DecorationDescriptorSet);
544 uniform_texture.binding =
545 compiler_->get_decoration(image.id, spv::Decoration::DecorationBinding);
546 data->AddUniformTexture(uniform_texture);
547 }
548
549 // We only need to worry about storing vertex attributes.
550 if (entrypoints.front().execution_model == spv::ExecutionModelVertex) {
551 const auto inputs = compiler_->get_shader_resources().stage_inputs;
552 auto input_offsets = ComputeOffsets(inputs);
553 for (const auto& input : inputs) {
554 std::optional<size_t> offset = GetOffset(input.id, input_offsets);
555
556 const auto type = compiler_->get_type(input.type_id);
557
558 InputDescription input_description;
559 input_description.name = input.name;
560 input_description.location = compiler_->get_decoration(
561 input.id, spv::Decoration::DecorationLocation);
562 input_description.set = compiler_->get_decoration(
563 input.id, spv::Decoration::DecorationDescriptorSet);
564 input_description.binding = compiler_->get_decoration(
565 input.id, spv::Decoration::DecorationBinding);
566 input_description.type = type.basetype;
567 input_description.bit_width = type.width;
568 input_description.vec_size = type.vecsize;
569 input_description.columns = type.columns;
570 input_description.offset = offset.value_or(0u);
571 data->AddInputDescription(std::move(input_description));
572 }
573 }
574
575 return data;
576 }
577
578 std::optional<uint32_t> Reflector::GetArrayElements(
579 const spirv_cross::SPIRType& type) const {
580 if (type.array.empty()) {
581 return std::nullopt;
582 }
583 FML_CHECK(type.array.size() == 1)
584 << "Multi-dimensional arrays are not supported.";
585 FML_CHECK(type.array_size_literal.front())
586 << "Must use a literal for array sizes.";
587 return type.array.front();
588 }
589
590 static std::string ToString(CompilerBackend::Type type) {
591 switch (type) {
592 case CompilerBackend::Type::kMSL:
593 return "Metal Shading Language";
594 case CompilerBackend::Type::kGLSL:
595 return "OpenGL Shading Language";
596 case CompilerBackend::Type::kGLSLVulkan:
597 return "OpenGL Shading Language (Relaxed Vulkan Semantics)";
598 case CompilerBackend::Type::kSkSL:
599 return "SkSL Shading Language";
600 }
601 FML_UNREACHABLE();
602 }
603
604 std::shared_ptr<fml::Mapping> Reflector::InflateTemplate(
605 std::string_view tmpl) const {
606 inja::Environment env;
607 env.set_trim_blocks(true);
608 env.set_lstrip_blocks(true);
609
610 env.add_callback("camel_case", 1u, [](inja::Arguments& args) {
611 return ToCamelCase(args.at(0u)->get<std::string>());
612 });
613
614 env.add_callback("to_shader_stage", 1u, [](inja::Arguments& args) {
615 return StringToShaderStage(args.at(0u)->get<std::string>());
616 });
617
618 env.add_callback("get_generator_name", 0u,
619 [type = compiler_.GetType()](inja::Arguments& args) {
620 return ToString(type);
621 });
622
623 auto inflated_template =
624 std::make_shared<std::string>(env.render(tmpl, *template_arguments_));
625
626 return std::make_shared<fml::NonOwnedMapping>(
627 reinterpret_cast<const uint8_t*>(inflated_template->data()),
628 inflated_template->size(), [inflated_template](auto, auto) {});
629 }
630
631 std::vector<size_t> Reflector::ComputeOffsets(
632 const spirv_cross::SmallVector<spirv_cross::Resource>& resources) const {
633 std::vector<size_t> offsets(resources.size(), 0);
634 if (resources.size() == 0) {
635 return offsets;
636 }
637 for (const auto& resource : resources) {
638 const auto type = compiler_->get_type(resource.type_id);
639 auto location = compiler_->get_decoration(
640 resource.id, spv::Decoration::DecorationLocation);
641 // Malformed shader, will be caught later on.
642 if (location >= resources.size() || location < 0) {
643 location = 0;
644 }
645 offsets[location] = (type.width * type.vecsize) / 8;
646 }
647 for (size_t i = 1; i < resources.size(); i++) {
648 offsets[i] += offsets[i - 1];
649 }
650 for (size_t i = resources.size() - 1; i > 0; i--) {
651 offsets[i] = offsets[i - 1];
652 }
653 offsets[0] = 0;
654
655 return offsets;
656 }
657
658 std::optional<size_t> Reflector::GetOffset(
659 spirv_cross::ID id,
660 const std::vector<size_t>& offsets) const {
661 uint32_t location =
662 compiler_->get_decoration(id, spv::Decoration::DecorationLocation);
663 if (location >= offsets.size()) {
664 return std::nullopt;
665 }
666 return offsets[location];
667 }
668
669 std::optional<nlohmann::json::object_t> Reflector::ReflectResource(
670 const spirv_cross::Resource& resource,
671 std::optional<size_t> offset) const {
672 nlohmann::json::object_t result;
673
674 result["name"] = resource.name;
675 result["descriptor_set"] = compiler_->get_decoration(
676 resource.id, spv::Decoration::DecorationDescriptorSet);
677 result["binding"] = compiler_->get_decoration(
678 resource.id, spv::Decoration::DecorationBinding);
679 result["set"] = compiler_->get_decoration(
680 resource.id, spv::Decoration::DecorationDescriptorSet);
681 result["location"] = compiler_->get_decoration(
682 resource.id, spv::Decoration::DecorationLocation);
683 result["index"] =
684 compiler_->get_decoration(resource.id, spv::Decoration::DecorationIndex);
685 result["ext_res_0"] = compiler_.GetExtendedMSLResourceBinding(
686 CompilerBackend::ExtendedResourceIndex::kPrimary, resource.id);
687 result["ext_res_1"] = compiler_.GetExtendedMSLResourceBinding(
688 CompilerBackend::ExtendedResourceIndex::kSecondary, resource.id);
689 result["relaxed_precision"] =
690 compiler_->get_decoration(
691 resource.id, spv::Decoration::DecorationRelaxedPrecision) == 1;
692 result["offset"] = offset.value_or(0u);
693 auto type = ReflectType(resource.type_id);
694 if (!type.has_value()) {
695 return std::nullopt;
696 }
697 result["type"] = std::move(type.value());
698 return result;
699 }
700
701 std::optional<nlohmann::json::object_t> Reflector::ReflectType(
702 const spirv_cross::TypeID& type_id) const {
703 nlohmann::json::object_t result;
704
705 const auto type = compiler_->get_type(type_id);
706
707 result["type_name"] = StructMember::BaseTypeToString(type.basetype);
708 result["bit_width"] = type.width;
709 result["vec_size"] = type.vecsize;
710 result["columns"] = type.columns;
711 auto& members = result["members"] = nlohmann::json::array_t{};
712 if (type.basetype == spirv_cross::SPIRType::BaseType::Struct) {
713 for (const auto& struct_member : ReadStructMembers(type_id)) {
714 auto member = nlohmann::json::object_t{};
715 member["name"] = struct_member.name;
716 member["type"] = struct_member.type;
717 member["base_type"] =
718 StructMember::BaseTypeToString(struct_member.base_type);
719 member["offset"] = struct_member.offset;
720 member["size"] = struct_member.size;
721 member["byte_length"] = struct_member.byte_length;
722 if (struct_member.array_elements.has_value()) {
723 member["array_elements"] = struct_member.array_elements.value();
724 } else {
725 member["array_elements"] = "std::nullopt";
726 }
727 members.emplace_back(std::move(member));
728 }
729 }
730
731 return result;
732 }
733
734 std::optional<nlohmann::json::array_t> Reflector::ReflectResources(
735 const spirv_cross::SmallVector<spirv_cross::Resource>& resources,
736 bool compute_offsets) const {
737 nlohmann::json::array_t result;
738 result.reserve(resources.size());
739 std::vector<size_t> offsets;
740 if (compute_offsets) {
741 offsets = ComputeOffsets(resources);
742 }
743 for (const auto& resource : resources) {
744 std::optional<size_t> maybe_offset = std::nullopt;
745 if (compute_offsets) {
746 maybe_offset = GetOffset(resource.id, offsets);
747 }
748 if (auto reflected = ReflectResource(resource, maybe_offset);
749 reflected.has_value()) {
750 result.emplace_back(std::move(reflected.value()));
751 } else {
752 return std::nullopt;
753 }
754 }
755 return result;
756 }
757
758 static std::string TypeNameWithPaddingOfSize(size_t size) {
759 std::stringstream stream;
760 stream << "Padding<" << size << ">";
761 return stream.str();
762 }
763
764 struct KnownType {
765 std::string name;
766 size_t byte_size = 0;
767 };
768
769 static std::optional<KnownType> ReadKnownScalarType(
770 spirv_cross::SPIRType::BaseType type) {
771 switch (type) {
772 case spirv_cross::SPIRType::BaseType::Boolean:
773 return KnownType{
774 .name = "bool",
775 .byte_size = sizeof(bool),
776 };
777 case spirv_cross::SPIRType::BaseType::Float:
778 return KnownType{
779 .name = "Scalar",
780 .byte_size = sizeof(Scalar),
781 };
782 case spirv_cross::SPIRType::BaseType::Half:
783 return KnownType{
784 .name = "Half",
785 .byte_size = sizeof(Half),
786 };
787 case spirv_cross::SPIRType::BaseType::UInt:
788 return KnownType{
789 .name = "uint32_t",
790 .byte_size = sizeof(uint32_t),
791 };
792 case spirv_cross::SPIRType::BaseType::Int:
793 return KnownType{
794 .name = "int32_t",
795 .byte_size = sizeof(int32_t),
796 };
797 default:
798 break;
799 }
800 return std::nullopt;
801 }
802
803 //------------------------------------------------------------------------------
804 /// @brief Get the reflected struct size. In the vast majority of the
805 /// cases, this is the same as the declared struct size as given by
806 /// the compiler. But, additional padding may need to be introduced
807 /// after the end of the struct to keep in line with the alignment
808 /// requirement of the individual struct members. This method
809 /// figures out the actual size of the reflected struct that can be
810 /// referenced in native code.
811 ///
812 /// @param[in] members The members
813 ///
814 /// @return The reflected structure size.
815 ///
816 static size_t GetReflectedStructSize(const std::vector<StructMember>& members) {
817 auto struct_size = 0u;
818 for (const auto& member : members) {
819 struct_size += member.byte_length;
820 }
821 return struct_size;
822 }
823
824 std::vector<StructMember> Reflector::ReadStructMembers(
825 const spirv_cross::TypeID& type_id) const {
826 const auto& struct_type = compiler_->get_type(type_id);
827 FML_CHECK(struct_type.basetype == spirv_cross::SPIRType::BaseType::Struct);
828
829 std::vector<StructMember> result;
830
831 size_t current_byte_offset = 0;
832 size_t max_member_alignment = 0;
833
834 for (size_t i = 0; i < struct_type.member_types.size(); i++) {
835 const auto& member = compiler_->get_type(struct_type.member_types[i]);
836 const auto struct_member_offset =
837 compiler_->type_struct_member_offset(struct_type, i);
838 auto array_elements = GetArrayElements(member);
839
840 if (struct_member_offset > current_byte_offset) {
841 const auto alignment_pad = struct_member_offset - current_byte_offset;
842 result.emplace_back(StructMember{
843 TypeNameWithPaddingOfSize(alignment_pad), // type
844 spirv_cross::SPIRType::BaseType::Void, // basetype
845 std::format("_PADDING_{}_",
846 GetMemberNameAtIndex(struct_type, i)), // name
847 current_byte_offset, // offset
848 alignment_pad, // size
849 alignment_pad, // byte_length
850 std::nullopt, // array_elements
851 0, // element_padding
852 });
853 current_byte_offset += alignment_pad;
854 }
855
856 max_member_alignment =
857 std::max<size_t>(max_member_alignment,
858 (member.width / 8) * member.columns * member.vecsize);
859
860 FML_CHECK(current_byte_offset == struct_member_offset);
861
862 // A user defined struct.
863 if (member.basetype == spirv_cross::SPIRType::BaseType::Struct) {
864 const size_t size =
865 GetReflectedStructSize(ReadStructMembers(member.self));
866 uint32_t stride = GetArrayStride<0>(struct_type, member, i);
867 if (stride == 0) {
868 stride = size;
869 }
870 uint32_t element_padding = stride - size;
871 result.emplace_back(StructMember{
872 compiler_->get_name(member.self), // type
873 member.basetype, // basetype
874 GetMemberNameAtIndex(struct_type, i), // name
875 struct_member_offset, // offset
876 size, // size
877 stride * array_elements.value_or(1), // byte_length
878 array_elements, // array_elements
879 element_padding, // element_padding
880 });
881 current_byte_offset += stride * array_elements.value_or(1);
882 continue;
883 }
884
885 // Tightly packed 4x4 Matrix is special cased as we know how to work with
886 // those.
887 if (member.basetype == spirv_cross::SPIRType::BaseType::Float && //
888 member.width == sizeof(Scalar) * 8 && //
889 member.columns == 4 && //
890 member.vecsize == 4 //
891 ) {
892 uint32_t stride = GetArrayStride<sizeof(Matrix)>(struct_type, member, i);
893 uint32_t element_padding = stride - sizeof(Matrix);
894 result.emplace_back(StructMember{
895 "Matrix", // type
896 member.basetype, // basetype
897 GetMemberNameAtIndex(struct_type, i), // name
898 struct_member_offset, // offset
899 sizeof(Matrix), // size
900 stride * array_elements.value_or(1), // byte_length
901 array_elements, // array_elements
902 element_padding, // element_padding
903 });
904 current_byte_offset += stride * array_elements.value_or(1);
905 continue;
906 }
907
908 // Tightly packed UintPoint32 (uvec2)
909 if (member.basetype == spirv_cross::SPIRType::BaseType::UInt && //
910 member.width == sizeof(uint32_t) * 8 && //
911 member.columns == 1 && //
912 member.vecsize == 2 //
913 ) {
914 uint32_t stride =
915 GetArrayStride<sizeof(UintPoint32)>(struct_type, member, i);
916 uint32_t element_padding = stride - sizeof(UintPoint32);
917 result.emplace_back(StructMember{
918 "UintPoint32", // type
919 member.basetype, // basetype
920 GetMemberNameAtIndex(struct_type, i), // name
921 struct_member_offset, // offset
922 sizeof(UintPoint32), // size
923 stride * array_elements.value_or(1), // byte_length
924 array_elements, // array_elements
925 element_padding, // element_padding
926 });
927 current_byte_offset += stride * array_elements.value_or(1);
928 continue;
929 }
930
931 // Tightly packed UintPoint32 (ivec2)
932 if (member.basetype == spirv_cross::SPIRType::BaseType::Int && //
933 member.width == sizeof(int32_t) * 8 && //
934 member.columns == 1 && //
935 member.vecsize == 2 //
936 ) {
937 uint32_t stride =
938 GetArrayStride<sizeof(IPoint32)>(struct_type, member, i);
939 uint32_t element_padding = stride - sizeof(IPoint32);
940 result.emplace_back(StructMember{
941 "IPoint32", // type
942 member.basetype, // basetype
943 GetMemberNameAtIndex(struct_type, i), // name
944 struct_member_offset, // offset
945 sizeof(IPoint32), // size
946 stride * array_elements.value_or(1), // byte_length
947 array_elements, // array_elements
948 element_padding, // element_padding
949 });
950 current_byte_offset += stride * array_elements.value_or(1);
951 continue;
952 }
953
954 // Tightly packed Point (vec2).
955 if (member.basetype == spirv_cross::SPIRType::BaseType::Float && //
956 member.width == sizeof(float) * 8 && //
957 member.columns == 1 && //
958 member.vecsize == 2 //
959 ) {
960 uint32_t stride = GetArrayStride<sizeof(Point)>(struct_type, member, i);
961 uint32_t element_padding = stride - sizeof(Point);
962 result.emplace_back(StructMember{
963 "Point", // type
964 member.basetype, // basetype
965 GetMemberNameAtIndex(struct_type, i), // name
966 struct_member_offset, // offset
967 sizeof(Point), // size
968 stride * array_elements.value_or(1), // byte_length
969 array_elements, // array_elements
970 element_padding, // element_padding
971 });
972 current_byte_offset += stride * array_elements.value_or(1);
973 continue;
974 }
975
976 // Tightly packed Vector3.
977 if (member.basetype == spirv_cross::SPIRType::BaseType::Float && //
978 member.width == sizeof(float) * 8 && //
979 member.columns == 1 && //
980 member.vecsize == 3 //
981 ) {
982 uint32_t stride = GetArrayStride<sizeof(Vector3)>(struct_type, member, i);
983 uint32_t element_padding = stride - sizeof(Vector3);
984 result.emplace_back(StructMember{
985 "Vector3", // type
986 member.basetype, // basetype
987 GetMemberNameAtIndex(struct_type, i), // name
988 struct_member_offset, // offset
989 sizeof(Vector3), // size
990 stride * array_elements.value_or(1), // byte_length
991 array_elements, // array_elements
992 element_padding, // element_padding
993 });
994 current_byte_offset += stride * array_elements.value_or(1);
995 continue;
996 }
997
998 // Tightly packed Vector4.
999 if (member.basetype == spirv_cross::SPIRType::BaseType::Float && //
1000 member.width == sizeof(float) * 8 && //
1001 member.columns == 1 && //
1002 member.vecsize == 4 //
1003 ) {
1004 uint32_t stride = GetArrayStride<sizeof(Vector4)>(struct_type, member, i);
1005 uint32_t element_padding = stride - sizeof(Vector4);
1006 result.emplace_back(StructMember{
1007 "Vector4", // type
1008 member.basetype, // basetype
1009 GetMemberNameAtIndex(struct_type, i), // name
1010 struct_member_offset, // offset
1011 sizeof(Vector4), // size
1012 stride * array_elements.value_or(1), // byte_length
1013 array_elements, // array_elements
1014 element_padding, // element_padding
1015 });
1016 current_byte_offset += stride * array_elements.value_or(1);
1017 continue;
1018 }
1019
1020 // Tightly packed half Point (vec2).
1021 if (member.basetype == spirv_cross::SPIRType::BaseType::Half && //
1022 member.width == sizeof(Half) * 8 && //
1023 member.columns == 1 && //
1024 member.vecsize == 2 //
1025 ) {
1026 uint32_t stride =
1027 GetArrayStride<sizeof(HalfVector2)>(struct_type, member, i);
1028 uint32_t element_padding = stride - sizeof(HalfVector2);
1029 result.emplace_back(StructMember{
1030 "HalfVector2", // type
1031 member.basetype, // basetype
1032 GetMemberNameAtIndex(struct_type, i), // name
1033 struct_member_offset, // offset
1034 sizeof(HalfVector2), // size
1035 stride * array_elements.value_or(1), // byte_length
1036 array_elements, // array_elements
1037 element_padding, // element_padding
1038 });
1039 current_byte_offset += stride * array_elements.value_or(1);
1040 continue;
1041 }
1042
1043 // Tightly packed Half Float Vector3.
1044 if (member.basetype == spirv_cross::SPIRType::BaseType::Half && //
1045 member.width == sizeof(Half) * 8 && //
1046 member.columns == 1 && //
1047 member.vecsize == 3 //
1048 ) {
1049 uint32_t stride =
1050 GetArrayStride<sizeof(HalfVector3)>(struct_type, member, i);
1051 uint32_t element_padding = stride - sizeof(HalfVector3);
1052 result.emplace_back(StructMember{
1053 "HalfVector3", // type
1054 member.basetype, // basetype
1055 GetMemberNameAtIndex(struct_type, i), // name
1056 struct_member_offset, // offset
1057 sizeof(HalfVector3), // size
1058 stride * array_elements.value_or(1), // byte_length
1059 array_elements, // array_elements
1060 element_padding, // element_padding
1061 });
1062 current_byte_offset += stride * array_elements.value_or(1);
1063 continue;
1064 }
1065
1066 // Tightly packed Half Float Vector4.
1067 if (member.basetype == spirv_cross::SPIRType::BaseType::Half && //
1068 member.width == sizeof(Half) * 8 && //
1069 member.columns == 1 && //
1070 member.vecsize == 4 //
1071 ) {
1072 uint32_t stride =
1073 GetArrayStride<sizeof(HalfVector4)>(struct_type, member, i);
1074 uint32_t element_padding = stride - sizeof(HalfVector4);
1075 result.emplace_back(StructMember{
1076 "HalfVector4", // type
1077 member.basetype, // basetype
1078 GetMemberNameAtIndex(struct_type, i), // name
1079 struct_member_offset, // offset
1080 sizeof(HalfVector4), // size
1081 stride * array_elements.value_or(1), // byte_length
1082 array_elements, // array_elements
1083 element_padding, // element_padding
1084 });
1085 current_byte_offset += stride * array_elements.value_or(1);
1086 continue;
1087 }
1088
1089 // Other isolated scalars (like bool, int, float/Scalar, etc..).
1090 {
1091 auto maybe_known_type = ReadKnownScalarType(member.basetype);
1092 if (maybe_known_type.has_value() && //
1093 member.columns == 1 && //
1094 member.vecsize == 1 //
1095 ) {
1096 uint32_t stride = GetArrayStride<0>(struct_type, member, i);
1097 if (stride == 0) {
1098 stride = maybe_known_type.value().byte_size;
1099 }
1100 uint32_t element_padding = stride - maybe_known_type.value().byte_size;
1101 // Add the type directly.
1102 result.emplace_back(StructMember{
1103 maybe_known_type.value().name, // type
1104 member.basetype, // basetype
1105 GetMemberNameAtIndex(struct_type, i), // name
1106 struct_member_offset, // offset
1107 maybe_known_type.value().byte_size, // size
1108 stride * array_elements.value_or(1), // byte_length
1109 array_elements, // array_elements
1110 element_padding, // element_padding
1111 });
1112 current_byte_offset += stride * array_elements.value_or(1);
1113 continue;
1114 }
1115 }
1116
1117 // Catch all for unknown types. Just add the necessary padding to the struct
1118 // and move on.
1119 {
1120 const size_t size = (member.width * member.columns * member.vecsize) / 8u;
1121 uint32_t stride = GetArrayStride<0>(struct_type, member, i);
1122 if (stride == 0) {
1123 stride = size;
1124 }
1125 auto element_padding = stride - size;
1126 result.emplace_back(StructMember{
1127 TypeNameWithPaddingOfSize(size), // type
1128 member.basetype, // basetype
1129 GetMemberNameAtIndex(struct_type, i), // name
1130 struct_member_offset, // offset
1131 size, // size
1132 stride * array_elements.value_or(1), // byte_length
1133 array_elements, // array_elements
1134 element_padding, // element_padding
1135 });
1136 current_byte_offset += stride * array_elements.value_or(1);
1137 continue;
1138 }
1139 }
1140
1141 if (max_member_alignment > 0u) {
1142 const auto struct_length = current_byte_offset;
1143 {
1144 const auto excess = struct_length % max_member_alignment;
1145 if (excess != 0) {
1146 const auto padding = max_member_alignment - excess;
1147 result.emplace_back(StructMember{
1148 TypeNameWithPaddingOfSize(padding), // type
1149 spirv_cross::SPIRType::BaseType::Void, // basetype
1150 "_PADDING_", // name
1151 current_byte_offset, // offset
1152 padding, // size
1153 padding, // byte_length
1154 std::nullopt, // array_elements
1155 0, // element_padding
1156 });
1157 }
1158 }
1159 }
1160
1161 return result;
1162 }
1163
1164 std::optional<Reflector::StructDefinition> Reflector::ReflectStructDefinition(
1165 const spirv_cross::TypeID& type_id) const {
1166 const auto& type = compiler_->get_type(type_id);
1167 if (type.basetype != spirv_cross::SPIRType::BaseType::Struct) {
1168 return std::nullopt;
1169 }
1170
1171 const auto struct_name = compiler_->get_name(type_id);
1172 if (struct_name.find("_RESERVED_IDENTIFIER_") != std::string::npos) {
1173 return std::nullopt;
1174 }
1175
1176 auto struct_members = ReadStructMembers(type_id);
1177 auto reflected_struct_size = GetReflectedStructSize(struct_members);
1178
1179 StructDefinition struc;
1180 struc.name = struct_name;
1181 struc.byte_length = reflected_struct_size;
1182 struc.members = std::move(struct_members);
1183 return struc;
1184 }
1185
1186 nlohmann::json::object_t Reflector::EmitStructDefinition(
1187 std::optional<Reflector::StructDefinition> struc) const {
1188 nlohmann::json::object_t result;
1189 result["name"] = struc->name;
1190 result["byte_length"] = struc->byte_length;
1191 auto& members = result["members"] = nlohmann::json::array_t{};
1192 for (const auto& struct_member : struc->members) {
1193 auto& member = members.emplace_back(nlohmann::json::object_t{});
1194 member["name"] = struct_member.name;
1195 member["type"] = struct_member.type;
1196 member["base_type"] =
1197 StructMember::BaseTypeToString(struct_member.base_type);
1198 member["offset"] = struct_member.offset;
1199 member["byte_length"] = struct_member.byte_length;
1200 if (struct_member.array_elements.has_value()) {
1201 member["array_elements"] = struct_member.array_elements.value();
1202 } else {
1203 member["array_elements"] = "std::nullopt";
1204 }
1205 member["element_padding"] = struct_member.element_padding;
1206 }
1207 return result;
1208 }
1209
1210 struct VertexType {
1211 std::string type_name;
1212 spirv_cross::SPIRType::BaseType base_type;
1213 std::string variable_name;
1214 size_t byte_length = 0u;
1215 };
1216
1217 static VertexType VertexTypeFromInputResource(
1218 const spirv_cross::Compiler& compiler,
1219 const spirv_cross::Resource* resource) {
1220 VertexType result;
1221 result.variable_name = resource->name;
1222 const auto& type = compiler.get_type(resource->type_id);
1223 result.base_type = type.basetype;
1224 const auto total_size = type.columns * type.vecsize * type.width / 8u;
1225 result.byte_length = total_size;
1226
1227 if (type.basetype == spirv_cross::SPIRType::BaseType::Float &&
1228 type.columns == 1u && type.vecsize == 2u &&
1229 type.width == sizeof(float) * 8u) {
1230 result.type_name = "Point";
1231 } else if (type.basetype == spirv_cross::SPIRType::BaseType::Float &&
1232 type.columns == 1u && type.vecsize == 4u &&
1233 type.width == sizeof(float) * 8u) {
1234 result.type_name = "Vector4";
1235 } else if (type.basetype == spirv_cross::SPIRType::BaseType::Float &&
1236 type.columns == 1u && type.vecsize == 3u &&
1237 type.width == sizeof(float) * 8u) {
1238 result.type_name = "Vector3";
1239 } else if (type.basetype == spirv_cross::SPIRType::BaseType::Float &&
1240 type.columns == 1u && type.vecsize == 1u &&
1241 type.width == sizeof(float) * 8u) {
1242 result.type_name = "Scalar";
1243 } else if (type.basetype == spirv_cross::SPIRType::BaseType::Int &&
1244 type.columns == 1u && type.vecsize == 1u &&
1245 type.width == sizeof(int32_t) * 8u) {
1246 result.type_name = "int32_t";
1247 } else {
1248 // Catch all unknown padding.
1249 result.type_name = TypeNameWithPaddingOfSize(total_size);
1250 }
1251
1252 return result;
1253 }
1254
1255 std::optional<Reflector::StructDefinition>
1256 Reflector::ReflectPerVertexStructDefinition(
1257 const spirv_cross::SmallVector<spirv_cross::Resource>& stage_inputs) const {
1258 // Avoid emitting a zero sized structure. The code gen templates assume a
1259 // non-zero size.
1260 if (stage_inputs.empty()) {
1261 return std::nullopt;
1262 }
1263
1264 // Validate locations are contiguous and there are no duplicates.
1265 std::set<uint32_t> locations;
1266 for (const auto& input : stage_inputs) {
1267 auto location = compiler_->get_decoration(
1268 input.id, spv::Decoration::DecorationLocation);
1269 if (locations.count(location) != 0) {
1270 // Duplicate location. Bail.
1271 return std::nullopt;
1272 }
1273 locations.insert(location);
1274 }
1275
1276 for (size_t i = 0; i < locations.size(); i++) {
1277 if (locations.count(i) != 1) {
1278 // Locations are not contiguous. This usually happens when a single stage
1279 // input takes multiple input slots. No reflection information can be
1280 // generated for such cases anyway. So bail! It is up to the shader author
1281 // to make sure one stage input maps to a single input slot.
1282 return std::nullopt;
1283 }
1284 }
1285
1286 auto input_for_location =
1287 [&](uint32_t queried_location) -> const spirv_cross::Resource* {
1288 for (const auto& input : stage_inputs) {
1289 auto location = compiler_->get_decoration(
1290 input.id, spv::Decoration::DecorationLocation);
1291 if (location == queried_location) {
1292 return &input;
1293 }
1294 }
1295 // This really cannot happen with all the validation above.
1296 FML_UNREACHABLE();
1297 return nullptr;
1298 };
1299
1300 StructDefinition struc;
1301 struc.name = "PerVertexData";
1302 struc.byte_length = 0u;
1303 for (size_t i = 0; i < locations.size(); i++) {
1304 auto resource = input_for_location(i);
1305 if (resource == nullptr) {
1306 return std::nullopt;
1307 }
1308 const auto vertex_type =
1309 VertexTypeFromInputResource(*compiler_.GetCompiler(), resource);
1310
1311 auto member = StructMember{
1312 vertex_type.type_name, // type
1313 vertex_type.base_type, // base type
1314 vertex_type.variable_name, // name
1315 struc.byte_length, // offset
1316 vertex_type.byte_length, // size
1317 vertex_type.byte_length, // byte_length
1318 std::nullopt, // array_elements
1319 0, // element_padding
1320 };
1321 struc.byte_length += vertex_type.byte_length;
1322 struc.members.emplace_back(std::move(member));
1323 }
1324 return struc;
1325 }
1326
1327 std::optional<std::string> Reflector::GetMemberNameAtIndexIfExists(
1328 const spirv_cross::SPIRType& parent_type,
1329 size_t index) const {
1330 if (parent_type.type_alias != 0) {
1331 return GetMemberNameAtIndexIfExists(
1332 compiler_->get_type(parent_type.type_alias), index);
1333 }
1334
1335 if (auto found = ir_->meta.find(parent_type.self); found != ir_->meta.end()) {
1336 const auto& members = found->second.members;
1337 if (index < members.size() && !members[index].alias.empty()) {
1338 return members[index].alias;
1339 }
1340 }
1341 return std::nullopt;
1342 }
1343
1344 std::string Reflector::GetMemberNameAtIndex(
1345 const spirv_cross::SPIRType& parent_type,
1346 size_t index,
1347 std::string suffix) const {
1348 if (auto name = GetMemberNameAtIndexIfExists(parent_type, index);
1349 name.has_value()) {
1350 return name.value();
1351 }
1352 static std::atomic_size_t sUnnamedMembersID;
1353 std::stringstream stream;
1354 stream << "unnamed_" << sUnnamedMembersID++ << suffix;
1355 return stream.str();
1356 }
1357
1358 std::vector<Reflector::BindPrototype> Reflector::ReflectBindPrototypes(
1359 const spirv_cross::ShaderResources& resources,
1360 spv::ExecutionModel execution_model) const {
1361 std::vector<BindPrototype> prototypes;
1362 for (const auto& uniform_buffer : resources.uniform_buffers) {
1363 auto& proto = prototypes.emplace_back(BindPrototype{});
1364 proto.return_type = "bool";
1365 proto.name = ToCamelCase(uniform_buffer.name);
1366 proto.descriptor_type = "DescriptorType::kUniformBuffer";
1367 {
1368 std::stringstream stream;
1369 stream << "Bind uniform buffer for resource named " << uniform_buffer.name
1370 << ".";
1371 proto.docstring = stream.str();
1372 }
1373 proto.args.push_back(BindPrototypeArgument{
1374 .type_name = "ResourceBinder&",
1375 .argument_name = "command",
1376 });
1377 proto.args.push_back(BindPrototypeArgument{
1378 .type_name = "BufferView",
1379 .argument_name = "view",
1380 });
1381 }
1382 for (const auto& storage_buffer : resources.storage_buffers) {
1383 auto& proto = prototypes.emplace_back(BindPrototype{});
1384 proto.return_type = "bool";
1385 proto.name = ToCamelCase(storage_buffer.name);
1386 proto.descriptor_type = "DescriptorType::kStorageBuffer";
1387 {
1388 std::stringstream stream;
1389 stream << "Bind storage buffer for resource named " << storage_buffer.name
1390 << ".";
1391 proto.docstring = stream.str();
1392 }
1393 proto.args.push_back(BindPrototypeArgument{
1394 .type_name = "ResourceBinder&",
1395 .argument_name = "command",
1396 });
1397 proto.args.push_back(BindPrototypeArgument{
1398 .type_name = "BufferView",
1399 .argument_name = "view",
1400 });
1401 }
1402 for (const auto& sampled_image : resources.sampled_images) {
1403 auto& proto = prototypes.emplace_back(BindPrototype{});
1404 proto.return_type = "bool";
1405 proto.name = ToCamelCase(sampled_image.name);
1406 proto.descriptor_type = "DescriptorType::kSampledImage";
1407 {
1408 std::stringstream stream;
1409 stream << "Bind combined image sampler for resource named "
1410 << sampled_image.name << ".";
1411 proto.docstring = stream.str();
1412 }
1413 proto.args.push_back(BindPrototypeArgument{
1414 .type_name = "ResourceBinder&",
1415 .argument_name = "command",
1416 });
1417 proto.args.push_back(BindPrototypeArgument{
1418 .type_name = "std::shared_ptr<const Texture>",
1419 .argument_name = "texture",
1420 });
1421 proto.args.push_back(BindPrototypeArgument{
1422 .type_name = "raw_ptr<const Sampler>",
1423 .argument_name = "sampler",
1424 });
1425 }
1426 for (const auto& separate_image : resources.separate_images) {
1427 auto& proto = prototypes.emplace_back(BindPrototype{});
1428 proto.return_type = "bool";
1429 proto.name = ToCamelCase(separate_image.name);
1430 proto.descriptor_type = "DescriptorType::kImage";
1431 {
1432 std::stringstream stream;
1433 stream << "Bind separate image for resource named " << separate_image.name
1434 << ".";
1435 proto.docstring = stream.str();
1436 }
1437 proto.args.push_back(BindPrototypeArgument{
1438 .type_name = "Command&",
1439 .argument_name = "command",
1440 });
1441 proto.args.push_back(BindPrototypeArgument{
1442 .type_name = "std::shared_ptr<const Texture>",
1443 .argument_name = "texture",
1444 });
1445 }
1446 for (const auto& separate_sampler : resources.separate_samplers) {
1447 auto& proto = prototypes.emplace_back(BindPrototype{});
1448 proto.return_type = "bool";
1449 proto.name = ToCamelCase(separate_sampler.name);
1450 proto.descriptor_type = "DescriptorType::kSampler";
1451 {
1452 std::stringstream stream;
1453 stream << "Bind separate sampler for resource named "
1454 << separate_sampler.name << ".";
1455 proto.docstring = stream.str();
1456 }
1457 proto.args.push_back(BindPrototypeArgument{
1458 .type_name = "Command&",
1459 .argument_name = "command",
1460 });
1461 proto.args.push_back(BindPrototypeArgument{
1462 .type_name = "std::shared_ptr<const Sampler>",
1463 .argument_name = "sampler",
1464 });
1465 }
1466 return prototypes;
1467 }
1468
1469 nlohmann::json::array_t Reflector::EmitBindPrototypes(
1470 const spirv_cross::ShaderResources& resources,
1471 spv::ExecutionModel execution_model) const {
1472 const auto prototypes = ReflectBindPrototypes(resources, execution_model);
1473 nlohmann::json::array_t result;
1474 for (const auto& res : prototypes) {
1475 auto& item = result.emplace_back(nlohmann::json::object_t{});
1476 item["return_type"] = res.return_type;
1477 item["name"] = res.name;
1478 item["docstring"] = res.docstring;
1479 item["descriptor_type"] = res.descriptor_type;
1480 auto& args = item["args"] = nlohmann::json::array_t{};
1481 for (const auto& arg : res.args) {
1482 auto& json_arg = args.emplace_back(nlohmann::json::object_t{});
1483 json_arg["type_name"] = arg.type_name;
1484 json_arg["argument_name"] = arg.argument_name;
1485 }
1486 }
1487 return result;
1488 }
1489
1490 } // namespace compiler
1491 } // namespace impeller
type
GLenum type
Definition: blit_command_gles.cc:153
impeller::RuntimeStage::kVulkanUBOName
static const char * kVulkanUBOName
Definition: runtime_stage.h:23
impeller::compiler::Reflector::Reflector
Reflector(Options options, const std::shared_ptr< const spirv_cross::ParsedIR > &ir, const std::shared_ptr< fml::Mapping > &shader_data, const CompilerBackend &compiler)
Definition: reflector.cc:63
impeller::compiler::Reflector::GetReflectionJSON
std::shared_ptr< fml::Mapping > GetReflectionJSON() const
Definition: reflector.cc:109
impeller::compiler::Reflector::IsValid
bool IsValid() const
Definition: reflector.cc:105
impeller::compiler::Reflector::GetReflectionCC
std::shared_ptr< fml::Mapping > GetReflectionCC() const
Definition: reflector.cc:126
impeller::compiler::Reflector::~Reflector
~Reflector()
impeller::compiler::Reflector::GetRuntimeStageShaderData
std::shared_ptr< RuntimeStageData::Shader > GetRuntimeStageShaderData() const
Definition: reflector.cc:130
impeller::compiler::Reflector::GetShaderBundleData
std::shared_ptr< ShaderBundleData > GetShaderBundleData() const
Definition: reflector.cc:135
impeller::compiler::Reflector::GetReflectionHeader
std::shared_ptr< fml::Mapping > GetReflectionHeader() const
Definition: reflector.cc:122
code_gen_template.h
types.h
location
uint32_t location
Definition: compiler_unittests.cc:129
value
int32_t value
Definition: dl_golden_unittests.cc:459
padding
Vector2 padding
The halo padding in source space.
Definition: gaussian_blur_filter_contents.cc:87
half.h
matrix.h
impeller::compiler::ReadKnownScalarType
static std::optional< KnownType > ReadKnownScalarType(spirv_cross::SPIRType::BaseType type)
Definition: reflector.cc:769
impeller::compiler::TypeNameWithPaddingOfSize
static std::string TypeNameWithPaddingOfSize(size_t size)
Definition: reflector.cc:758
impeller::compiler::VertexTypeFromInputResource
static VertexType VertexTypeFromInputResource(const spirv_cross::Compiler &compiler, const spirv_cross::Resource *resource)
Definition: reflector.cc:1217
impeller::compiler::ToString
static std::string ToString(CompilerBackend::Type type)
Definition: reflector.cc:590
impeller::compiler::GetReflectedStructSize
static size_t GetReflectedStructSize(const std::vector< StructMember > &members)
Get the reflected struct size. In the vast majority of the cases, this is the same as the declared st...
Definition: reflector.cc:816
impeller::compiler::GetRuntimeStageBackend
static std::optional< RuntimeStageBackend > GetRuntimeStageBackend(TargetPlatform target_platform)
Definition: reflector.cc:316
impeller::compiler::ExecutionModelToString
static std::string ExecutionModelToString(spv::ExecutionModel model)
Definition: reflector.cc:34
impeller::compiler::StringToShaderStage
static std::string StringToShaderStage(const std::string &str)
Definition: reflector.cc:47
impeller::compiler::kReflectionHeaderTemplate
constexpr std::string_view kReflectionHeaderTemplate
Definition: code_gen_template.h:10
impeller::compiler::ToCamelCase
std::string ToCamelCase(std::string_view string)
Definition: utilities.cc:38
impeller::compiler::kReflectionCCTemplate
constexpr std::string_view kReflectionCCTemplate
Definition: code_gen_template.h:202
impeller::compiler::StringStartsWith
bool StringStartsWith(const std::string &target, const std::string &prefix)
Definition: utilities.cc:86
impeller::compiler::TargetPlatform
TargetPlatform
Definition: types.h:29
impeller::compiler::TargetPlatform::kRuntimeStageMetal
@ kRuntimeStageMetal
impeller::compiler::TargetPlatform::kOpenGLES
@ kOpenGLES
impeller::compiler::TargetPlatform::kUnknown
@ kUnknown
impeller::compiler::TargetPlatform::kRuntimeStageVulkan
@ kRuntimeStageVulkan
impeller::compiler::TargetPlatform::kRuntimeStageGLES3
@ kRuntimeStageGLES3
impeller::compiler::TargetPlatform::kVulkan
@ kVulkan
impeller::compiler::TargetPlatform::kMetalDesktop
@ kMetalDesktop
impeller::compiler::TargetPlatform::kMetalIOS
@ kMetalIOS
impeller::compiler::TargetPlatform::kSkSL
@ kSkSL
impeller::compiler::TargetPlatform::kOpenGLDesktop
@ kOpenGLDesktop
impeller::compiler::TargetPlatform::kRuntimeStageGLES
@ kRuntimeStageGLES
impeller
Definition: allocation.cc:12
impeller::SortUniforms
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.
Definition: uniform_sorter.cc:11
impeller::Scalar
float Scalar
Definition: scalar.h:19
impeller::RuntimeStageBackend::kOpenGLES
@ kOpenGLES
impeller::RuntimeStageBackend::kMetal
@ kMetal
impeller::RuntimeStageBackend::kVulkan
@ kVulkan
impeller::RuntimeStageBackend::kSkSL
@ kSkSL
impeller::RuntimeStageBackend::kOpenGLES3
@ kOpenGLES3
impeller::Point
TPoint< Scalar > Point
Definition: point.h:425
impeller::IPoint32
TPoint< int32_t > IPoint32
Definition: point.h:427
impeller::UintPoint32
TPoint< uint32_t > UintPoint32
Definition: point.h:428
std
Definition: comparable.h:95
reflector.h
runtime_stage.h
runtime_types.h
scalar.h
shader_bundle_data.h
strings.h
impeller::Half
A storage only class for half precision floating point.
Definition: half.h:41
impeller::compiler::CompilerBackend
Definition: compiler_backend.h:19
impeller::compiler::CompilerBackend::GetType
Type GetType() const
Definition: compiler_backend.cc:108
impeller::compiler::CompilerBackend::GetCompiler
spirv_cross::Compiler * GetCompiler()
Definition: compiler_backend.cc:70
impeller::compiler::CompilerBackend::Type
Type
Definition: compiler_backend.h:25
impeller::compiler::CompilerBackend::Type::kGLSLVulkan
@ kGLSLVulkan
impeller::compiler::CompilerBackend::Type::kGLSL
@ kGLSL
impeller::compiler::CompilerBackend::Type::kMSL
@ kMSL
impeller::compiler::CompilerBackend::Type::kSkSL
@ kSkSL
impeller::compiler::CompilerBackend::GetExtendedMSLResourceBinding
uint32_t GetExtendedMSLResourceBinding(ExtendedResourceIndex index, spirv_cross::ID id) const
Definition: compiler_backend.cc:35
impeller::compiler::CompilerBackend::ExtendedResourceIndex::kSecondary
@ kSecondary
impeller::compiler::CompilerBackend::ExtendedResourceIndex::kPrimary
@ kPrimary
impeller::compiler::KnownType
Definition: reflector.cc:764
impeller::compiler::KnownType::byte_size
size_t byte_size
Definition: reflector.cc:766
impeller::compiler::KnownType::name
std::string name
Definition: reflector.cc:765
impeller::compiler::Reflector::Options
Definition: reflector.h:146
impeller::compiler::Reflector::Options::target_platform
TargetPlatform target_platform
Definition: reflector.h:147
impeller::compiler::Reflector::Options::entry_point_name
std::string entry_point_name
Definition: reflector.h:148
impeller::compiler::Reflector::Options::shader_name
std::string shader_name
Definition: reflector.h:149
impeller::compiler::Reflector::Options::header_file_name
std::string header_file_name
Definition: reflector.h:150
impeller::compiler::StructMember::BaseTypeToString
static std::string BaseTypeToString(spirv_cross::SPIRType::BaseType type)
Definition: reflector.h:44
impeller::compiler::StructMember::UnderlyingType::kOther
@ kOther
impeller::compiler::StructMember::UnderlyingType::kPadding
@ kPadding
impeller::compiler::StructMember::UnderlyingType::kFloat
@ kFloat
impeller::compiler::VertexType
Definition: reflector.cc:1210
impeller::compiler::VertexType::byte_length
size_t byte_length
Definition: reflector.cc:1214
impeller::compiler::VertexType::base_type
spirv_cross::SPIRType::BaseType base_type
Definition: reflector.cc:1212
impeller::compiler::VertexType::type_name
std::string type_name
Definition: reflector.cc:1211
impeller::compiler::VertexType::variable_name
std::string variable_name
Definition: reflector.cc:1213
data
std::shared_ptr< const fml::Mapping > data
Definition: texture_gles.cc:69
uniform_sorter.h
utilities.h
validation.h
VALIDATION_LOG
#define VALIDATION_LOG
Definition: validation.h:91