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