1 /*
2 * Copyright 2018-2020 Bradley Austin Davis
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "spirv_reflect.hpp"
18 #include "spirv_glsl.hpp"
19 #include <iomanip>
20
21 using namespace spv;
22 using namespace SPIRV_CROSS_NAMESPACE;
23 using namespace std;
24
25 namespace simple_json
26 {
27 enum class Type
28 {
29 Object,
30 Array,
31 };
32
33 using State = std::pair<Type, bool>;
34 using Stack = std::stack<State>;
35
36 class Stream
37 {
38 Stack stack;
39 StringStream<> buffer;
40 uint32_t indent{ 0 };
41 char current_locale_radix_character = '.';
42
43 public:
set_current_locale_radix_character(char c)44 void set_current_locale_radix_character(char c)
45 {
46 current_locale_radix_character = c;
47 }
48
49 void begin_json_object();
50 void end_json_object();
51 void emit_json_key(const std::string &key);
52 void emit_json_key_value(const std::string &key, const std::string &value);
53 void emit_json_key_value(const std::string &key, bool value);
54 void emit_json_key_value(const std::string &key, uint32_t value);
55 void emit_json_key_value(const std::string &key, int32_t value);
56 void emit_json_key_value(const std::string &key, float value);
57 void emit_json_key_object(const std::string &key);
58 void emit_json_key_array(const std::string &key);
59
60 void begin_json_array();
61 void end_json_array();
62 void emit_json_array_value(const std::string &value);
63 void emit_json_array_value(uint32_t value);
64 void emit_json_array_value(bool value);
65
str() const66 std::string str() const
67 {
68 return buffer.str();
69 }
70
71 private:
statement_indent()72 inline void statement_indent()
73 {
74 for (uint32_t i = 0; i < indent; i++)
75 buffer << " ";
76 }
77
78 template <typename T>
statement_inner(T && t)79 inline void statement_inner(T &&t)
80 {
81 buffer << std::forward<T>(t);
82 }
83
84 template <typename T, typename... Ts>
statement_inner(T && t,Ts &&...ts)85 inline void statement_inner(T &&t, Ts &&... ts)
86 {
87 buffer << std::forward<T>(t);
88 statement_inner(std::forward<Ts>(ts)...);
89 }
90
91 template <typename... Ts>
statement(Ts &&...ts)92 inline void statement(Ts &&... ts)
93 {
94 statement_indent();
95 statement_inner(std::forward<Ts>(ts)...);
96 buffer << '\n';
97 }
98
99 template <typename... Ts>
statement_no_return(Ts &&...ts)100 void statement_no_return(Ts &&... ts)
101 {
102 statement_indent();
103 statement_inner(std::forward<Ts>(ts)...);
104 }
105 };
106 } // namespace simple_json
107
108 using namespace simple_json;
109
110 // Hackery to emit JSON without using nlohmann/json C++ library (which requires a
111 // higher level of compiler compliance than is required by SPIRV-Cross
begin_json_array()112 void Stream::begin_json_array()
113 {
114 if (!stack.empty() && stack.top().second)
115 {
116 statement_inner(",\n");
117 }
118 statement("[");
119 ++indent;
120 stack.emplace(Type::Array, false);
121 }
122
end_json_array()123 void Stream::end_json_array()
124 {
125 if (stack.empty() || stack.top().first != Type::Array)
126 SPIRV_CROSS_THROW("Invalid JSON state");
127 if (stack.top().second)
128 {
129 statement_inner("\n");
130 }
131 --indent;
132 statement_no_return("]");
133 stack.pop();
134 if (!stack.empty())
135 {
136 stack.top().second = true;
137 }
138 }
139
emit_json_array_value(const std::string & value)140 void Stream::emit_json_array_value(const std::string &value)
141 {
142 if (stack.empty() || stack.top().first != Type::Array)
143 SPIRV_CROSS_THROW("Invalid JSON state");
144
145 if (stack.top().second)
146 statement_inner(",\n");
147
148 statement_no_return("\"", value, "\"");
149 stack.top().second = true;
150 }
151
emit_json_array_value(uint32_t value)152 void Stream::emit_json_array_value(uint32_t value)
153 {
154 if (stack.empty() || stack.top().first != Type::Array)
155 SPIRV_CROSS_THROW("Invalid JSON state");
156 if (stack.top().second)
157 statement_inner(",\n");
158 statement_no_return(std::to_string(value));
159 stack.top().second = true;
160 }
161
emit_json_array_value(bool value)162 void Stream::emit_json_array_value(bool value)
163 {
164 if (stack.empty() || stack.top().first != Type::Array)
165 SPIRV_CROSS_THROW("Invalid JSON state");
166 if (stack.top().second)
167 statement_inner(",\n");
168 statement_no_return(value ? "true" : "false");
169 stack.top().second = true;
170 }
171
begin_json_object()172 void Stream::begin_json_object()
173 {
174 if (!stack.empty() && stack.top().second)
175 {
176 statement_inner(",\n");
177 }
178 statement("{");
179 ++indent;
180 stack.emplace(Type::Object, false);
181 }
182
end_json_object()183 void Stream::end_json_object()
184 {
185 if (stack.empty() || stack.top().first != Type::Object)
186 SPIRV_CROSS_THROW("Invalid JSON state");
187 if (stack.top().second)
188 {
189 statement_inner("\n");
190 }
191 --indent;
192 statement_no_return("}");
193 stack.pop();
194 if (!stack.empty())
195 {
196 stack.top().second = true;
197 }
198 }
199
emit_json_key(const std::string & key)200 void Stream::emit_json_key(const std::string &key)
201 {
202 if (stack.empty() || stack.top().first != Type::Object)
203 SPIRV_CROSS_THROW("Invalid JSON state");
204
205 if (stack.top().second)
206 statement_inner(",\n");
207 statement_no_return("\"", key, "\" : ");
208 stack.top().second = true;
209 }
210
emit_json_key_value(const std::string & key,const std::string & value)211 void Stream::emit_json_key_value(const std::string &key, const std::string &value)
212 {
213 emit_json_key(key);
214 statement_inner("\"", value, "\"");
215 }
216
emit_json_key_value(const std::string & key,uint32_t value)217 void Stream::emit_json_key_value(const std::string &key, uint32_t value)
218 {
219 emit_json_key(key);
220 statement_inner(value);
221 }
222
emit_json_key_value(const std::string & key,int32_t value)223 void Stream::emit_json_key_value(const std::string &key, int32_t value)
224 {
225 emit_json_key(key);
226 statement_inner(value);
227 }
228
emit_json_key_value(const std::string & key,float value)229 void Stream::emit_json_key_value(const std::string &key, float value)
230 {
231 emit_json_key(key);
232 statement_inner(convert_to_string(value, current_locale_radix_character));
233 }
234
emit_json_key_value(const std::string & key,bool value)235 void Stream::emit_json_key_value(const std::string &key, bool value)
236 {
237 emit_json_key(key);
238 statement_inner(value ? "true" : "false");
239 }
240
emit_json_key_object(const std::string & key)241 void Stream::emit_json_key_object(const std::string &key)
242 {
243 emit_json_key(key);
244 statement_inner("{\n");
245 ++indent;
246 stack.emplace(Type::Object, false);
247 }
248
emit_json_key_array(const std::string & key)249 void Stream::emit_json_key_array(const std::string &key)
250 {
251 emit_json_key(key);
252 statement_inner("[\n");
253 ++indent;
254 stack.emplace(Type::Array, false);
255 }
256
set_format(const std::string & format)257 void CompilerReflection::set_format(const std::string &format)
258 {
259 if (format != "json")
260 {
261 SPIRV_CROSS_THROW("Unsupported format");
262 }
263 }
264
compile()265 string CompilerReflection::compile()
266 {
267 json_stream = std::make_shared<simple_json::Stream>();
268 json_stream->set_current_locale_radix_character(current_locale_radix_character);
269 json_stream->begin_json_object();
270 reorder_type_alias();
271 emit_entry_points();
272 emit_types();
273 emit_resources();
274 emit_specialization_constants();
275 json_stream->end_json_object();
276 return json_stream->str();
277 }
278
naturally_emit_type(const SPIRType & type)279 static bool naturally_emit_type(const SPIRType &type)
280 {
281 return type.basetype == SPIRType::Struct && !type.pointer && type.array.empty();
282 }
283
type_is_reference(const SPIRType & type) const284 bool CompilerReflection::type_is_reference(const SPIRType &type) const
285 {
286 // Physical pointers and arrays of physical pointers need to refer to the pointee's type.
287 return type_is_top_level_physical_pointer(type) ||
288 (!type.array.empty() && type_is_top_level_physical_pointer(get<SPIRType>(type.parent_type)));
289 }
290
emit_types()291 void CompilerReflection::emit_types()
292 {
293 bool emitted_open_tag = false;
294
295 SmallVector<uint32_t> physical_pointee_types;
296
297 // If we have physical pointers or arrays of physical pointers, it's also helpful to emit the pointee type
298 // and chain the type hierarchy. For POD, arrays can emit the entire type in-place.
299 ir.for_each_typed_id<SPIRType>([&](uint32_t self, SPIRType &type) {
300 if (naturally_emit_type(type))
301 {
302 emit_type(self, emitted_open_tag);
303 }
304 else if (type_is_reference(type))
305 {
306 if (!naturally_emit_type(this->get<SPIRType>(type.parent_type)) &&
307 find(physical_pointee_types.begin(), physical_pointee_types.end(), type.parent_type) ==
308 physical_pointee_types.end())
309 {
310 physical_pointee_types.push_back(type.parent_type);
311 }
312 }
313 });
314
315 for (uint32_t pointee_type : physical_pointee_types)
316 emit_type(pointee_type, emitted_open_tag);
317
318 if (emitted_open_tag)
319 {
320 json_stream->end_json_object();
321 }
322 }
323
emit_type(uint32_t type_id,bool & emitted_open_tag)324 void CompilerReflection::emit_type(uint32_t type_id, bool &emitted_open_tag)
325 {
326 auto &type = get<SPIRType>(type_id);
327 auto name = type_to_glsl(type);
328
329 if (!emitted_open_tag)
330 {
331 json_stream->emit_json_key_object("types");
332 emitted_open_tag = true;
333 }
334 json_stream->emit_json_key_object("_" + std::to_string(type_id));
335 json_stream->emit_json_key_value("name", name);
336
337 if (type_is_top_level_physical_pointer(type))
338 {
339 json_stream->emit_json_key_value("type", "_" + std::to_string(type.parent_type));
340 json_stream->emit_json_key_value("physical_pointer", true);
341 }
342 else if (!type.array.empty())
343 {
344 emit_type_array(type);
345 json_stream->emit_json_key_value("type", "_" + std::to_string(type.parent_type));
346 json_stream->emit_json_key_value("array_stride", get_decoration(type_id, DecorationArrayStride));
347 }
348 else
349 {
350 json_stream->emit_json_key_array("members");
351 // FIXME ideally we'd like to emit the size of a structure as a
352 // convenience to people parsing the reflected JSON. The problem
353 // is that there's no implicit size for a type. It's final size
354 // will be determined by the top level declaration in which it's
355 // included. So there might be one size for the struct if it's
356 // included in a std140 uniform block and another if it's included
357 // in a std430 uniform block.
358 // The solution is to include *all* potential sizes as a map of
359 // layout type name to integer, but that will probably require
360 // some additional logic being written in this class, or in the
361 // parent CompilerGLSL class.
362 auto size = type.member_types.size();
363 for (uint32_t i = 0; i < size; ++i)
364 {
365 emit_type_member(type, i);
366 }
367 json_stream->end_json_array();
368 }
369
370 json_stream->end_json_object();
371 }
372
emit_type_member(const SPIRType & type,uint32_t index)373 void CompilerReflection::emit_type_member(const SPIRType &type, uint32_t index)
374 {
375 auto &membertype = get<SPIRType>(type.member_types[index]);
376 json_stream->begin_json_object();
377 auto name = to_member_name(type, index);
378 // FIXME we'd like to emit the offset of each member, but such offsets are
379 // context dependent. See the comment above regarding structure sizes
380 json_stream->emit_json_key_value("name", name);
381
382 if (type_is_reference(membertype))
383 {
384 json_stream->emit_json_key_value("type", "_" + std::to_string(membertype.parent_type));
385 }
386 else if (membertype.basetype == SPIRType::Struct)
387 {
388 json_stream->emit_json_key_value("type", "_" + std::to_string(membertype.self));
389 }
390 else
391 {
392 json_stream->emit_json_key_value("type", type_to_glsl(membertype));
393 }
394 emit_type_member_qualifiers(type, index);
395 json_stream->end_json_object();
396 }
397
emit_type_array(const SPIRType & type)398 void CompilerReflection::emit_type_array(const SPIRType &type)
399 {
400 if (!type_is_top_level_physical_pointer(type) && !type.array.empty())
401 {
402 json_stream->emit_json_key_array("array");
403 // Note that we emit the zeros here as a means of identifying
404 // unbounded arrays. This is necessary as otherwise there would
405 // be no way of differentiating between float[4] and float[4][]
406 for (const auto &value : type.array)
407 json_stream->emit_json_array_value(value);
408 json_stream->end_json_array();
409
410 json_stream->emit_json_key_array("array_size_is_literal");
411 for (const auto &value : type.array_size_literal)
412 json_stream->emit_json_array_value(value);
413 json_stream->end_json_array();
414 }
415 }
416
emit_type_member_qualifiers(const SPIRType & type,uint32_t index)417 void CompilerReflection::emit_type_member_qualifiers(const SPIRType &type, uint32_t index)
418 {
419 auto &membertype = get<SPIRType>(type.member_types[index]);
420 emit_type_array(membertype);
421 auto &memb = ir.meta[type.self].members;
422 if (index < memb.size())
423 {
424 auto &dec = memb[index];
425 if (dec.decoration_flags.get(DecorationLocation))
426 json_stream->emit_json_key_value("location", dec.location);
427 if (dec.decoration_flags.get(DecorationOffset))
428 json_stream->emit_json_key_value("offset", dec.offset);
429
430 // Array stride is a property of the array type, not the struct.
431 if (has_decoration(type.member_types[index], DecorationArrayStride))
432 json_stream->emit_json_key_value("array_stride",
433 get_decoration(type.member_types[index], DecorationArrayStride));
434
435 if (dec.decoration_flags.get(DecorationMatrixStride))
436 json_stream->emit_json_key_value("matrix_stride", dec.matrix_stride);
437 if (dec.decoration_flags.get(DecorationRowMajor))
438 json_stream->emit_json_key_value("row_major", true);
439
440 if (type_is_top_level_physical_pointer(membertype))
441 json_stream->emit_json_key_value("physical_pointer", true);
442 }
443 }
444
execution_model_to_str(spv::ExecutionModel model)445 string CompilerReflection::execution_model_to_str(spv::ExecutionModel model)
446 {
447 switch (model)
448 {
449 case ExecutionModelVertex:
450 return "vert";
451 case ExecutionModelTessellationControl:
452 return "tesc";
453 case ExecutionModelTessellationEvaluation:
454 return "tese";
455 case ExecutionModelGeometry:
456 return "geom";
457 case ExecutionModelFragment:
458 return "frag";
459 case ExecutionModelGLCompute:
460 return "comp";
461 case ExecutionModelRayGenerationNV:
462 return "rgen";
463 case ExecutionModelIntersectionNV:
464 return "rint";
465 case ExecutionModelAnyHitNV:
466 return "rahit";
467 case ExecutionModelClosestHitNV:
468 return "rchit";
469 case ExecutionModelMissNV:
470 return "rmiss";
471 case ExecutionModelCallableNV:
472 return "rcall";
473 default:
474 return "???";
475 }
476 }
477
478 // FIXME include things like the local_size dimensions, geometry output vertex count, etc
emit_entry_points()479 void CompilerReflection::emit_entry_points()
480 {
481 auto entries = get_entry_points_and_stages();
482 if (!entries.empty())
483 {
484 // Needed to make output deterministic.
485 sort(begin(entries), end(entries), [](const EntryPoint &a, const EntryPoint &b) -> bool {
486 if (a.execution_model < b.execution_model)
487 return true;
488 else if (a.execution_model > b.execution_model)
489 return false;
490 else
491 return a.name < b.name;
492 });
493
494 json_stream->emit_json_key_array("entryPoints");
495 for (auto &e : entries)
496 {
497 json_stream->begin_json_object();
498 json_stream->emit_json_key_value("name", e.name);
499 json_stream->emit_json_key_value("mode", execution_model_to_str(e.execution_model));
500 if (e.execution_model == ExecutionModelGLCompute)
501 {
502 const auto &spv_entry = get_entry_point(e.name, e.execution_model);
503
504 SpecializationConstant spec_x, spec_y, spec_z;
505 get_work_group_size_specialization_constants(spec_x, spec_y, spec_z);
506
507 json_stream->emit_json_key_array("workgroup_size");
508 json_stream->emit_json_array_value(spec_x.id != ID(0) ? spec_x.constant_id :
509 spv_entry.workgroup_size.x);
510 json_stream->emit_json_array_value(spec_y.id != ID(0) ? spec_y.constant_id :
511 spv_entry.workgroup_size.y);
512 json_stream->emit_json_array_value(spec_z.id != ID(0) ? spec_z.constant_id :
513 spv_entry.workgroup_size.z);
514 json_stream->end_json_array();
515
516 json_stream->emit_json_key_array("workgroup_size_is_spec_constant_id");
517 json_stream->emit_json_array_value(spec_x.id != ID(0));
518 json_stream->emit_json_array_value(spec_y.id != ID(0));
519 json_stream->emit_json_array_value(spec_z.id != ID(0));
520 json_stream->end_json_array();
521 }
522 json_stream->end_json_object();
523 }
524 json_stream->end_json_array();
525 }
526 }
527
emit_resources()528 void CompilerReflection::emit_resources()
529 {
530 auto res = get_shader_resources();
531 emit_resources("subpass_inputs", res.subpass_inputs);
532 emit_resources("inputs", res.stage_inputs);
533 emit_resources("outputs", res.stage_outputs);
534 emit_resources("textures", res.sampled_images);
535 emit_resources("separate_images", res.separate_images);
536 emit_resources("separate_samplers", res.separate_samplers);
537 emit_resources("images", res.storage_images);
538 emit_resources("ssbos", res.storage_buffers);
539 emit_resources("ubos", res.uniform_buffers);
540 emit_resources("push_constants", res.push_constant_buffers);
541 emit_resources("counters", res.atomic_counters);
542 emit_resources("acceleration_structures", res.acceleration_structures);
543 }
544
emit_resources(const char * tag,const SmallVector<Resource> & resources)545 void CompilerReflection::emit_resources(const char *tag, const SmallVector<Resource> &resources)
546 {
547 if (resources.empty())
548 {
549 return;
550 }
551
552 json_stream->emit_json_key_array(tag);
553 for (auto &res : resources)
554 {
555 auto &type = get_type(res.type_id);
556 auto typeflags = ir.meta[type.self].decoration.decoration_flags;
557 auto &mask = get_decoration_bitset(res.id);
558
559 // If we don't have a name, use the fallback for the type instead of the variable
560 // for SSBOs and UBOs since those are the only meaningful names to use externally.
561 // Push constant blocks are still accessed by name and not block name, even though they are technically Blocks.
562 bool is_push_constant = get_storage_class(res.id) == StorageClassPushConstant;
563 bool is_block = get_decoration_bitset(type.self).get(DecorationBlock) ||
564 get_decoration_bitset(type.self).get(DecorationBufferBlock);
565
566 ID fallback_id = !is_push_constant && is_block ? ID(res.base_type_id) : ID(res.id);
567
568 json_stream->begin_json_object();
569
570 if (type.basetype == SPIRType::Struct)
571 {
572 json_stream->emit_json_key_value("type", "_" + std::to_string(res.base_type_id));
573 }
574 else
575 {
576 json_stream->emit_json_key_value("type", type_to_glsl(type));
577 }
578
579 json_stream->emit_json_key_value("name", !res.name.empty() ? res.name : get_fallback_name(fallback_id));
580 {
581 bool ssbo_block = type.storage == StorageClassStorageBuffer ||
582 (type.storage == StorageClassUniform && typeflags.get(DecorationBufferBlock));
583 if (ssbo_block)
584 {
585 auto buffer_flags = get_buffer_block_flags(res.id);
586 if (buffer_flags.get(DecorationNonReadable))
587 json_stream->emit_json_key_value("writeonly", true);
588 if (buffer_flags.get(DecorationNonWritable))
589 json_stream->emit_json_key_value("readonly", true);
590 if (buffer_flags.get(DecorationRestrict))
591 json_stream->emit_json_key_value("restrict", true);
592 if (buffer_flags.get(DecorationCoherent))
593 json_stream->emit_json_key_value("coherent", true);
594 }
595 }
596
597 emit_type_array(type);
598
599 {
600 bool is_sized_block = is_block && (get_storage_class(res.id) == StorageClassUniform ||
601 get_storage_class(res.id) == StorageClassUniformConstant ||
602 get_storage_class(res.id) == StorageClassStorageBuffer);
603 if (is_sized_block)
604 {
605 uint32_t block_size = uint32_t(get_declared_struct_size(get_type(res.base_type_id)));
606 json_stream->emit_json_key_value("block_size", block_size);
607 }
608 }
609
610 if (type.storage == StorageClassPushConstant)
611 json_stream->emit_json_key_value("push_constant", true);
612 if (mask.get(DecorationLocation))
613 json_stream->emit_json_key_value("location", get_decoration(res.id, DecorationLocation));
614 if (mask.get(DecorationRowMajor))
615 json_stream->emit_json_key_value("row_major", true);
616 if (mask.get(DecorationColMajor))
617 json_stream->emit_json_key_value("column_major", true);
618 if (mask.get(DecorationIndex))
619 json_stream->emit_json_key_value("index", get_decoration(res.id, DecorationIndex));
620 if (type.storage != StorageClassPushConstant && mask.get(DecorationDescriptorSet))
621 json_stream->emit_json_key_value("set", get_decoration(res.id, DecorationDescriptorSet));
622 if (mask.get(DecorationBinding))
623 json_stream->emit_json_key_value("binding", get_decoration(res.id, DecorationBinding));
624 if (mask.get(DecorationInputAttachmentIndex))
625 json_stream->emit_json_key_value("input_attachment_index",
626 get_decoration(res.id, DecorationInputAttachmentIndex));
627 if (mask.get(DecorationOffset))
628 json_stream->emit_json_key_value("offset", get_decoration(res.id, DecorationOffset));
629
630 // For images, the type itself adds a layout qualifer.
631 // Only emit the format for storage images.
632 if (type.basetype == SPIRType::Image && type.image.sampled == 2)
633 {
634 const char *fmt = format_to_glsl(type.image.format);
635 if (fmt != nullptr)
636 json_stream->emit_json_key_value("format", std::string(fmt));
637 }
638 json_stream->end_json_object();
639 }
640 json_stream->end_json_array();
641 }
642
emit_specialization_constants()643 void CompilerReflection::emit_specialization_constants()
644 {
645 auto specialization_constants = get_specialization_constants();
646 if (specialization_constants.empty())
647 return;
648
649 json_stream->emit_json_key_array("specialization_constants");
650 for (const auto &spec_const : specialization_constants)
651 {
652 auto &c = get<SPIRConstant>(spec_const.id);
653 auto type = get<SPIRType>(c.constant_type);
654 json_stream->begin_json_object();
655 json_stream->emit_json_key_value("name", get_name(spec_const.id));
656 json_stream->emit_json_key_value("id", spec_const.constant_id);
657 json_stream->emit_json_key_value("type", type_to_glsl(type));
658 json_stream->emit_json_key_value("variable_id", spec_const.id);
659 switch (type.basetype)
660 {
661 case SPIRType::UInt:
662 json_stream->emit_json_key_value("default_value", c.scalar());
663 break;
664
665 case SPIRType::Int:
666 json_stream->emit_json_key_value("default_value", c.scalar_i32());
667 break;
668
669 case SPIRType::Float:
670 json_stream->emit_json_key_value("default_value", c.scalar_f32());
671 break;
672
673 case SPIRType::Boolean:
674 json_stream->emit_json_key_value("default_value", c.scalar() != 0);
675 break;
676
677 default:
678 break;
679 }
680 json_stream->end_json_object();
681 }
682 json_stream->end_json_array();
683 }
684
to_member_name(const SPIRType & type,uint32_t index) const685 string CompilerReflection::to_member_name(const SPIRType &type, uint32_t index) const
686 {
687 auto *type_meta = ir.find_meta(type.self);
688
689 if (type_meta)
690 {
691 auto &memb = type_meta->members;
692 if (index < memb.size() && !memb[index].alias.empty())
693 return memb[index].alias;
694 else
695 return join("_m", index);
696 }
697 else
698 return join("_m", index);
699 }
700