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  // 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 
339 std::shared_ptr<fml::Mapping> Reflector::GenerateReflectionHeader() const {
340  return InflateTemplate(kReflectionHeaderTemplate);
341 }
342 
343 std::shared_ptr<fml::Mapping> Reflector::GenerateReflectionCC() const {
344  return InflateTemplate(kReflectionCCTemplate);
345 }
346 
347 static std::optional<RuntimeStageBackend> GetRuntimeStageBackend(
348  TargetPlatform target_platform) {
349  switch (target_platform) {
356  return std::nullopt;
367  }
368  FML_UNREACHABLE();
369 }
370 
371 std::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 
407  if (TargetPlatformIsMetal(options_.target_platform) &&
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(
420  }
421  uniform_description.padding_layout.push_back(
423  }
424  }
425  }
426 
427  FML_CHECK(data->backend != RuntimeStageBackend::kVulkan ||
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 
543 std::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 
640 std::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 
652 static std::string ToString(CompilerBackend::Type type) {
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  }
663  FML_UNREACHABLE();
664 }
665 
666 std::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 
693 std::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 
720 std::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 
731 std::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 
763 std::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 
801 std::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 
825 static std::string TypeNameWithPaddingOfSize(size_t size) {
826  std::stringstream stream;
827  stream << "Padding<" << size << ">";
828  return stream.str();
829 }
830 
831 struct KnownType {
832  std::string name;
833  size_t byte_size = 0;
834 };
835 
836 static 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 ///
883 static 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 
891 std::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{
1303  /*p_type=*/TypeNameWithPaddingOfSize(padding),
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 
1319 std::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 
1341 nlohmann::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 
1370 struct VertexType {
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 
1415 std::optional<Reflector::StructDefinition>
1416 Reflector::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.
1456  FML_UNREACHABLE();
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 
1487 std::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 
1504 std::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 
1518 std::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 
1629 nlohmann::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
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:836
static std::string TypeNameWithPaddingOfSize(size_t size)
Definition: reflector.cc:825
static VertexType VertexTypeFromInputResource(const spirv_cross::Compiler &compiler, const spirv_cross::Resource *resource)
Definition: reflector.cc:1377
static std::string ToString(CompilerBackend::Type type)
Definition: reflector.cc:652
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::optional< RuntimeStageBackend > GetRuntimeStageBackend(TargetPlatform target_platform)
Definition: reflector.cc:347
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:52
spirv_cross::SPIRType::BaseType base_type
Definition: reflector.cc:1372
#define VALIDATION_LOG
Definition: validation.h:91