Flutter Impeller
reflector.cc
Go to the documentation of this file.
1 // Copyright 2013 The Flutter Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 // FLUTTER_NOLINT: https://github.com/flutter/flutter/issues/105732
6 
8 
9 #include <atomic>
10 #include <format>
11 #include <optional>
12 #include <set>
13 #include <sstream>
14 
15 #include "flutter/fml/logging.h"
16 #include "fml/backtrace.h"
17 #include "impeller/base/strings.h"
25 #include "impeller/geometry/half.h"
29 #include "runtime_stage_types_flatbuffers.h"
30 #include "spirv_common.hpp"
31 
32 namespace impeller {
33 namespace compiler {
34 
35 static 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 
48 static 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 
104 Reflector::~Reflector() = default;
105 
106 bool Reflector::IsValid() const {
107  return is_valid_;
108 }
109 
110 std::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 
123 std::shared_ptr<fml::Mapping> Reflector::GetReflectionHeader() const {
124  return reflection_header_;
125 }
126 
127 std::shared_ptr<fml::Mapping> Reflector::GetReflectionCC() const {
128  return reflection_cc_;
129 }
130 
131 std::shared_ptr<RuntimeStageData::Shader> Reflector::GetRuntimeStageShaderData()
132  const {
133  return runtime_stage_shader_;
134 }
135 
136 std::shared_ptr<ShaderBundleData> Reflector::GetShaderBundleData() const {
137  return shader_bundle_data_;
138 }
139 
140 std::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 
309 std::shared_ptr<fml::Mapping> Reflector::GenerateReflectionHeader() const {
310  return InflateTemplate(kReflectionHeaderTemplate);
311 }
312 
313 std::shared_ptr<fml::Mapping> Reflector::GenerateReflectionCC() const {
314  return InflateTemplate(kReflectionCCTemplate);
315 }
316 
317 static std::optional<RuntimeStageBackend> GetRuntimeStageBackend(
318  TargetPlatform target_platform) {
319  switch (target_platform) {
326  return std::nullopt;
337  }
338  FML_UNREACHABLE();
339 }
340 
341 std::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 
377  if (TargetPlatformIsMetal(options_.target_platform) &&
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(
390  }
391  uniform_description.padding_layout.push_back(
393  }
394  }
395  }
396 
397  FML_CHECK(data->backend != RuntimeStageBackend::kVulkan ||
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 
513 std::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.fields.push_back(uniform_struct_field);
560  }
561  uniform_struct.size_in_bytes = size_in_bytes;
562 
563  data->AddUniformStruct(uniform_struct);
564  }
565 
566  const auto sampled_images = compiler_->get_shader_resources().sampled_images;
567  for (const auto& image : sampled_images) {
568  ShaderBundleData::ShaderUniformTexture uniform_texture;
569  uniform_texture.name = image.name;
570  uniform_texture.ext_res_0 = compiler_.GetExtendedMSLResourceBinding(
572  uniform_texture.set = compiler_->get_decoration(
573  image.id, spv::Decoration::DecorationDescriptorSet);
574  uniform_texture.binding =
575  compiler_->get_decoration(image.id, spv::Decoration::DecorationBinding);
576  data->AddUniformTexture(uniform_texture);
577  }
578 
579  // We only need to worry about storing vertex attributes.
580  if (entrypoints.front().execution_model == spv::ExecutionModelVertex) {
581  const auto inputs = compiler_->get_shader_resources().stage_inputs;
582  auto input_offsets = ComputeOffsets(inputs);
583  for (const auto& input : inputs) {
584  std::optional<size_t> offset = GetOffset(input.id, input_offsets);
585 
586  const auto type = compiler_->get_type(input.type_id);
587 
588  InputDescription input_description;
589  input_description.name = input.name;
590  input_description.location = compiler_->get_decoration(
591  input.id, spv::Decoration::DecorationLocation);
592  input_description.set = compiler_->get_decoration(
593  input.id, spv::Decoration::DecorationDescriptorSet);
594  input_description.binding = compiler_->get_decoration(
595  input.id, spv::Decoration::DecorationBinding);
596  input_description.type = type.basetype;
597  input_description.bit_width = type.width;
598  input_description.vec_size = type.vecsize;
599  input_description.columns = type.columns;
600  input_description.offset = offset.value_or(0u);
601  data->AddInputDescription(std::move(input_description));
602  }
603  }
604 
605  return data;
606 }
607 
608 std::optional<uint32_t> Reflector::GetArrayElements(
609  const spirv_cross::SPIRType& type) const {
610  if (type.array.empty()) {
611  return std::nullopt;
612  }
613  FML_CHECK(type.array.size() == 1)
614  << "Multi-dimensional arrays are not supported.";
615  FML_CHECK(type.array_size_literal.front())
616  << "Must use a literal for array sizes.";
617  return type.array.front();
618 }
619 
620 static std::string ToString(CompilerBackend::Type type) {
621  switch (type) {
623  return "Metal Shading Language";
625  return "OpenGL Shading Language";
627  return "OpenGL Shading Language (Relaxed Vulkan Semantics)";
629  return "SkSL Shading Language";
630  }
631  FML_UNREACHABLE();
632 }
633 
634 std::shared_ptr<fml::Mapping> Reflector::InflateTemplate(
635  std::string_view tmpl) const {
636  inja::Environment env;
637  env.set_trim_blocks(true);
638  env.set_lstrip_blocks(true);
639 
640  env.add_callback("camel_case", 1u, [](inja::Arguments& args) {
641  return ToCamelCase(args.at(0u)->get<std::string>());
642  });
643 
644  env.add_callback("to_shader_stage", 1u, [](inja::Arguments& args) {
645  return StringToShaderStage(args.at(0u)->get<std::string>());
646  });
647 
648  env.add_callback("get_generator_name", 0u,
649  [type = compiler_.GetType()](inja::Arguments& args) {
650  return ToString(type);
651  });
652 
653  auto inflated_template =
654  std::make_shared<std::string>(env.render(tmpl, *template_arguments_));
655 
656  return std::make_shared<fml::NonOwnedMapping>(
657  reinterpret_cast<const uint8_t*>(inflated_template->data()),
658  inflated_template->size(), [inflated_template](auto, auto) {});
659 }
660 
661 std::vector<size_t> Reflector::ComputeOffsets(
662  const spirv_cross::SmallVector<spirv_cross::Resource>& resources) const {
663  std::vector<size_t> offsets(resources.size(), 0);
664  if (resources.size() == 0) {
665  return offsets;
666  }
667  for (const auto& resource : resources) {
668  const auto type = compiler_->get_type(resource.type_id);
669  auto location = compiler_->get_decoration(
670  resource.id, spv::Decoration::DecorationLocation);
671  // Malformed shader, will be caught later on.
672  if (location >= resources.size() || location < 0) {
673  location = 0;
674  }
675  offsets[location] = (type.width * type.vecsize) / 8;
676  }
677  for (size_t i = 1; i < resources.size(); i++) {
678  offsets[i] += offsets[i - 1];
679  }
680  for (size_t i = resources.size() - 1; i > 0; i--) {
681  offsets[i] = offsets[i - 1];
682  }
683  offsets[0] = 0;
684 
685  return offsets;
686 }
687 
688 std::optional<size_t> Reflector::GetOffset(
689  spirv_cross::ID id,
690  const std::vector<size_t>& offsets) const {
691  uint32_t location =
692  compiler_->get_decoration(id, spv::Decoration::DecorationLocation);
693  if (location >= offsets.size()) {
694  return std::nullopt;
695  }
696  return offsets[location];
697 }
698 
699 std::optional<nlohmann::json::object_t> Reflector::ReflectResource(
700  const spirv_cross::Resource& resource,
701  std::optional<size_t> offset) const {
702  nlohmann::json::object_t result;
703 
704  result["name"] = resource.name;
705  result["descriptor_set"] = compiler_->get_decoration(
706  resource.id, spv::Decoration::DecorationDescriptorSet);
707  result["binding"] = compiler_->get_decoration(
708  resource.id, spv::Decoration::DecorationBinding);
709  result["set"] = compiler_->get_decoration(
710  resource.id, spv::Decoration::DecorationDescriptorSet);
711  result["location"] = compiler_->get_decoration(
712  resource.id, spv::Decoration::DecorationLocation);
713  result["index"] =
714  compiler_->get_decoration(resource.id, spv::Decoration::DecorationIndex);
715  result["ext_res_0"] = compiler_.GetExtendedMSLResourceBinding(
717  result["ext_res_1"] = compiler_.GetExtendedMSLResourceBinding(
719  result["relaxed_precision"] =
720  compiler_->get_decoration(
721  resource.id, spv::Decoration::DecorationRelaxedPrecision) == 1;
722  result["offset"] = offset.value_or(0u);
723  auto type = ReflectType(resource.type_id);
724  if (!type.has_value()) {
725  return std::nullopt;
726  }
727  result["type"] = std::move(type.value());
728  return result;
729 }
730 
731 std::optional<nlohmann::json::object_t> Reflector::ReflectType(
732  const spirv_cross::TypeID& type_id) const {
733  nlohmann::json::object_t result;
734 
735  const auto type = compiler_->get_type(type_id);
736 
737  result["type_name"] = StructMember::BaseTypeToString(type.basetype);
738  result["bit_width"] = type.width;
739  result["vec_size"] = type.vecsize;
740  result["columns"] = type.columns;
741  auto& members = result["members"] = nlohmann::json::array_t{};
742  if (type.basetype == spirv_cross::SPIRType::BaseType::Struct) {
743  for (const auto& struct_member : ReadStructMembers(type_id)) {
744  auto member = nlohmann::json::object_t{};
745  member["name"] = struct_member.name;
746  member["type"] = struct_member.type;
747  member["base_type"] =
748  StructMember::BaseTypeToString(struct_member.base_type);
749  member["offset"] = struct_member.offset;
750  member["size"] = struct_member.size;
751  member["byte_length"] = struct_member.byte_length;
752  if (struct_member.array_elements.has_value()) {
753  member["array_elements"] = struct_member.array_elements.value();
754  } else {
755  member["array_elements"] = "std::nullopt";
756  }
757  if (struct_member.float_type.has_value()) {
758  member["float_type"] = struct_member.float_type.value();
759  } else {
760  member["float_type"] = "std::nullopt";
761  }
762  members.emplace_back(std::move(member));
763  }
764  }
765 
766  return result;
767 }
768 
769 std::optional<nlohmann::json::array_t> Reflector::ReflectResources(
770  const spirv_cross::SmallVector<spirv_cross::Resource>& resources,
771  bool compute_offsets) const {
772  nlohmann::json::array_t result;
773  result.reserve(resources.size());
774  std::vector<size_t> offsets;
775  if (compute_offsets) {
776  offsets = ComputeOffsets(resources);
777  }
778  for (const auto& resource : resources) {
779  std::optional<size_t> maybe_offset = std::nullopt;
780  if (compute_offsets) {
781  maybe_offset = GetOffset(resource.id, offsets);
782  }
783  if (auto reflected = ReflectResource(resource, maybe_offset);
784  reflected.has_value()) {
785  result.emplace_back(std::move(reflected.value()));
786  } else {
787  return std::nullopt;
788  }
789  }
790  return result;
791 }
792 
793 static std::string TypeNameWithPaddingOfSize(size_t size) {
794  std::stringstream stream;
795  stream << "Padding<" << size << ">";
796  return stream.str();
797 }
798 
799 struct KnownType {
800  std::string name;
801  size_t byte_size = 0;
802 };
803 
804 static std::optional<KnownType> ReadKnownScalarType(
805  spirv_cross::SPIRType::BaseType type) {
806  switch (type) {
807  case spirv_cross::SPIRType::BaseType::Boolean:
808  return KnownType{
809  .name = "bool",
810  .byte_size = sizeof(bool),
811  };
812  case spirv_cross::SPIRType::BaseType::Float:
813  return KnownType{
814  .name = "Scalar",
815  .byte_size = sizeof(Scalar),
816  };
817  case spirv_cross::SPIRType::BaseType::Half:
818  return KnownType{
819  .name = "Half",
820  .byte_size = sizeof(Half),
821  };
822  case spirv_cross::SPIRType::BaseType::UInt:
823  return KnownType{
824  .name = "uint32_t",
825  .byte_size = sizeof(uint32_t),
826  };
827  case spirv_cross::SPIRType::BaseType::Int:
828  return KnownType{
829  .name = "int32_t",
830  .byte_size = sizeof(int32_t),
831  };
832  default:
833  break;
834  }
835  return std::nullopt;
836 }
837 
838 //------------------------------------------------------------------------------
839 /// @brief Get the reflected struct size. In the vast majority of the
840 /// cases, this is the same as the declared struct size as given by
841 /// the compiler. But, additional padding may need to be introduced
842 /// after the end of the struct to keep in line with the alignment
843 /// requirement of the individual struct members. This method
844 /// figures out the actual size of the reflected struct that can be
845 /// referenced in native code.
846 ///
847 /// @param[in] members The members
848 ///
849 /// @return The reflected structure size.
850 ///
851 static size_t GetReflectedStructSize(const std::vector<StructMember>& members) {
852  auto struct_size = 0u;
853  for (const auto& member : members) {
854  struct_size += member.byte_length;
855  }
856  return struct_size;
857 }
858 
859 std::vector<StructMember> Reflector::ReadStructMembers(
860  const spirv_cross::TypeID& type_id) const {
861  const auto& struct_type = compiler_->get_type(type_id);
862  FML_CHECK(struct_type.basetype == spirv_cross::SPIRType::BaseType::Struct);
863 
864  std::vector<StructMember> result;
865 
866  size_t current_byte_offset = 0;
867  size_t max_member_alignment = 0;
868 
869  for (size_t i = 0; i < struct_type.member_types.size(); i++) {
870  const spirv_cross::SPIRType& member =
871  compiler_->get_type(struct_type.member_types[i]);
872  const uint32_t struct_member_offset =
873  compiler_->type_struct_member_offset(struct_type, i);
874  std::optional<uint32_t> array_elements = GetArrayElements(member);
875 
876  if (struct_member_offset > current_byte_offset) {
877  const size_t alignment_pad = struct_member_offset - current_byte_offset;
878  result.emplace_back(StructMember{
879  /*p_type=*/TypeNameWithPaddingOfSize(alignment_pad),
880  /*p_base_type=*/spirv_cross::SPIRType::BaseType::Void,
881  /*p_name=*/
882  std::format("_PADDING_{}_", GetMemberNameAtIndex(struct_type, i)),
883  /*p_offset=*/current_byte_offset,
884  /*p_size=*/alignment_pad,
885  /*p_byte_length=*/alignment_pad,
886  /*p_array_elements=*/std::nullopt,
887  /*p_element_padding=*/0,
888  });
889  current_byte_offset += alignment_pad;
890  }
891 
892  max_member_alignment =
893  std::max<size_t>(max_member_alignment,
894  (member.width / 8) * member.columns * member.vecsize);
895 
896  FML_CHECK(current_byte_offset == struct_member_offset);
897 
898  // A user defined struct.
899  if (member.basetype == spirv_cross::SPIRType::BaseType::Struct) {
900  const size_t size =
901  GetReflectedStructSize(ReadStructMembers(member.self));
902  uint32_t stride = GetArrayStride<0>(struct_type, member, i);
903  if (stride == 0) {
904  stride = size;
905  }
906  uint32_t element_padding = stride - size;
907  result.emplace_back(StructMember{
908  /*p_type=*/compiler_->get_name(member.self),
909  /*p_base_type=*/member.basetype,
910  /*p_name=*/GetMemberNameAtIndex(struct_type, i),
911  /*p_offset=*/struct_member_offset,
912  /*p_size=*/size,
913  /*p_byte_length=*/stride * array_elements.value_or(1),
914  /*p_array_elements=*/array_elements,
915  /*p_element_padding=*/element_padding,
916  });
917  current_byte_offset += stride * array_elements.value_or(1);
918  continue;
919  }
920 
921  // Mat2
922  if (member.basetype == spirv_cross::SPIRType::BaseType::Float &&
923  member.width == 32 && member.columns == 2 && member.vecsize == 2) {
924  // Mat2's are packaged like 2 vec2's, ie
925  // {val, val, padding, padding, val, val, padding, padding}.
926  uint32_t count = array_elements.value_or(1) * 2;
927  uint32_t stride = 16;
928  uint32_t total_length = stride * count;
929 
930  result.emplace_back(StructMember{
931  /*p_type=*/"Mat2",
932  /*p_base_type=*/spirv_cross::SPIRType::BaseType::Float,
933  /*p_name=*/GetMemberNameAtIndex(struct_type, i),
934  /*p_offset=*/struct_member_offset,
935  /*p_size=*/sizeof(Point),
936  /*p_byte_length=*/total_length,
937  /*p_array_elements=*/count,
938  /*p_element_padding=*/8,
939  /*p_float_type=*/"ShaderFloatType::kMat2",
940  });
941  current_byte_offset += total_length;
942  continue;
943  }
944 
945  if (member.basetype == spirv_cross::SPIRType::BaseType::Float &&
946  member.width == 32 && member.columns == 3 && member.vecsize == 3) {
947  // Mat3s are packed as three vec3s with one float of padding after each.
948  // {val, val, val, padding, val, val, val, padding, val, val, val,
949  // padding}.
950  uint32_t count = array_elements.value_or(1) * 3;
951  uint32_t stride = 16;
952  uint32_t total_length = stride * count;
953 
954  result.emplace_back(StructMember{
955  /*p_type=*/"Mat3",
956  /*p_base_type=*/spirv_cross::SPIRType::BaseType::Float,
957  /*p_name=*/GetMemberNameAtIndex(struct_type, i),
958  /*p_offset=*/struct_member_offset,
959  /*p_size=*/12,
960  /*p_byte_length=*/total_length,
961  /*p_array_elements=*/count,
962  /*p_element_padding=*/4,
963  /*p_float_type=*/"ShaderFloatType::kMat3",
964  });
965  current_byte_offset += total_length;
966  continue;
967  }
968 
969  // Tightly packed 4x4 Matrix is special cased as we know how to work with
970  // those.
971  if (member.basetype == spirv_cross::SPIRType::BaseType::Float && //
972  member.width == sizeof(Scalar) * 8 && //
973  member.columns == 4 && //
974  member.vecsize == 4 //
975  ) {
976  uint32_t stride = GetArrayStride<sizeof(Matrix)>(struct_type, member, i);
977  uint32_t element_padding = stride - sizeof(Matrix);
978  result.emplace_back(StructMember{
979  /*p_type=*/"Matrix",
980  /*p_base_type=*/member.basetype,
981  /*p_name=*/GetMemberNameAtIndex(struct_type, i),
982  /*p_offset=*/struct_member_offset,
983  /*p_size=*/sizeof(Matrix),
984  /*p_byte_length=*/stride * array_elements.value_or(1),
985  /*p_array_elements=*/array_elements,
986  /*p_element_padding=*/element_padding,
987  /*p_float_type=*/"ShaderFloatType::kMat4",
988  });
989  current_byte_offset += stride * array_elements.value_or(1);
990  continue;
991  }
992 
993  // Tightly packed UintPoint32 (uvec2)
994  if (member.basetype == spirv_cross::SPIRType::BaseType::UInt && //
995  member.width == sizeof(uint32_t) * 8 && //
996  member.columns == 1 && //
997  member.vecsize == 2 //
998  ) {
999  uint32_t stride =
1000  GetArrayStride<sizeof(UintPoint32)>(struct_type, member, i);
1001  uint32_t element_padding = stride - sizeof(UintPoint32);
1002  result.emplace_back(StructMember{
1003  /*p_type=*/"UintPoint32",
1004  /*p_base_type=*/member.basetype,
1005  /*p_name=*/GetMemberNameAtIndex(struct_type, i),
1006  /*p_offset=*/struct_member_offset,
1007  /*p_size=*/sizeof(UintPoint32),
1008  /*p_byte_length=*/stride * array_elements.value_or(1),
1009  /*p_array_elements=*/array_elements,
1010  /*p_element_padding=*/element_padding,
1011  });
1012  current_byte_offset += stride * array_elements.value_or(1);
1013  continue;
1014  }
1015 
1016  // Tightly packed UintPoint32 (ivec2)
1017  if (member.basetype == spirv_cross::SPIRType::BaseType::Int && //
1018  member.width == sizeof(int32_t) * 8 && //
1019  member.columns == 1 && //
1020  member.vecsize == 2 //
1021  ) {
1022  uint32_t stride =
1023  GetArrayStride<sizeof(IPoint32)>(struct_type, member, i);
1024  uint32_t element_padding = stride - sizeof(IPoint32);
1025  result.emplace_back(StructMember{
1026  /*p_type=*/"IPoint32",
1027  /*p_base_type=*/member.basetype,
1028  /*p_name=*/GetMemberNameAtIndex(struct_type, i),
1029  /*p_offset=*/struct_member_offset,
1030  /*p_size=*/sizeof(IPoint32),
1031  /*p_byte_length=*/stride * array_elements.value_or(1),
1032  /*p_array_elements=*/array_elements,
1033  /*p_element_padding=*/element_padding,
1034  });
1035  current_byte_offset += stride * array_elements.value_or(1);
1036  continue;
1037  }
1038 
1039  // Tightly packed Point (vec2).
1040  if (member.basetype == spirv_cross::SPIRType::BaseType::Float && //
1041  member.width == sizeof(float) * 8 && //
1042  member.columns == 1 && //
1043  member.vecsize == 2 //
1044  ) {
1045  uint32_t stride = GetArrayStride<sizeof(Point)>(struct_type, member, i);
1046  uint32_t element_padding = stride - sizeof(Point);
1047  result.emplace_back(StructMember{
1048  /*p_type=*/"Point",
1049  /*p_base_type=*/member.basetype,
1050  /*p_name=*/GetMemberNameAtIndex(struct_type, i),
1051  /*p_offset=*/struct_member_offset,
1052  /*p_size=*/sizeof(Point),
1053  /*p_byte_length=*/stride * array_elements.value_or(1),
1054  /*p_array_elements=*/array_elements,
1055  /*p_element_padding=*/element_padding,
1056  /*p_float_type=*/"ShaderFloatType::kVec2",
1057  });
1058  current_byte_offset += stride * array_elements.value_or(1);
1059  continue;
1060  }
1061 
1062  // Tightly packed Vector3.
1063  if (member.basetype == spirv_cross::SPIRType::BaseType::Float && //
1064  member.width == sizeof(float) * 8 && //
1065  member.columns == 1 && //
1066  member.vecsize == 3 //
1067  ) {
1068  uint32_t stride = GetArrayStride<sizeof(Vector3)>(struct_type, member, i);
1069  uint32_t element_padding = stride - sizeof(Vector3);
1070  result.emplace_back(StructMember{
1071  /*p_type=*/"Vector3",
1072  /*p_base_type=*/member.basetype,
1073  /*p_name=*/GetMemberNameAtIndex(struct_type, i),
1074  /*p_offset=*/struct_member_offset,
1075  /*p_size=*/sizeof(Vector3),
1076  /*p_byte_length=*/stride * array_elements.value_or(1),
1077  /*p_array_elements=*/array_elements,
1078  /*p_element_padding=*/element_padding,
1079  /*p_float_type=*/"ShaderFloatType::kVec3",
1080  });
1081  current_byte_offset += stride * array_elements.value_or(1);
1082  continue;
1083  }
1084 
1085  // Tightly packed Vector4.
1086  if (member.basetype == spirv_cross::SPIRType::BaseType::Float && //
1087  member.width == sizeof(float) * 8 && //
1088  member.columns == 1 && //
1089  member.vecsize == 4 //
1090  ) {
1091  uint32_t stride = GetArrayStride<sizeof(Vector4)>(struct_type, member, i);
1092  uint32_t element_padding = stride - sizeof(Vector4);
1093  result.emplace_back(StructMember{
1094  /*p_type=*/"Vector4",
1095  /*p_base_type=*/member.basetype,
1096  /*p_name=*/GetMemberNameAtIndex(struct_type, i),
1097  /*p_offset=*/struct_member_offset,
1098  /*p_size=*/sizeof(Vector4),
1099  /*p_byte_length=*/stride * array_elements.value_or(1),
1100  /*p_array_elements=*/array_elements,
1101  /*p_element_padding=*/element_padding,
1102  /*p_float_type=*/"ShaderFloatType::kVec4",
1103  });
1104  current_byte_offset += stride * array_elements.value_or(1);
1105  continue;
1106  }
1107 
1108  // Tightly packed half Point (vec2).
1109  if (member.basetype == spirv_cross::SPIRType::BaseType::Half && //
1110  member.width == sizeof(Half) * 8 && //
1111  member.columns == 1 && //
1112  member.vecsize == 2 //
1113  ) {
1114  uint32_t stride =
1115  GetArrayStride<sizeof(HalfVector2)>(struct_type, member, i);
1116  uint32_t element_padding = stride - sizeof(HalfVector2);
1117  result.emplace_back(StructMember{
1118  /*p_type=*/"HalfVector2",
1119  /*p_base_type=*/member.basetype,
1120  /*p_name=*/GetMemberNameAtIndex(struct_type, i),
1121  /*p_offset=*/struct_member_offset,
1122  /*p_size=*/sizeof(HalfVector2),
1123  /*p_byte_length=*/stride * array_elements.value_or(1),
1124  /*p_array_elements=*/array_elements,
1125  /*p_element_padding=*/element_padding,
1126  });
1127  current_byte_offset += stride * array_elements.value_or(1);
1128  continue;
1129  }
1130 
1131  // Tightly packed Half Float Vector3.
1132  if (member.basetype == spirv_cross::SPIRType::BaseType::Half && //
1133  member.width == sizeof(Half) * 8 && //
1134  member.columns == 1 && //
1135  member.vecsize == 3 //
1136  ) {
1137  uint32_t stride =
1138  GetArrayStride<sizeof(HalfVector3)>(struct_type, member, i);
1139  uint32_t element_padding = stride - sizeof(HalfVector3);
1140  result.emplace_back(StructMember{
1141  /*p_type=*/"HalfVector3",
1142  /*p_base_type=*/member.basetype,
1143  /*p_name=*/GetMemberNameAtIndex(struct_type, i),
1144  /*p_offset=*/struct_member_offset,
1145  /*p_size=*/sizeof(HalfVector3),
1146  /*p_byte_length=*/stride * array_elements.value_or(1),
1147  /*p_array_elements=*/array_elements,
1148  /*p_element_padding=*/element_padding,
1149  });
1150  current_byte_offset += stride * array_elements.value_or(1);
1151  continue;
1152  }
1153 
1154  // Tightly packed Half Float Vector4.
1155  if (member.basetype == spirv_cross::SPIRType::BaseType::Half && //
1156  member.width == sizeof(Half) * 8 && //
1157  member.columns == 1 && //
1158  member.vecsize == 4 //
1159  ) {
1160  uint32_t stride =
1161  GetArrayStride<sizeof(HalfVector4)>(struct_type, member, i);
1162  uint32_t element_padding = stride - sizeof(HalfVector4);
1163  result.emplace_back(StructMember{
1164  /*p_type=*/"HalfVector4",
1165  /*p_base_type=*/member.basetype,
1166  /*p_name=*/GetMemberNameAtIndex(struct_type, i),
1167  /*p_offset=*/struct_member_offset,
1168  /*p_size=*/sizeof(HalfVector4),
1169  /*p_byte_length=*/stride * array_elements.value_or(1),
1170  /*p_array_elements=*/array_elements,
1171  /*p_element_padding=*/element_padding,
1172  });
1173  current_byte_offset += stride * array_elements.value_or(1);
1174  continue;
1175  }
1176 
1177  // Other isolated scalars (like bool, int, float/Scalar, etc..).
1178  {
1179  auto maybe_known_type = ReadKnownScalarType(member.basetype);
1180  if (maybe_known_type.has_value() && //
1181  member.columns == 1 && //
1182  member.vecsize == 1 //
1183  ) {
1184  uint32_t stride = GetArrayStride<0>(struct_type, member, i);
1185  if (stride == 0) {
1186  stride = maybe_known_type.value().byte_size;
1187  }
1188  std::optional<std::string> float_type = std::nullopt;
1189  if (member.basetype == spirv_cross::SPIRType::BaseType::Float) {
1190  float_type = "ShaderFloatType::kFloat";
1191  }
1192  uint32_t element_padding = stride - maybe_known_type.value().byte_size;
1193 
1194  // Add the type directly.
1195  result.emplace_back(StructMember{
1196  /*p_type=*/maybe_known_type.value().name,
1197  /*p_base_type=*/member.basetype,
1198  /*p_name=*/GetMemberNameAtIndex(struct_type, i),
1199  /*p_offset=*/struct_member_offset,
1200  /*p_size=*/maybe_known_type.value().byte_size,
1201  /*p_byte_length=*/stride * array_elements.value_or(1),
1202  /*p_array_elements=*/array_elements,
1203  /*p_element_padding=*/element_padding,
1204  /*p_float_type=*/float_type,
1205  });
1206  current_byte_offset += stride * array_elements.value_or(1);
1207  continue;
1208  }
1209  }
1210 
1211  // Catch all for unknown types. Just add the necessary padding to the struct
1212  // and move on.
1213  {
1214  const size_t size = (member.width * member.columns * member.vecsize) / 8u;
1215  uint32_t stride = GetArrayStride<0>(struct_type, member, i);
1216  if (stride == 0) {
1217  stride = size;
1218  }
1219  size_t element_padding = stride - size;
1220  result.emplace_back(StructMember{
1221  /*p_type=*/TypeNameWithPaddingOfSize(size),
1222  /*p_base_type=*/member.basetype,
1223  /*p_name=*/GetMemberNameAtIndex(struct_type, i),
1224  /*p_offset=*/struct_member_offset,
1225  /*p_size=*/size,
1226  /*p_byte_length=*/stride * array_elements.value_or(1),
1227  /*p_array_elements=*/array_elements,
1228  /*p_element_padding=*/element_padding,
1229  });
1230  current_byte_offset += stride * array_elements.value_or(1);
1231  continue;
1232  }
1233  }
1234 
1235  if (max_member_alignment > 0u) {
1236  const size_t struct_length = current_byte_offset;
1237  {
1238  const size_t excess = struct_length % max_member_alignment;
1239  if (excess != 0) {
1240  const auto padding = max_member_alignment - excess;
1241  result.emplace_back(StructMember{
1242  /*p_type=*/TypeNameWithPaddingOfSize(padding),
1243  /*p_base_type=*/spirv_cross::SPIRType::BaseType::Void,
1244  /*p_name=*/"_PADDING_",
1245  /*p_offset=*/current_byte_offset,
1246  /*p_size=*/padding,
1247  /*p_byte_length=*/padding,
1248  /*p_array_elements=*/std::nullopt,
1249  /*p_element_padding=*/0,
1250  });
1251  }
1252  }
1253  }
1254 
1255  return result;
1256 }
1257 
1258 std::optional<Reflector::StructDefinition> Reflector::ReflectStructDefinition(
1259  const spirv_cross::TypeID& type_id) const {
1260  const auto& type = compiler_->get_type(type_id);
1261  if (type.basetype != spirv_cross::SPIRType::BaseType::Struct) {
1262  return std::nullopt;
1263  }
1264 
1265  const auto struct_name = compiler_->get_name(type_id);
1266  if (struct_name.find("_RESERVED_IDENTIFIER_") != std::string::npos) {
1267  return std::nullopt;
1268  }
1269 
1270  auto struct_members = ReadStructMembers(type_id);
1271  auto reflected_struct_size = GetReflectedStructSize(struct_members);
1272 
1273  StructDefinition struc;
1274  struc.name = struct_name;
1275  struc.byte_length = reflected_struct_size;
1276  struc.members = std::move(struct_members);
1277  return struc;
1278 }
1279 
1280 nlohmann::json::object_t Reflector::EmitStructDefinition(
1281  std::optional<Reflector::StructDefinition> struc) const {
1282  nlohmann::json::object_t result;
1283  result["name"] = struc->name;
1284  result["byte_length"] = struc->byte_length;
1285  auto& members = result["members"] = nlohmann::json::array_t{};
1286  for (const auto& struct_member : struc->members) {
1287  auto& member = members.emplace_back(nlohmann::json::object_t{});
1288  member["name"] = struct_member.name;
1289  member["type"] = struct_member.type;
1290  member["base_type"] =
1291  StructMember::BaseTypeToString(struct_member.base_type);
1292  member["offset"] = struct_member.offset;
1293  member["byte_length"] = struct_member.byte_length;
1294  if (struct_member.array_elements.has_value()) {
1295  member["array_elements"] = struct_member.array_elements.value();
1296  } else {
1297  member["array_elements"] = "std::nullopt";
1298  }
1299  member["element_padding"] = struct_member.element_padding;
1300  if (struct_member.float_type.has_value()) {
1301  member["float_type"] = struct_member.float_type.value();
1302  } else {
1303  member["float_type"] = "std::nullopt";
1304  }
1305  }
1306  return result;
1307 }
1308 
1309 struct VertexType {
1310  std::string type_name;
1311  spirv_cross::SPIRType::BaseType base_type;
1312  std::string variable_name;
1313  size_t byte_length = 0u;
1314 };
1315 
1317  const spirv_cross::Compiler& compiler,
1318  const spirv_cross::Resource* resource) {
1319  VertexType result;
1320  result.variable_name = resource->name;
1321  const auto& type = compiler.get_type(resource->type_id);
1322  result.base_type = type.basetype;
1323  const auto total_size = type.columns * type.vecsize * type.width / 8u;
1324  result.byte_length = total_size;
1325 
1326  if (type.basetype == spirv_cross::SPIRType::BaseType::Float &&
1327  type.columns == 1u && type.vecsize == 2u &&
1328  type.width == sizeof(float) * 8u) {
1329  result.type_name = "Point";
1330  } else if (type.basetype == spirv_cross::SPIRType::BaseType::Float &&
1331  type.columns == 1u && type.vecsize == 4u &&
1332  type.width == sizeof(float) * 8u) {
1333  result.type_name = "Vector4";
1334  } else if (type.basetype == spirv_cross::SPIRType::BaseType::Float &&
1335  type.columns == 1u && type.vecsize == 3u &&
1336  type.width == sizeof(float) * 8u) {
1337  result.type_name = "Vector3";
1338  } else if (type.basetype == spirv_cross::SPIRType::BaseType::Float &&
1339  type.columns == 1u && type.vecsize == 1u &&
1340  type.width == sizeof(float) * 8u) {
1341  result.type_name = "Scalar";
1342  } else if (type.basetype == spirv_cross::SPIRType::BaseType::Int &&
1343  type.columns == 1u && type.vecsize == 1u &&
1344  type.width == sizeof(int32_t) * 8u) {
1345  result.type_name = "int32_t";
1346  } else {
1347  // Catch all unknown padding.
1348  result.type_name = TypeNameWithPaddingOfSize(total_size);
1349  }
1350 
1351  return result;
1352 }
1353 
1354 std::optional<Reflector::StructDefinition>
1355 Reflector::ReflectPerVertexStructDefinition(
1356  const spirv_cross::SmallVector<spirv_cross::Resource>& stage_inputs) const {
1357  // Avoid emitting a zero sized structure. The code gen templates assume a
1358  // non-zero size.
1359  if (stage_inputs.empty()) {
1360  return std::nullopt;
1361  }
1362 
1363  // Validate locations are contiguous and there are no duplicates.
1364  std::set<uint32_t> locations;
1365  for (const auto& input : stage_inputs) {
1366  auto location = compiler_->get_decoration(
1367  input.id, spv::Decoration::DecorationLocation);
1368  if (locations.count(location) != 0) {
1369  // Duplicate location. Bail.
1370  return std::nullopt;
1371  }
1372  locations.insert(location);
1373  }
1374 
1375  for (size_t i = 0; i < locations.size(); i++) {
1376  if (locations.count(i) != 1) {
1377  // Locations are not contiguous. This usually happens when a single stage
1378  // input takes multiple input slots. No reflection information can be
1379  // generated for such cases anyway. So bail! It is up to the shader author
1380  // to make sure one stage input maps to a single input slot.
1381  return std::nullopt;
1382  }
1383  }
1384 
1385  auto input_for_location =
1386  [&](uint32_t queried_location) -> const spirv_cross::Resource* {
1387  for (const auto& input : stage_inputs) {
1388  auto location = compiler_->get_decoration(
1389  input.id, spv::Decoration::DecorationLocation);
1390  if (location == queried_location) {
1391  return &input;
1392  }
1393  }
1394  // This really cannot happen with all the validation above.
1395  FML_UNREACHABLE();
1396  return nullptr;
1397  };
1398 
1399  StructDefinition struc;
1400  struc.name = "PerVertexData";
1401  struc.byte_length = 0u;
1402  for (size_t i = 0; i < locations.size(); i++) {
1403  auto resource = input_for_location(i);
1404  if (resource == nullptr) {
1405  return std::nullopt;
1406  }
1407  const auto vertex_type =
1408  VertexTypeFromInputResource(*compiler_.GetCompiler(), resource);
1409 
1410  auto member = StructMember{
1411  /*p_type=*/vertex_type.type_name,
1412  /*p_base_type=*/vertex_type.base_type,
1413  /*p_name=*/vertex_type.variable_name,
1414  /*p_offset=*/struc.byte_length,
1415  /*p_size=*/vertex_type.byte_length,
1416  /*p_byte_length=*/vertex_type.byte_length,
1417  /*p_array_elements=*/std::nullopt,
1418  /*p_element_padding=*/0,
1419  };
1420  struc.byte_length += vertex_type.byte_length;
1421  struc.members.emplace_back(std::move(member));
1422  }
1423  return struc;
1424 }
1425 
1426 std::optional<std::string> Reflector::GetMemberNameAtIndexIfExists(
1427  const spirv_cross::SPIRType& parent_type,
1428  size_t index) const {
1429  if (parent_type.type_alias != 0) {
1430  return GetMemberNameAtIndexIfExists(
1431  compiler_->get_type(parent_type.type_alias), index);
1432  }
1433 
1434  if (auto found = ir_->meta.find(parent_type.self); found != ir_->meta.end()) {
1435  const auto& members = found->second.members;
1436  if (index < members.size() && !members[index].alias.empty()) {
1437  return members[index].alias;
1438  }
1439  }
1440  return std::nullopt;
1441 }
1442 
1443 std::string Reflector::GetMemberNameAtIndex(
1444  const spirv_cross::SPIRType& parent_type,
1445  size_t index,
1446  std::string suffix) const {
1447  if (auto name = GetMemberNameAtIndexIfExists(parent_type, index);
1448  name.has_value()) {
1449  return name.value();
1450  }
1451  static std::atomic_size_t sUnnamedMembersID;
1452  std::stringstream stream;
1453  stream << "unnamed_" << sUnnamedMembersID++ << suffix;
1454  return stream.str();
1455 }
1456 
1457 std::vector<Reflector::BindPrototype> Reflector::ReflectBindPrototypes(
1458  const spirv_cross::ShaderResources& resources,
1459  spv::ExecutionModel execution_model) const {
1460  std::vector<BindPrototype> prototypes;
1461  for (const auto& uniform_buffer : resources.uniform_buffers) {
1462  auto& proto = prototypes.emplace_back(BindPrototype{});
1463  proto.return_type = "bool";
1464  proto.name = ToCamelCase(uniform_buffer.name);
1465  proto.descriptor_type = "DescriptorType::kUniformBuffer";
1466  {
1467  std::stringstream stream;
1468  stream << "Bind uniform buffer for resource named " << uniform_buffer.name
1469  << ".";
1470  proto.docstring = stream.str();
1471  }
1472  proto.args.push_back(BindPrototypeArgument{
1473  .type_name = "ResourceBinder&",
1474  .argument_name = "command",
1475  });
1476  proto.args.push_back(BindPrototypeArgument{
1477  .type_name = "BufferView",
1478  .argument_name = "view",
1479  });
1480  }
1481  for (const auto& storage_buffer : resources.storage_buffers) {
1482  auto& proto = prototypes.emplace_back(BindPrototype{});
1483  proto.return_type = "bool";
1484  proto.name = ToCamelCase(storage_buffer.name);
1485  proto.descriptor_type = "DescriptorType::kStorageBuffer";
1486  {
1487  std::stringstream stream;
1488  stream << "Bind storage buffer for resource named " << storage_buffer.name
1489  << ".";
1490  proto.docstring = stream.str();
1491  }
1492  proto.args.push_back(BindPrototypeArgument{
1493  .type_name = "ResourceBinder&",
1494  .argument_name = "command",
1495  });
1496  proto.args.push_back(BindPrototypeArgument{
1497  .type_name = "BufferView",
1498  .argument_name = "view",
1499  });
1500  }
1501  for (const auto& sampled_image : resources.sampled_images) {
1502  auto& proto = prototypes.emplace_back(BindPrototype{});
1503  proto.return_type = "bool";
1504  proto.name = ToCamelCase(sampled_image.name);
1505  proto.descriptor_type = "DescriptorType::kSampledImage";
1506  {
1507  std::stringstream stream;
1508  stream << "Bind combined image sampler for resource named "
1509  << sampled_image.name << ".";
1510  proto.docstring = stream.str();
1511  }
1512  proto.args.push_back(BindPrototypeArgument{
1513  .type_name = "ResourceBinder&",
1514  .argument_name = "command",
1515  });
1516  proto.args.push_back(BindPrototypeArgument{
1517  .type_name = "std::shared_ptr<const Texture>",
1518  .argument_name = "texture",
1519  });
1520  proto.args.push_back(BindPrototypeArgument{
1521  .type_name = "raw_ptr<const Sampler>",
1522  .argument_name = "sampler",
1523  });
1524  }
1525  for (const auto& separate_image : resources.separate_images) {
1526  auto& proto = prototypes.emplace_back(BindPrototype{});
1527  proto.return_type = "bool";
1528  proto.name = ToCamelCase(separate_image.name);
1529  proto.descriptor_type = "DescriptorType::kImage";
1530  {
1531  std::stringstream stream;
1532  stream << "Bind separate image for resource named " << separate_image.name
1533  << ".";
1534  proto.docstring = stream.str();
1535  }
1536  proto.args.push_back(BindPrototypeArgument{
1537  .type_name = "Command&",
1538  .argument_name = "command",
1539  });
1540  proto.args.push_back(BindPrototypeArgument{
1541  .type_name = "std::shared_ptr<const Texture>",
1542  .argument_name = "texture",
1543  });
1544  }
1545  for (const auto& separate_sampler : resources.separate_samplers) {
1546  auto& proto = prototypes.emplace_back(BindPrototype{});
1547  proto.return_type = "bool";
1548  proto.name = ToCamelCase(separate_sampler.name);
1549  proto.descriptor_type = "DescriptorType::kSampler";
1550  {
1551  std::stringstream stream;
1552  stream << "Bind separate sampler for resource named "
1553  << separate_sampler.name << ".";
1554  proto.docstring = stream.str();
1555  }
1556  proto.args.push_back(BindPrototypeArgument{
1557  .type_name = "Command&",
1558  .argument_name = "command",
1559  });
1560  proto.args.push_back(BindPrototypeArgument{
1561  .type_name = "std::shared_ptr<const Sampler>",
1562  .argument_name = "sampler",
1563  });
1564  }
1565  return prototypes;
1566 }
1567 
1568 nlohmann::json::array_t Reflector::EmitBindPrototypes(
1569  const spirv_cross::ShaderResources& resources,
1570  spv::ExecutionModel execution_model) const {
1571  const auto prototypes = ReflectBindPrototypes(resources, execution_model);
1572  nlohmann::json::array_t result;
1573  for (const auto& res : prototypes) {
1574  auto& item = result.emplace_back(nlohmann::json::object_t{});
1575  item["return_type"] = res.return_type;
1576  item["name"] = res.name;
1577  item["docstring"] = res.docstring;
1578  item["descriptor_type"] = res.descriptor_type;
1579  auto& args = item["args"] = nlohmann::json::array_t{};
1580  for (const auto& arg : res.args) {
1581  auto& json_arg = args.emplace_back(nlohmann::json::object_t{});
1582  json_arg["type_name"] = arg.type_name;
1583  json_arg["argument_name"] = arg.argument_name;
1584  }
1585  }
1586  return result;
1587 }
1588 
1589 } // namespace compiler
1590 } // namespace impeller
static const char * kVulkanUBOName
Definition: runtime_stage.h:23
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
uint32_t location
int32_t value
Vector2 padding
The halo padding in source space.
static std::optional< KnownType > ReadKnownScalarType(spirv_cross::SPIRType::BaseType type)
Definition: reflector.cc:804
static std::string TypeNameWithPaddingOfSize(size_t size)
Definition: reflector.cc:793
static VertexType VertexTypeFromInputResource(const spirv_cross::Compiler &compiler, const spirv_cross::Resource *resource)
Definition: reflector.cc:1316
static std::string ToString(CompilerBackend::Type type)
Definition: reflector.cc:620
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:851
static std::optional< RuntimeStageBackend > GetRuntimeStageBackend(TargetPlatform target_platform)
Definition: reflector.cc:317
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
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
constexpr auto kPadding
Definition: comparable.h:93
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:45
spirv_cross::SPIRType::BaseType base_type
Definition: reflector.cc:1311
#define VALIDATION_LOG
Definition: validation.h:91