/* * Copyright 2018-2021 Bradley Austin Davis * SPDX-License-Identifier: Apache-2.0 OR MIT * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* * At your option, you may choose to accept this material under either: * 1. The Apache License, Version 2.0, found at , or * 2. The MIT License, found at . */ #include "spirv_reflect.hpp" #include "spirv_glsl.hpp" #include using namespace spv; using namespace SPIRV_CROSS_NAMESPACE; using namespace std; namespace simple_json { enum class Type { Object, Array, }; using State = std::pair; using Stack = std::stack; class Stream { Stack stack; StringStream<> buffer; uint32_t indent{ 0 }; char current_locale_radix_character = '.'; public: void set_current_locale_radix_character(char c) { current_locale_radix_character = c; } void begin_json_object(); void end_json_object(); void emit_json_key(const std::string &key); void emit_json_key_value(const std::string &key, const std::string &value); void emit_json_key_value(const std::string &key, bool value); void emit_json_key_value(const std::string &key, uint32_t value); void emit_json_key_value(const std::string &key, int32_t value); void emit_json_key_value(const std::string &key, float value); void emit_json_key_object(const std::string &key); void emit_json_key_array(const std::string &key); void begin_json_array(); void end_json_array(); void emit_json_array_value(const std::string &value); void emit_json_array_value(uint32_t value); void emit_json_array_value(bool value); std::string str() const { return buffer.str(); } private: inline void statement_indent() { for (uint32_t i = 0; i < indent; i++) buffer << " "; } template inline void statement_inner(T &&t) { buffer << std::forward(t); } template inline void statement_inner(T &&t, Ts &&... ts) { buffer << std::forward(t); statement_inner(std::forward(ts)...); } template inline void statement(Ts &&... ts) { statement_indent(); statement_inner(std::forward(ts)...); buffer << '\n'; } template void statement_no_return(Ts &&... ts) { statement_indent(); statement_inner(std::forward(ts)...); } }; } // namespace simple_json using namespace simple_json; // Hackery to emit JSON without using nlohmann/json C++ library (which requires a // higher level of compiler compliance than is required by SPIRV-Cross void Stream::begin_json_array() { if (!stack.empty() && stack.top().second) { statement_inner(",\n"); } statement("["); ++indent; stack.emplace(Type::Array, false); } void Stream::end_json_array() { if (stack.empty() || stack.top().first != Type::Array) SPIRV_CROSS_THROW("Invalid JSON state"); if (stack.top().second) { statement_inner("\n"); } --indent; statement_no_return("]"); stack.pop(); if (!stack.empty()) { stack.top().second = true; } } void Stream::emit_json_array_value(const std::string &value) { if (stack.empty() || stack.top().first != Type::Array) SPIRV_CROSS_THROW("Invalid JSON state"); if (stack.top().second) statement_inner(",\n"); statement_no_return("\"", value, "\""); stack.top().second = true; } void Stream::emit_json_array_value(uint32_t value) { if (stack.empty() || stack.top().first != Type::Array) SPIRV_CROSS_THROW("Invalid JSON state"); if (stack.top().second) statement_inner(",\n"); statement_no_return(std::to_string(value)); stack.top().second = true; } void Stream::emit_json_array_value(bool value) { if (stack.empty() || stack.top().first != Type::Array) SPIRV_CROSS_THROW("Invalid JSON state"); if (stack.top().second) statement_inner(",\n"); statement_no_return(value ? "true" : "false"); stack.top().second = true; } void Stream::begin_json_object() { if (!stack.empty() && stack.top().second) { statement_inner(",\n"); } statement("{"); ++indent; stack.emplace(Type::Object, false); } void Stream::end_json_object() { if (stack.empty() || stack.top().first != Type::Object) SPIRV_CROSS_THROW("Invalid JSON state"); if (stack.top().second) { statement_inner("\n"); } --indent; statement_no_return("}"); stack.pop(); if (!stack.empty()) { stack.top().second = true; } } void Stream::emit_json_key(const std::string &key) { if (stack.empty() || stack.top().first != Type::Object) SPIRV_CROSS_THROW("Invalid JSON state"); if (stack.top().second) statement_inner(",\n"); statement_no_return("\"", key, "\" : "); stack.top().second = true; } void Stream::emit_json_key_value(const std::string &key, const std::string &value) { emit_json_key(key); statement_inner("\"", value, "\""); } void Stream::emit_json_key_value(const std::string &key, uint32_t value) { emit_json_key(key); statement_inner(value); } void Stream::emit_json_key_value(const std::string &key, int32_t value) { emit_json_key(key); statement_inner(value); } void Stream::emit_json_key_value(const std::string &key, float value) { emit_json_key(key); statement_inner(convert_to_string(value, current_locale_radix_character)); } void Stream::emit_json_key_value(const std::string &key, bool value) { emit_json_key(key); statement_inner(value ? "true" : "false"); } void Stream::emit_json_key_object(const std::string &key) { emit_json_key(key); statement_inner("{\n"); ++indent; stack.emplace(Type::Object, false); } void Stream::emit_json_key_array(const std::string &key) { emit_json_key(key); statement_inner("[\n"); ++indent; stack.emplace(Type::Array, false); } void CompilerReflection::set_format(const std::string &format) { if (format != "json") { SPIRV_CROSS_THROW("Unsupported format"); } } string CompilerReflection::compile() { json_stream = std::make_shared(); json_stream->set_current_locale_radix_character(current_locale_radix_character); json_stream->begin_json_object(); reorder_type_alias(); emit_entry_points(); emit_types(); emit_resources(); emit_specialization_constants(); json_stream->end_json_object(); return json_stream->str(); } static bool naturally_emit_type(const SPIRType &type) { return type.basetype == SPIRType::Struct && !type.pointer && type.array.empty(); } bool CompilerReflection::type_is_reference(const SPIRType &type) const { // Physical pointers and arrays of physical pointers need to refer to the pointee's type. return type_is_top_level_physical_pointer(type) || (!type.array.empty() && type_is_top_level_physical_pointer(get(type.parent_type))); } void CompilerReflection::emit_types() { bool emitted_open_tag = false; SmallVector physical_pointee_types; // If we have physical pointers or arrays of physical pointers, it's also helpful to emit the pointee type // and chain the type hierarchy. For POD, arrays can emit the entire type in-place. ir.for_each_typed_id([&](uint32_t self, SPIRType &type) { if (naturally_emit_type(type)) { emit_type(self, emitted_open_tag); } else if (type_is_reference(type)) { if (!naturally_emit_type(this->get(type.parent_type)) && find(physical_pointee_types.begin(), physical_pointee_types.end(), type.parent_type) == physical_pointee_types.end()) { physical_pointee_types.push_back(type.parent_type); } } }); for (uint32_t pointee_type : physical_pointee_types) emit_type(pointee_type, emitted_open_tag); if (emitted_open_tag) { json_stream->end_json_object(); } } void CompilerReflection::emit_type(uint32_t type_id, bool &emitted_open_tag) { auto &type = get(type_id); auto name = type_to_glsl(type); if (!emitted_open_tag) { json_stream->emit_json_key_object("types"); emitted_open_tag = true; } json_stream->emit_json_key_object("_" + std::to_string(type_id)); json_stream->emit_json_key_value("name", name); if (type_is_top_level_physical_pointer(type)) { json_stream->emit_json_key_value("type", "_" + std::to_string(type.parent_type)); json_stream->emit_json_key_value("physical_pointer", true); } else if (!type.array.empty()) { emit_type_array(type); json_stream->emit_json_key_value("type", "_" + std::to_string(type.parent_type)); json_stream->emit_json_key_value("array_stride", get_decoration(type_id, DecorationArrayStride)); } else { json_stream->emit_json_key_array("members"); // FIXME ideally we'd like to emit the size of a structure as a // convenience to people parsing the reflected JSON. The problem // is that there's no implicit size for a type. It's final size // will be determined by the top level declaration in which it's // included. So there might be one size for the struct if it's // included in a std140 uniform block and another if it's included // in a std430 uniform block. // The solution is to include *all* potential sizes as a map of // layout type name to integer, but that will probably require // some additional logic being written in this class, or in the // parent CompilerGLSL class. auto size = type.member_types.size(); for (uint32_t i = 0; i < size; ++i) { emit_type_member(type, i); } json_stream->end_json_array(); } json_stream->end_json_object(); } void CompilerReflection::emit_type_member(const SPIRType &type, uint32_t index) { auto &membertype = get(type.member_types[index]); json_stream->begin_json_object(); auto name = to_member_name(type, index); // FIXME we'd like to emit the offset of each member, but such offsets are // context dependent. See the comment above regarding structure sizes json_stream->emit_json_key_value("name", name); if (type_is_reference(membertype)) { json_stream->emit_json_key_value("type", "_" + std::to_string(membertype.parent_type)); } else if (membertype.basetype == SPIRType::Struct) { json_stream->emit_json_key_value("type", "_" + std::to_string(membertype.self)); } else { json_stream->emit_json_key_value("type", type_to_glsl(membertype)); } emit_type_member_qualifiers(type, index); json_stream->end_json_object(); } void CompilerReflection::emit_type_array(const SPIRType &type) { if (!type_is_top_level_physical_pointer(type) && !type.array.empty()) { json_stream->emit_json_key_array("array"); // Note that we emit the zeros here as a means of identifying // unbounded arrays. This is necessary as otherwise there would // be no way of differentiating between float[4] and float[4][] for (const auto &value : type.array) json_stream->emit_json_array_value(value); json_stream->end_json_array(); json_stream->emit_json_key_array("array_size_is_literal"); for (const auto &value : type.array_size_literal) json_stream->emit_json_array_value(value); json_stream->end_json_array(); } } void CompilerReflection::emit_type_member_qualifiers(const SPIRType &type, uint32_t index) { auto &membertype = get(type.member_types[index]); emit_type_array(membertype); auto &memb = ir.meta[type.self].members; if (index < memb.size()) { auto &dec = memb[index]; if (dec.decoration_flags.get(DecorationLocation)) json_stream->emit_json_key_value("location", dec.location); if (dec.decoration_flags.get(DecorationOffset)) json_stream->emit_json_key_value("offset", dec.offset); // Array stride is a property of the array type, not the struct. if (has_decoration(type.member_types[index], DecorationArrayStride)) json_stream->emit_json_key_value("array_stride", get_decoration(type.member_types[index], DecorationArrayStride)); if (dec.decoration_flags.get(DecorationMatrixStride)) json_stream->emit_json_key_value("matrix_stride", dec.matrix_stride); if (dec.decoration_flags.get(DecorationRowMajor)) json_stream->emit_json_key_value("row_major", true); if (type_is_top_level_physical_pointer(membertype)) json_stream->emit_json_key_value("physical_pointer", true); } } string CompilerReflection::execution_model_to_str(spv::ExecutionModel model) { switch (model) { case ExecutionModelVertex: return "vert"; case ExecutionModelTessellationControl: return "tesc"; case ExecutionModelTessellationEvaluation: return "tese"; case ExecutionModelGeometry: return "geom"; case ExecutionModelFragment: return "frag"; case ExecutionModelGLCompute: return "comp"; case ExecutionModelRayGenerationNV: return "rgen"; case ExecutionModelIntersectionNV: return "rint"; case ExecutionModelAnyHitNV: return "rahit"; case ExecutionModelClosestHitNV: return "rchit"; case ExecutionModelMissNV: return "rmiss"; case ExecutionModelCallableNV: return "rcall"; default: return "???"; } } // FIXME include things like the local_size dimensions, geometry output vertex count, etc void CompilerReflection::emit_entry_points() { auto entries = get_entry_points_and_stages(); if (!entries.empty()) { // Needed to make output deterministic. sort(begin(entries), end(entries), [](const EntryPoint &a, const EntryPoint &b) -> bool { if (a.execution_model < b.execution_model) return true; else if (a.execution_model > b.execution_model) return false; else return a.name < b.name; }); json_stream->emit_json_key_array("entryPoints"); for (auto &e : entries) { json_stream->begin_json_object(); json_stream->emit_json_key_value("name", e.name); json_stream->emit_json_key_value("mode", execution_model_to_str(e.execution_model)); if (e.execution_model == ExecutionModelGLCompute) { const auto &spv_entry = get_entry_point(e.name, e.execution_model); SpecializationConstant spec_x, spec_y, spec_z; get_work_group_size_specialization_constants(spec_x, spec_y, spec_z); json_stream->emit_json_key_array("workgroup_size"); json_stream->emit_json_array_value(spec_x.id != ID(0) ? spec_x.constant_id : spv_entry.workgroup_size.x); json_stream->emit_json_array_value(spec_y.id != ID(0) ? spec_y.constant_id : spv_entry.workgroup_size.y); json_stream->emit_json_array_value(spec_z.id != ID(0) ? spec_z.constant_id : spv_entry.workgroup_size.z); json_stream->end_json_array(); json_stream->emit_json_key_array("workgroup_size_is_spec_constant_id"); json_stream->emit_json_array_value(spec_x.id != ID(0)); json_stream->emit_json_array_value(spec_y.id != ID(0)); json_stream->emit_json_array_value(spec_z.id != ID(0)); json_stream->end_json_array(); } json_stream->end_json_object(); } json_stream->end_json_array(); } } void CompilerReflection::emit_resources() { auto res = get_shader_resources(); emit_resources("subpass_inputs", res.subpass_inputs); emit_resources("inputs", res.stage_inputs); emit_resources("outputs", res.stage_outputs); emit_resources("textures", res.sampled_images); emit_resources("separate_images", res.separate_images); emit_resources("separate_samplers", res.separate_samplers); emit_resources("images", res.storage_images); emit_resources("ssbos", res.storage_buffers); emit_resources("ubos", res.uniform_buffers); emit_resources("push_constants", res.push_constant_buffers); emit_resources("counters", res.atomic_counters); emit_resources("acceleration_structures", res.acceleration_structures); } void CompilerReflection::emit_resources(const char *tag, const SmallVector &resources) { if (resources.empty()) { return; } json_stream->emit_json_key_array(tag); for (auto &res : resources) { auto &type = get_type(res.type_id); auto typeflags = ir.meta[type.self].decoration.decoration_flags; auto &mask = get_decoration_bitset(res.id); // If we don't have a name, use the fallback for the type instead of the variable // for SSBOs and UBOs since those are the only meaningful names to use externally. // Push constant blocks are still accessed by name and not block name, even though they are technically Blocks. bool is_push_constant = get_storage_class(res.id) == StorageClassPushConstant; bool is_block = get_decoration_bitset(type.self).get(DecorationBlock) || get_decoration_bitset(type.self).get(DecorationBufferBlock); ID fallback_id = !is_push_constant && is_block ? ID(res.base_type_id) : ID(res.id); json_stream->begin_json_object(); if (type.basetype == SPIRType::Struct) { json_stream->emit_json_key_value("type", "_" + std::to_string(res.base_type_id)); } else { json_stream->emit_json_key_value("type", type_to_glsl(type)); } json_stream->emit_json_key_value("name", !res.name.empty() ? res.name : get_fallback_name(fallback_id)); { bool ssbo_block = type.storage == StorageClassStorageBuffer || (type.storage == StorageClassUniform && typeflags.get(DecorationBufferBlock)); Bitset qualifier_mask = ssbo_block ? get_buffer_block_flags(res.id) : mask; if (qualifier_mask.get(DecorationNonReadable)) json_stream->emit_json_key_value("writeonly", true); if (qualifier_mask.get(DecorationNonWritable)) json_stream->emit_json_key_value("readonly", true); if (qualifier_mask.get(DecorationRestrict)) json_stream->emit_json_key_value("restrict", true); if (qualifier_mask.get(DecorationCoherent)) json_stream->emit_json_key_value("coherent", true); if (qualifier_mask.get(DecorationVolatile)) json_stream->emit_json_key_value("volatile", true); } emit_type_array(type); { bool is_sized_block = is_block && (get_storage_class(res.id) == StorageClassUniform || get_storage_class(res.id) == StorageClassUniformConstant || get_storage_class(res.id) == StorageClassStorageBuffer); if (is_sized_block) { uint32_t block_size = uint32_t(get_declared_struct_size(get_type(res.base_type_id))); json_stream->emit_json_key_value("block_size", block_size); } } if (type.storage == StorageClassPushConstant) json_stream->emit_json_key_value("push_constant", true); if (mask.get(DecorationLocation)) json_stream->emit_json_key_value("location", get_decoration(res.id, DecorationLocation)); if (mask.get(DecorationRowMajor)) json_stream->emit_json_key_value("row_major", true); if (mask.get(DecorationColMajor)) json_stream->emit_json_key_value("column_major", true); if (mask.get(DecorationIndex)) json_stream->emit_json_key_value("index", get_decoration(res.id, DecorationIndex)); if (type.storage != StorageClassPushConstant && mask.get(DecorationDescriptorSet)) json_stream->emit_json_key_value("set", get_decoration(res.id, DecorationDescriptorSet)); if (mask.get(DecorationBinding)) json_stream->emit_json_key_value("binding", get_decoration(res.id, DecorationBinding)); if (mask.get(DecorationInputAttachmentIndex)) json_stream->emit_json_key_value("input_attachment_index", get_decoration(res.id, DecorationInputAttachmentIndex)); if (mask.get(DecorationOffset)) json_stream->emit_json_key_value("offset", get_decoration(res.id, DecorationOffset)); // For images, the type itself adds a layout qualifer. // Only emit the format for storage images. if (type.basetype == SPIRType::Image && type.image.sampled == 2) { const char *fmt = format_to_glsl(type.image.format); if (fmt != nullptr) json_stream->emit_json_key_value("format", std::string(fmt)); } json_stream->end_json_object(); } json_stream->end_json_array(); } void CompilerReflection::emit_specialization_constants() { auto specialization_constants = get_specialization_constants(); if (specialization_constants.empty()) return; json_stream->emit_json_key_array("specialization_constants"); for (const auto &spec_const : specialization_constants) { auto &c = get(spec_const.id); auto type = get(c.constant_type); json_stream->begin_json_object(); json_stream->emit_json_key_value("name", get_name(spec_const.id)); json_stream->emit_json_key_value("id", spec_const.constant_id); json_stream->emit_json_key_value("type", type_to_glsl(type)); json_stream->emit_json_key_value("variable_id", spec_const.id); switch (type.basetype) { case SPIRType::UInt: json_stream->emit_json_key_value("default_value", c.scalar()); break; case SPIRType::Int: json_stream->emit_json_key_value("default_value", c.scalar_i32()); break; case SPIRType::Float: json_stream->emit_json_key_value("default_value", c.scalar_f32()); break; case SPIRType::Boolean: json_stream->emit_json_key_value("default_value", c.scalar() != 0); break; default: break; } json_stream->end_json_object(); } json_stream->end_json_array(); } string CompilerReflection::to_member_name(const SPIRType &type, uint32_t index) const { auto *type_meta = ir.find_meta(type.self); if (type_meta) { auto &memb = type_meta->members; if (index < memb.size() && !memb[index].alias.empty()) return memb[index].alias; else return join("_m", index); } else return join("_m", index); }