mirror of
https://github.com/KhronosGroup/SPIRV-Cross.git
synced 2024-11-16 00:40:08 +00:00
574 lines
16 KiB
C++
574 lines
16 KiB
C++
/*
|
|
* 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 <iomanip>
|
|
|
|
using namespace spv;
|
|
using namespace spirv_cross;
|
|
using namespace std;
|
|
|
|
namespace simple_json
|
|
{
|
|
enum class Type
|
|
{
|
|
Object,
|
|
Array,
|
|
};
|
|
|
|
using State = std::pair<Type, bool>;
|
|
using Stack = std::stack<State>;
|
|
|
|
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 <typename T>
|
|
inline void statement_inner(T &&t)
|
|
{
|
|
buffer << std::forward<T>(t);
|
|
}
|
|
|
|
template <typename T, typename... Ts>
|
|
inline void statement_inner(T &&t, Ts &&... ts)
|
|
{
|
|
buffer << std::forward<T>(t);
|
|
statement_inner(std::forward<Ts>(ts)...);
|
|
}
|
|
|
|
template <typename... Ts>
|
|
inline void statement(Ts &&... ts)
|
|
{
|
|
statement_indent();
|
|
statement_inner(std::forward<Ts>(ts)...);
|
|
buffer << '\n';
|
|
}
|
|
|
|
template <typename... Ts>
|
|
void statement_no_return(Ts &&... ts)
|
|
{
|
|
statement_indent();
|
|
statement_inner(std::forward<Ts>(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<simple_json::Stream>();
|
|
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<SPIRType>([&](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<SPIRType>(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<SPIRType>(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())
|
|
{
|
|
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<Resource> &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);
|
|
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<SPIRConstant>(spec_const.id);
|
|
auto type = get<SPIRType>(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);
|
|
}
|