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