/* * Copyright 2018-2019 Bradley Austin Davis * * 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. */ #include "spirv_reflect.hpp" #include "spirv_glsl.hpp" #include using namespace spv; using namespace spirv_cross; using namespace std; namespace simple_json { enum class Type { Object, Array, }; using State = std::pair; using Stack = std::stack; class Stream { Stack stack; std::ostringstream buffer; uint32_t indent{ 0 }; public: 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); 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::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(value); } 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() { // Force a classic "C" locale, reverts when function returns ClassicLocale classic_locale; // Move constructor for this type is broken on GCC 4.9 ... json_stream = std::make_shared(); json_stream->begin_json_object(); emit_entry_points(); emit_types(); emit_resources(); emit_specialization_constants(); json_stream->end_json_object(); return json_stream->str(); } void CompilerReflection::emit_types() { bool emitted_open_tag = false; ir.for_each_typed_id([&](uint32_t, SPIRType &type) { if (type.basetype == SPIRType::Struct && !type.pointer && type.array.empty()) emit_type(type, emitted_open_tag); }); if (emitted_open_tag) { json_stream->end_json_object(); } } void CompilerReflection::emit_type(const SPIRType &type, bool &emitted_open_tag) { auto name = type_to_glsl(type); if (type.type_alias != 0) return; 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.self)); json_stream->emit_json_key_value("name", name); 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 (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.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(); } } void CompilerReflection::emit_type_member_qualifiers(const SPIRType &type, uint32_t index) { auto flags = combined_decoration_for_member(type, index); if (flags.get(DecorationRowMajor)) json_stream->emit_json_key_value("row_major", true); 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); } } string CompilerReflection::execution_model_to_str(spv::ExecutionModel model) { switch (model) { case spv::ExecutionModelVertex: return "vert"; case spv::ExecutionModelTessellationControl: return "tesc"; case ExecutionModelTessellationEvaluation: return "tese"; case ExecutionModelGeometry: return "geom"; case ExecutionModelFragment: return "frag"; case ExecutionModelGLCompute: return "comp"; 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)); 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); } void CompilerReflection::emit_resources(const char *tag, const vector &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); uint32_t fallback_id = !is_push_constant && is_block ? res.base_type_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)); if (ssbo_block) { auto buffer_flags = get_buffer_block_flags(res.id); if (buffer_flags.get(DecorationNonReadable)) json_stream->emit_json_key_value("writeonly", true); if (buffer_flags.get(DecorationNonWritable)) json_stream->emit_json_key_value("readonly", true); if (buffer_flags.get(DecorationRestrict)) json_stream->emit_json_key_value("restrict", true); if (buffer_flags.get(DecorationCoherent)) json_stream->emit_json_key_value("coherent", 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("id", spec_const.constant_id); json_stream->emit_json_key_value("type", type_to_glsl(type)); 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); }