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