diff options
Diffstat (limited to 'source')
| -rw-r--r-- | source/compiler-core/slang-pretty-writer.cpp | 85 | ||||
| -rw-r--r-- | source/compiler-core/slang-pretty-writer.h | 122 | ||||
| -rw-r--r-- | source/slang/slang-options.cpp | 13 | ||||
| -rw-r--r-- | source/slang/slang-reflection-json.cpp | 1214 | ||||
| -rw-r--r-- | source/slang/slang-reflection-json.h | 17 | ||||
| -rw-r--r-- | source/slang/slang.cpp | 17 |
6 files changed, 1468 insertions, 0 deletions
diff --git a/source/compiler-core/slang-pretty-writer.cpp b/source/compiler-core/slang-pretty-writer.cpp new file mode 100644 index 000000000..682b02c1e --- /dev/null +++ b/source/compiler-core/slang-pretty-writer.cpp @@ -0,0 +1,85 @@ +#include "slang-pretty-writer.h" + +#include "../core/slang-string-escape-util.h" + +namespace Slang +{ + +void PrettyWriter::writeRaw(char const* begin, char const* end) +{ + SLANG_ASSERT(end >= begin); + writeRaw(UnownedStringSlice(begin, end)); +} + +void PrettyWriter::adjust() +{ + // Only indent if at start of a line + if (m_startOfLine) + { + // Output current indentation + m_builder.appendRepeatedChar(' ', m_indent * 4); + m_startOfLine = false; + } +} + +void PrettyWriter::dedent() +{ + SLANG_ASSERT(m_indent > 0); + m_indent--; +} + +void PrettyWriter::write(const UnownedStringSlice& slice) +{ + const auto end = slice.end(); + auto start = slice.begin(); + + while (start < end) + { + const char* cur = start; + + // Search for \n if there is one + while (cur < end && *cur != '\n') + cur++; + + // If there were some chars, adjust and write + if (cur > start) + { + adjust(); + writeRaw(UnownedStringSlice(start, cur)); + } + + if (cur < end && *cur == '\n') + { + writeRawChar('\n'); + // Skip the CR + cur++; + // Mark we are at the start of a line + m_startOfLine = true; + } + + start = cur; + } +} + +void PrettyWriter::writeEscapedString(const UnownedStringSlice& slice) +{ + adjust(); + auto handler = StringEscapeUtil::getHandler(StringEscapeUtil::Style::Cpp); + StringEscapeUtil::appendQuoted(handler, slice, m_builder); +} + +void PrettyWriter::maybeComma() +{ + if (auto state = m_commaState) + { + if (!state->needComma) + { + state->needComma = true; + return; + } + } + + write(toSlice(",\n")); +} + +} // namespace Slang diff --git a/source/compiler-core/slang-pretty-writer.h b/source/compiler-core/slang-pretty-writer.h new file mode 100644 index 000000000..6817047a2 --- /dev/null +++ b/source/compiler-core/slang-pretty-writer.h @@ -0,0 +1,122 @@ +#ifndef SLANG_CORE_PRETTY_WRITER_H +#define SLANG_CORE_PRETTY_WRITER_H + + +#include "../core/slang-char-util.h" +#include "../core/slang-string-util.h" +#include "../core/slang-string.h" + +namespace Slang +{ + +struct PrettyWriter +{ + typedef PrettyWriter ThisType; + + friend struct CommaTrackerRAII; + + struct CommaState + { + bool needComma = false; + }; + + void writeRaw(const UnownedStringSlice& slice) { m_builder.append(slice); } + void writeRaw(char const* begin, char const* end); + void writeRaw(char const* begin) { writeRaw(UnownedStringSlice(begin)); } + + void writeRawChar(int c) { m_builder.appendChar(char(c)); } + + void writeHexChar(int c) { writeRawChar(CharUtil::getHexChar(Index(c))); } + + /// Adjusts indentation if at start of a line + void adjust(); + + /// Increase indentation + void indent() { m_indent++; } + /// Decreate indentation + void dedent(); + + /// Write taking into account any CR that might be in a slice + void write(const UnownedStringSlice& slice); + void write(char const* text) { write(UnownedStringSlice(text)); } + void write(char const* text, size_t length) { write(UnownedStringSlice(text, length)); } + + /// Write the slice as an escaped string + void writeEscapedString(const UnownedStringSlice& slice); + + /// Call before items in a comma-separated JSON list to emit the comma if/when needed + void maybeComma(); + + /// Get the builder the result is being constructed in + StringBuilder& getBuilder() { return m_builder; } + + ThisType& operator<<(const UnownedStringSlice& slice) + { + write(slice); + return *this; + } + ThisType& operator<<(const char* text) + { + write(text); + return *this; + } + ThisType& operator<<(uint64_t val) + { + adjust(); + m_builder << val; + return *this; + } + ThisType& operator<<(int64_t val) + { + adjust(); + m_builder << val; + return *this; + } + ThisType& operator<<(int32_t val) + { + adjust(); + m_builder << val; + return *this; + } + ThisType& operator<<(uint32_t val) + { + adjust(); + m_builder << val; + return *this; + } + ThisType& operator<<(float val) + { + adjust(); + // We want to use a specific format, so we use the StringUtil to specify format, and not + // just use << + StringUtil::appendFormat(m_builder, "%f", val); + return *this; + } + + bool m_startOfLine = true; + int m_indent = 0; + CommaState* m_commaState = nullptr; + StringBuilder m_builder; +}; + +/// Type for tracking whether a comma is needed in a comma-separated JSON list +struct CommaTrackerRAII +{ + CommaTrackerRAII(PrettyWriter& writer) + : m_writer(&writer), m_previousState(writer.m_commaState) + { + writer.m_commaState = &m_state; + } + + ~CommaTrackerRAII() { m_writer->m_commaState = m_previousState; } + +private: + PrettyWriter::CommaState m_state; + PrettyWriter* m_writer; + PrettyWriter::CommaState* m_previousState; +}; + +} // namespace Slang + + +#endif diff --git a/source/slang/slang-options.cpp b/source/slang/slang-options.cpp index 9353acd35..681999168 100644 --- a/source/slang/slang-options.cpp +++ b/source/slang/slang-options.cpp @@ -524,8 +524,13 @@ void initCommandOptions(CommandOptions& options) nullptr, "Preserve all resource parameters in the output code, even if they are not used by the " "shader."}, + {OptionKind::EmitReflectionJSON, + "-reflection-json", + "reflection-json <path>", + "Emit reflection data in JSON format to a file."}, }; + _addOptions(makeConstArrayView(generalOpts), options); /* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Target !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */ @@ -2663,6 +2668,14 @@ SlangResult OptionsParser::_parse(int argc, char const* const* argv) addOutputPath(outputPath.value.getBuffer()); break; } + case OptionKind::EmitReflectionJSON: + { + CommandLineArg outputPath; + SLANG_RETURN_ON_FAIL(m_reader.expectArg(outputPath)); + + linkage->m_optionSet.set(CompilerOptionName::EmitReflectionJSON, outputPath.value); + break; + } case OptionKind::DepFile: { CommandLineArg dependencyPath; diff --git a/source/slang/slang-reflection-json.cpp b/source/slang/slang-reflection-json.cpp new file mode 100644 index 000000000..c7d11a6c3 --- /dev/null +++ b/source/slang/slang-reflection-json.cpp @@ -0,0 +1,1214 @@ + +#include "slang-reflection-json.h" + +#include "../core/slang-blob.h" + +template<typename T> +struct Range +{ +public: + Range(T begin, T end) + : m_begin(begin), m_end(end) + { + } + + struct Iterator + { + public: + explicit Iterator(T value) + : m_value(value) + { + } + + T operator*() const { return m_value; } + void operator++() { m_value++; } + + bool operator!=(Iterator const& other) { return m_value != other.m_value; } + + private: + T m_value; + }; + + Iterator begin() const { return Iterator(m_begin); } + Iterator end() const { return Iterator(m_end); } + +private: + T m_begin; + T m_end; +}; + +template<typename T> +Range<T> makeRange(T begin, T end) +{ + return Range<T>(begin, end); +} + +template<typename T> +Range<T> makeRange(T end) +{ + return Range<T>(T(0), end); +} + +namespace Slang +{ + +static void emitReflectionVarInfoJSON(PrettyWriter& writer, slang::VariableReflection* var); +static void emitReflectionTypeLayoutJSON(PrettyWriter& writer, slang::TypeLayoutReflection* type); +static void emitReflectionTypeJSON(PrettyWriter& writer, slang::TypeReflection* type); + +static void emitReflectionVarBindingInfoJSON( + PrettyWriter& writer, + SlangParameterCategory category, + SlangUInt index, + SlangUInt count, + SlangUInt space = 0) +{ + if (category == SLANG_PARAMETER_CATEGORY_UNIFORM) + { + writer << "\"kind\": \"uniform\""; + writer << ", "; + writer << "\"offset\": " << index; + writer << ", "; + writer << "\"size\": " << count; + } + else + { + writer << "\"kind\": \""; + switch (category) + { +#define CASE(NAME, KIND) \ + case SLANG_PARAMETER_CATEGORY_##NAME: \ + writer.write(toSlice(#KIND)); \ + break + CASE(CONSTANT_BUFFER, constantBuffer); + CASE(SHADER_RESOURCE, shaderResource); + CASE(UNORDERED_ACCESS, unorderedAccess); + CASE(VARYING_INPUT, varyingInput); + CASE(VARYING_OUTPUT, varyingOutput); + CASE(SAMPLER_STATE, samplerState); + CASE(UNIFORM, uniform); + CASE(PUSH_CONSTANT_BUFFER, pushConstantBuffer); + CASE(DESCRIPTOR_TABLE_SLOT, descriptorTableSlot); + CASE(SPECIALIZATION_CONSTANT, specializationConstant); + CASE(MIXED, mixed); + CASE(REGISTER_SPACE, registerSpace); + CASE(SUB_ELEMENT_REGISTER_SPACE, subElementRegisterSpace); + CASE(GENERIC, generic); + CASE(METAL_ARGUMENT_BUFFER_ELEMENT, metalArgumentBufferElement); +#undef CASE + + default: + writer << "unknown"; + assert(!"unhandled case"); + break; + } + writer << "\""; + if (space && category != SLANG_PARAMETER_CATEGORY_REGISTER_SPACE) + { + writer << ", "; + writer << "\"space\": " << space; + } + writer << ", "; + writer << "\"index\": "; + writer << index; + if (count != 1) + { + writer << ", "; + writer << "\"count\": "; + if (count == SLANG_UNBOUNDED_SIZE) + { + writer << "\"unbounded\""; + } + else + { + writer << count; + } + } + } +} + +static void emitReflectionVarBindingInfoJSON( + PrettyWriter& writer, + slang::VariableLayoutReflection* var, + SlangCompileRequest* request = nullptr, + int entryPointIndex = -1) +{ + auto stage = var->getStage(); + if (stage != SLANG_STAGE_NONE) + { + writer.maybeComma(); + char const* stageName = "UNKNOWN"; + switch (stage) + { + case SLANG_STAGE_VERTEX: + stageName = "vertex"; + break; + case SLANG_STAGE_HULL: + stageName = "hull"; + break; + case SLANG_STAGE_DOMAIN: + stageName = "domain"; + break; + case SLANG_STAGE_GEOMETRY: + stageName = "geometry"; + break; + case SLANG_STAGE_FRAGMENT: + stageName = "fragment"; + break; + case SLANG_STAGE_COMPUTE: + stageName = "compute"; + break; + + default: + break; + } + + writer << "\"stage\": \"" << stageName << "\""; + } + + auto typeLayout = var->getTypeLayout(); + auto categoryCount = var->getCategoryCount(); + + if (categoryCount) + { + writer.maybeComma(); + if (categoryCount != 1) + { + writer << "\"bindings\": [\n"; + } + else + { + writer << "\"binding\": "; + } + writer.indent(); + + for (uint32_t cc = 0; cc < categoryCount; ++cc) + { + auto category = SlangParameterCategory(var->getCategoryByIndex(cc)); + auto index = var->getOffset(category); + auto space = var->getBindingSpace(category); + auto count = typeLayout->getSize(category); + + // Query the paramater usage for the specified entry point. + // Note: both `request` and `entryPointIndex` may be invalid here, but that should just + // make the function return a failure. + bool used = false; + bool usedAvailable = spIsParameterLocationUsed( + request, + entryPointIndex, + 0, + category, + space, + index, + used) == SLANG_OK; + + if (cc != 0) + writer << ",\n"; + + writer << "{"; + + emitReflectionVarBindingInfoJSON(writer, category, index, count, space); + + if (usedAvailable) + { + writer << ", \"used\": "; + writer << used; + } + + writer << "}"; + } + + writer.dedent(); + if (categoryCount != 1) + { + writer << "\n]"; + } + } + + if (auto semanticName = var->getSemanticName()) + { + writer.maybeComma(); + writer << "\"semanticName\": "; + writer.writeEscapedString(UnownedStringSlice(semanticName)); + + if (auto semanticIndex = var->getSemanticIndex()) + { + writer.maybeComma(); + writer << "\"semanticIndex\": " << int(semanticIndex); + } + } +} + +static void emitReflectionNameInfoJSON(PrettyWriter& writer, char const* name) +{ + // TODO: deal with escaping special characters if/when needed + writer << "\"name\": "; + writer.writeEscapedString(UnownedStringSlice(name)); +} + +static void emitUserAttributes(PrettyWriter& writer, slang::VariableReflection* var); + +static void emitReflectionModifierInfoJSON(PrettyWriter& writer, slang::VariableReflection* var) +{ + if (var->findModifier(slang::Modifier::Shared)) + { + writer.maybeComma(); + writer << "\"shared\": true"; + } + + emitUserAttributes(writer, var); +} + +static void emitUserAttributeJSON(PrettyWriter& writer, slang::UserAttribute* userAttribute) +{ + writer << "{\n"; + writer.indent(); + writer << "\"name\": \""; + writer.write(userAttribute->getName()); + writer << "\",\n"; + writer << "\"arguments\": [\n"; + writer.indent(); + for (unsigned int i = 0; i < userAttribute->getArgumentCount(); i++) + { + int intVal; + float floatVal; + size_t bufSize = 0; + if (i > 0) + writer << ",\n"; + if (SLANG_SUCCEEDED(userAttribute->getArgumentValueInt(i, &intVal))) + { + writer << intVal; + } + else if (SLANG_SUCCEEDED(userAttribute->getArgumentValueFloat(i, &floatVal))) + { + writer << floatVal; + } + else if (auto str = userAttribute->getArgumentValueString(i, &bufSize)) + { + writer.write(str, bufSize); + } + else + writer << "\"invalid value\""; + } + writer.dedent(); + writer << "\n]\n"; + writer.dedent(); + writer << "}\n"; +} + +static void emitUserAttributes(PrettyWriter& writer, slang::TypeReflection* type) +{ + auto attribCount = type->getUserAttributeCount(); + if (attribCount) + { + writer << ",\n\"userAttribs\": ["; + for (unsigned int i = 0; i < attribCount; i++) + { + if (i > 0) + writer << ",\n"; + auto attrib = type->getUserAttributeByIndex(i); + emitUserAttributeJSON(writer, attrib); + } + writer << "]"; + } +} +static void emitUserAttributes(PrettyWriter& writer, slang::VariableReflection* var) +{ + auto attribCount = var->getUserAttributeCount(); + if (attribCount) + { + writer << ",\n\"userAttribs\": ["; + for (unsigned int i = 0; i < attribCount; i++) + { + if (i > 0) + writer << ",\n"; + auto attrib = var->getUserAttributeByIndex(i); + emitUserAttributeJSON(writer, attrib); + } + writer << "]"; + } +} + +static void emitReflectionVarLayoutJSON(PrettyWriter& writer, slang::VariableLayoutReflection* var) +{ + writer << "{\n"; + writer.indent(); + + CommaTrackerRAII commaTracker(writer); + + if (auto name = var->getName()) + { + writer.maybeComma(); + emitReflectionNameInfoJSON(writer, name); + } + + writer.maybeComma(); + writer << "\"type\": "; + emitReflectionTypeLayoutJSON(writer, var->getTypeLayout()); + + emitReflectionModifierInfoJSON(writer, var->getVariable()); + + emitReflectionVarBindingInfoJSON(writer, var); + + emitUserAttributes(writer, var->getVariable()); + writer.dedent(); + writer << "\n}"; +} + +static void emitReflectionScalarTypeInfoJSON(PrettyWriter& writer, SlangScalarType scalarType) +{ + writer << "\"scalarType\": \""; + switch (scalarType) + { + default: + writer << "unknown"; + assert(!"unhandled case"); + break; +#define CASE(TAG, ID) \ + case static_cast<SlangScalarType>(slang::TypeReflection::ScalarType::TAG): \ + writer.write(toSlice(#ID)); \ + break + CASE(Void, void); + CASE(Bool, bool); + + CASE(Int8, int8); + CASE(UInt8, uint8); + CASE(Int16, int16); + CASE(UInt16, uint16); + CASE(Int32, int32); + CASE(UInt32, uint32); + CASE(Int64, int64); + CASE(UInt64, uint64); + + CASE(Float16, float16); + CASE(Float32, float32); + CASE(Float64, float64); +#undef CASE + } + writer << "\""; +} + +static void emitReflectionResourceTypeBaseInfoJSON( + PrettyWriter& writer, + slang::TypeReflection* type) +{ + auto shape = type->getResourceShape(); + auto access = type->getResourceAccess(); + writer.maybeComma(); + writer << "\"kind\": \"resource\""; + writer.maybeComma(); + writer << "\"baseShape\": \""; + switch (shape & SLANG_RESOURCE_BASE_SHAPE_MASK) + { + default: + writer << "unknown"; + assert(!"unhandled case"); + break; + +#define CASE(SHAPE, NAME) \ + case SLANG_##SHAPE: \ + writer.write(toSlice(#NAME)); \ + break + CASE(TEXTURE_1D, texture1D); + CASE(TEXTURE_2D, texture2D); + CASE(TEXTURE_3D, texture3D); + CASE(TEXTURE_CUBE, textureCube); + CASE(TEXTURE_BUFFER, textureBuffer); + CASE(STRUCTURED_BUFFER, structuredBuffer); + CASE(BYTE_ADDRESS_BUFFER, byteAddressBuffer); +#undef CASE + } + writer << "\""; + if (shape & SLANG_TEXTURE_ARRAY_FLAG) + { + writer.maybeComma(); + writer << "\"array\": true"; + } + if (shape & SLANG_TEXTURE_MULTISAMPLE_FLAG) + { + writer.maybeComma(); + writer << "\"multisample\": true"; + } + if (shape & SLANG_TEXTURE_FEEDBACK_FLAG) + { + writer.maybeComma(); + writer << "\"feedback\": true"; + } + + if (access != SLANG_RESOURCE_ACCESS_READ) + { + writer.maybeComma(); + writer << "\"access\": \""; + switch (access) + { + default: + writer << "unknown"; + assert(!"unhandled case"); + break; + + case SLANG_RESOURCE_ACCESS_READ: + break; + case SLANG_RESOURCE_ACCESS_WRITE: + writer << "write"; + break; + case SLANG_RESOURCE_ACCESS_READ_WRITE: + writer << "readWrite"; + break; + case SLANG_RESOURCE_ACCESS_RASTER_ORDERED: + writer << "rasterOrdered"; + break; + case SLANG_RESOURCE_ACCESS_APPEND: + writer << "append"; + break; + case SLANG_RESOURCE_ACCESS_CONSUME: + writer << "consume"; + break; + case SLANG_RESOURCE_ACCESS_FEEDBACK: + writer << "feedback"; + break; + } + writer << "\""; + } +} + + +static void emitReflectionTypeInfoJSON(PrettyWriter& writer, slang::TypeReflection* type) +{ + auto kind = type->getKind(); + switch (kind) + { + case slang::TypeReflection::Kind::SamplerState: + writer.maybeComma(); + writer << "\"kind\": \"samplerState\""; + break; + + case slang::TypeReflection::Kind::Resource: + { + emitReflectionResourceTypeBaseInfoJSON(writer, type); + + // TODO: We should really print the result type for all resource + // types, but current test output depends on the old behavior, so + // we only add result type output for structured buffers at first. + // + auto shape = type->getResourceShape(); + switch (shape & SLANG_RESOURCE_BASE_SHAPE_MASK) + { + default: + break; + + case SLANG_STRUCTURED_BUFFER: + if (auto resultType = type->getResourceResultType()) + { + writer.maybeComma(); + writer << "\"resultType\": "; + emitReflectionTypeJSON(writer, resultType); + } + break; + } + } + break; + + case slang::TypeReflection::Kind::ConstantBuffer: + writer.maybeComma(); + writer << "\"kind\": \"constantBuffer\""; + writer.maybeComma(); + writer << "\"elementType\": "; + emitReflectionTypeJSON(writer, type->getElementType()); + break; + + case slang::TypeReflection::Kind::ParameterBlock: + writer.maybeComma(); + writer << "\"kind\": \"parameterBlock\""; + writer.maybeComma(); + writer << "\"elementType\": "; + emitReflectionTypeJSON(writer, type->getElementType()); + break; + + case slang::TypeReflection::Kind::TextureBuffer: + writer.maybeComma(); + writer << "\"kind\": \"textureBuffer\""; + writer.maybeComma(); + writer << "\"elementType\": "; + emitReflectionTypeJSON(writer, type->getElementType()); + break; + + case slang::TypeReflection::Kind::ShaderStorageBuffer: + writer.maybeComma(); + writer << "\"kind\": \"shaderStorageBuffer\""; + writer.maybeComma(); + writer << "\"elementType\": "; + emitReflectionTypeJSON(writer, type->getElementType()); + break; + + case slang::TypeReflection::Kind::Scalar: + writer.maybeComma(); + writer << "\"kind\": \"scalar\""; + writer.maybeComma(); + emitReflectionScalarTypeInfoJSON(writer, SlangScalarType(type->getScalarType())); + break; + + case slang::TypeReflection::Kind::Vector: + writer.maybeComma(); + writer << "\"kind\": \"vector\""; + writer.maybeComma(); + writer << "\"elementCount\": "; + writer << int(type->getElementCount()); + writer.maybeComma(); + writer << "\"elementType\": "; + emitReflectionTypeJSON(writer, type->getElementType()); + break; + + case slang::TypeReflection::Kind::Matrix: + writer.maybeComma(); + writer << "\"kind\": \"matrix\""; + writer.maybeComma(); + writer << "\"rowCount\": "; + writer << type->getRowCount(); + writer.maybeComma(); + writer << "\"columnCount\": "; + writer << type->getColumnCount(); + writer.maybeComma(); + writer << "\"elementType\": "; + emitReflectionTypeJSON(writer, type->getElementType()); + break; + + case slang::TypeReflection::Kind::Array: + { + auto arrayType = type; + writer.maybeComma(); + writer << "\"kind\": \"array\""; + writer.maybeComma(); + writer << "\"elementCount\": "; + writer << int(arrayType->getElementCount()); + writer.maybeComma(); + writer << "\"elementType\": "; + emitReflectionTypeJSON(writer, arrayType->getElementType()); + } + break; + case slang::TypeReflection::Kind::Pointer: + { + auto pointerType = type; + writer.maybeComma(); + writer << "\"kind\": \"pointer\""; + writer.maybeComma(); + writer << "\"targetType\": "; + emitReflectionTypeJSON(writer, pointerType->getElementType()); + } + break; + + case slang::TypeReflection::Kind::Struct: + { + writer.maybeComma(); + writer << "\"kind\": \"struct\""; + writer.maybeComma(); + writer << "\"fields\": [\n"; + writer.indent(); + + auto structType = type; + auto fieldCount = structType->getFieldCount(); + for (uint32_t ff = 0; ff < fieldCount; ++ff) + { + if (ff != 0) + writer << ",\n"; + emitReflectionVarInfoJSON(writer, structType->getFieldByIndex(ff)); + } + writer.dedent(); + writer << "\n]"; + } + break; + + case slang::TypeReflection::Kind::GenericTypeParameter: + writer.maybeComma(); + writer << "\"kind\": \"GenericTypeParameter\""; + writer.maybeComma(); + emitReflectionNameInfoJSON(writer, type->getName()); + break; + case slang::TypeReflection::Kind::Interface: + writer.maybeComma(); + writer << "\"kind\": \"Interface\""; + writer.maybeComma(); + emitReflectionNameInfoJSON(writer, type->getName()); + break; + case slang::TypeReflection::Kind::Feedback: + writer.maybeComma(); + writer << "\"kind\": \"Feedback\""; + writer.maybeComma(); + emitReflectionNameInfoJSON(writer, type->getName()); + break; + case slang::TypeReflection::Kind::DynamicResource: + writer.maybeComma(); + writer << "\"kind\": \"DynamicResource\""; + break; + default: + assert(!"unhandled case"); + break; + } + emitUserAttributes(writer, type); +} + +static void emitReflectionParameterGroupTypeLayoutInfoJSON( + PrettyWriter& writer, + slang::TypeLayoutReflection* typeLayout, + const char* kind) +{ + writer << "\"kind\": \""; + writer.write(kind); + writer << "\""; + + writer << ",\n\"elementType\": "; + emitReflectionTypeLayoutJSON(writer, typeLayout->getElementTypeLayout()); + + // Note: There is a subtle detail below when it comes to the + // container/element variable layouts that get nested inside + // a parameter group type layout. + // + // A top-level parameter group type layout like `ConstantBuffer<Foo>` + // needs to store both information about the `ConstantBuffer` part of + // things (e.g., it might consume 1 `binding`), as well as the `Foo` + // part (e.g., it might consume 4 bytes plus 1 `binding`), and there + // is offset information for each. + // + // The "element" part is easy: it is a variable layout for a variable + // of type `Foo`. The actual variable will be null, but everything else + // will be filled in as a client would expect. + // + // The "container" part is thornier: what should the type and type + // layout of the "container" variable be? The obvious answer (which + // the Slang reflection implementation uses today) is that the type + // is the type of the parameter group itself (e.g., `ConstantBuffer<Foo>`), + // and the layout is a dummy `TypeLayout` that just reflects the + // resource usage of the "container" part of things. + // + // That means that at runtime the "container var layout" will have + // a parameter group type (e.g., `TYPE_KIND_CONSTANT_BUFFER`) + // but its type layotu will be a base `TypeLayout` and not a + // `ParameterGroupLayout` (since that would introduce infinite regress). + // + // We thus have to guard here against the recursive path where + // we are emitting reflection info for the "container" part of things. + // + // TODO: We should probably + + { + CommaTrackerRAII commaTracker(writer); + + writer << ",\n\"containerVarLayout\": {\n"; + writer.indent(); + emitReflectionVarBindingInfoJSON(writer, typeLayout->getContainerVarLayout()); + writer.dedent(); + writer << "\n}"; + } + + writer << ",\n\"elementVarLayout\": "; + emitReflectionVarLayoutJSON(writer, typeLayout->getElementVarLayout()); +} + +static void emitReflectionTypeLayoutInfoJSON( + PrettyWriter& writer, + slang::TypeLayoutReflection* typeLayout) +{ + switch (typeLayout->getKind()) + { + default: + emitReflectionTypeInfoJSON(writer, typeLayout->getType()); + break; + + case slang::TypeReflection::Kind::Pointer: + { + auto valueTypeLayout = typeLayout->getElementTypeLayout(); + SLANG_ASSERT(valueTypeLayout); + + writer.maybeComma(); + writer << "\"kind\": \"pointer\""; + + writer.maybeComma(); + writer << "\"valueType\": "; + + auto typeName = valueTypeLayout->getType()->getName(); + + if (typeName && typeName[0]) + { + // TODO(JS): + // We can't emit the type layout, because the type could contain + // a pointer and we end up in a recursive loop. For now we output the typename. + writer.writeEscapedString(UnownedStringSlice(typeName)); + } + else + { + // TODO(JS): We will need to generate name that we will associate with this type + // as it doesn't seem to have one + writer.writeEscapedString(toSlice("unknown name!")); + SLANG_ASSERT(!"Doesn't have an associated name"); + } + + /* + emitReflectionTypeLayoutJSON( + writer, + valueTypeLayout); */ + } + break; + case slang::TypeReflection::Kind::Array: + { + auto arrayTypeLayout = typeLayout; + auto elementTypeLayout = arrayTypeLayout->getElementTypeLayout(); + writer.maybeComma(); + writer << "\"kind\": \"array\""; + + writer.maybeComma(); + writer << "\"elementCount\": "; + writer << int(arrayTypeLayout->getElementCount()); + + writer.maybeComma(); + writer << "\"elementType\": "; + emitReflectionTypeLayoutJSON(writer, elementTypeLayout); + + if (arrayTypeLayout->getSize(SLANG_PARAMETER_CATEGORY_UNIFORM) != 0) + { + writer.maybeComma(); + writer << "\"uniformStride\": "; + writer << int(arrayTypeLayout->getElementStride(SLANG_PARAMETER_CATEGORY_UNIFORM)); + } + } + break; + + case slang::TypeReflection::Kind::Struct: + { + auto structTypeLayout = typeLayout; + + writer.maybeComma(); + writer << "\"kind\": \"struct\""; + if (auto name = structTypeLayout->getName()) + { + writer.maybeComma(); + emitReflectionNameInfoJSON(writer, name); + } + writer.maybeComma(); + writer << "\"fields\": [\n"; + writer.indent(); + + auto fieldCount = structTypeLayout->getFieldCount(); + for (uint32_t ff = 0; ff < fieldCount; ++ff) + { + if (ff != 0) + writer << ",\n"; + emitReflectionVarLayoutJSON(writer, structTypeLayout->getFieldByIndex(ff)); + } + writer.dedent(); + writer << "\n]"; + emitUserAttributes(writer, structTypeLayout->getType()); + } + break; + + case slang::TypeReflection::Kind::ConstantBuffer: + emitReflectionParameterGroupTypeLayoutInfoJSON(writer, typeLayout, "constantBuffer"); + break; + + case slang::TypeReflection::Kind::ParameterBlock: + emitReflectionParameterGroupTypeLayoutInfoJSON(writer, typeLayout, "parameterBlock"); + break; + + case slang::TypeReflection::Kind::TextureBuffer: + emitReflectionParameterGroupTypeLayoutInfoJSON(writer, typeLayout, "textureBuffer"); + break; + + case slang::TypeReflection::Kind::ShaderStorageBuffer: + writer.maybeComma(); + writer << "\"kind\": \"shaderStorageBuffer\""; + + writer.maybeComma(); + writer << "\"elementType\": "; + emitReflectionTypeLayoutJSON(writer, typeLayout->getElementTypeLayout()); + break; + case slang::TypeReflection::Kind::GenericTypeParameter: + writer.maybeComma(); + writer << "\"kind\": \"GenericTypeParameter\""; + + writer.maybeComma(); + emitReflectionNameInfoJSON(writer, typeLayout->getName()); + break; + case slang::TypeReflection::Kind::Interface: + writer.maybeComma(); + writer << "\"kind\": \"Interface\""; + + writer.maybeComma(); + emitReflectionNameInfoJSON(writer, typeLayout->getName()); + break; + + case slang::TypeReflection::Kind::Resource: + { + // Some resource types (notably structured buffers) + // encode layout information for their result/element + // type, but others don't. We need to check for + // the relevant cases here. + // + auto type = typeLayout->getType(); + auto shape = type->getResourceShape(); + + const auto baseType = shape & SLANG_RESOURCE_BASE_SHAPE_MASK; + + if (baseType == SLANG_STRUCTURED_BUFFER) + { + emitReflectionResourceTypeBaseInfoJSON(writer, type); + + if (auto resultTypeLayout = typeLayout->getElementTypeLayout()) + { + writer.maybeComma(); + writer << "\"resultType\": "; + emitReflectionTypeLayoutJSON(writer, resultTypeLayout); + } + } + else if (shape & SLANG_TEXTURE_FEEDBACK_FLAG) + { + emitReflectionResourceTypeBaseInfoJSON(writer, type); + + if (auto resultType = typeLayout->getResourceResultType()) + { + writer.maybeComma(); + writer << "\"resultType\": "; + emitReflectionTypeJSON(writer, resultType); + } + } + else + { + emitReflectionTypeInfoJSON(writer, type); + } + } + break; + } +} + +static void emitReflectionTypeLayoutJSON( + PrettyWriter& writer, + slang::TypeLayoutReflection* typeLayout) +{ + CommaTrackerRAII commaTracker(writer); + writer << "{\n"; + writer.indent(); + emitReflectionTypeLayoutInfoJSON(writer, typeLayout); + writer.dedent(); + writer << "\n}"; +} + +static void emitReflectionTypeJSON(PrettyWriter& writer, slang::TypeReflection* type) +{ + CommaTrackerRAII commaTracker(writer); + writer << "{\n"; + writer.indent(); + emitReflectionTypeInfoJSON(writer, type); + writer.dedent(); + writer << "\n}"; +} + +static void emitReflectionVarInfoJSON(PrettyWriter& writer, slang::VariableReflection* var) +{ + emitReflectionNameInfoJSON(writer, var->getName()); + + emitReflectionModifierInfoJSON(writer, var); + + writer << ",\n"; + writer << "\"type\": "; + emitReflectionTypeJSON(writer, var->getType()); +} + +static void emitReflectionParamJSON(PrettyWriter& writer, slang::VariableLayoutReflection* param) +{ + // TODO: This function is likely redundant with `emitReflectionVarLayoutJSON` + // and we should try to collapse them into one. + + writer << "{\n"; + writer.indent(); + + CommaTrackerRAII commaTracker(writer); + + if (auto name = param->getName()) + { + writer.maybeComma(); + emitReflectionNameInfoJSON(writer, name); + } + + emitReflectionModifierInfoJSON(writer, param->getVariable()); + + emitReflectionVarBindingInfoJSON(writer, param); + + writer.maybeComma(); + writer << "\"type\": "; + emitReflectionTypeLayoutJSON(writer, param->getTypeLayout()); + + writer.dedent(); + writer << "\n}"; +} + + +static void emitEntryPointParamJSON( + PrettyWriter& writer, + slang::VariableLayoutReflection* param, + SlangCompileRequest* request, + int entryPointIndex) +{ + writer << "{\n"; + writer.indent(); + + if (auto name = param->getName()) + { + emitReflectionNameInfoJSON(writer, name); + } + + emitReflectionVarBindingInfoJSON(writer, param, request, entryPointIndex); + + writer.dedent(); + writer << "\n}"; +} + + +static void emitReflectionTypeParamJSON( + PrettyWriter& writer, + slang::TypeParameterReflection* typeParam) +{ + writer << "{\n"; + writer.indent(); + emitReflectionNameInfoJSON(writer, typeParam->getName()); + writer << ",\n"; + writer << "\"constraints\": \n"; + writer << "[\n"; + writer.indent(); + auto constraintCount = typeParam->getConstraintCount(); + for (auto ee : makeRange(constraintCount)) + { + if (ee != 0) + writer << ",\n"; + writer << "{\n"; + writer.indent(); + CommaTrackerRAII commaTracker(writer); + emitReflectionTypeInfoJSON(writer, typeParam->getConstraintByIndex(ee)); + writer.dedent(); + writer << "\n}"; + } + writer.dedent(); + writer << "\n]"; + writer.dedent(); + writer << "\n}"; +} + +static void emitReflectionEntryPointJSON( + PrettyWriter& writer, + SlangCompileRequest* request, + slang::ShaderReflection* programReflection, + int entryPointIndex) +{ + slang::EntryPointReflection* entryPoint = + programReflection->getEntryPointByIndex(entryPointIndex); + + writer << "{\n"; + writer.indent(); + + emitReflectionNameInfoJSON(writer, entryPoint->getName()); + + switch (entryPoint->getStage()) + { + case SLANG_STAGE_VERTEX: + writer << ",\n\"stage:\": \"vertex\""; + break; + case SLANG_STAGE_HULL: + writer << ",\n\"stage:\": \"hull\""; + break; + case SLANG_STAGE_DOMAIN: + writer << ",\n\"stage:\": \"domain\""; + break; + case SLANG_STAGE_GEOMETRY: + writer << ",\n\"stage:\": \"geometry\""; + break; + case SLANG_STAGE_FRAGMENT: + writer << ",\n\"stage:\": \"fragment\""; + break; + case SLANG_STAGE_COMPUTE: + writer << ",\n\"stage:\": \"compute\""; + break; + default: + break; + } + + auto entryPointParameterCount = entryPoint->getParameterCount(); + if (entryPointParameterCount) + { + writer << ",\n\"parameters\": [\n"; + writer.indent(); + + for (auto pp : makeRange(entryPointParameterCount)) + { + if (pp != 0) + writer << ",\n"; + + auto parameter = entryPoint->getParameterByIndex(pp); + emitReflectionParamJSON(writer, parameter); + } + + writer.dedent(); + writer << "\n]"; + } + if (entryPoint->usesAnySampleRateInput()) + { + writer << ",\n\"usesAnySampleRateInput\": true"; + } + if (auto resultVarLayout = entryPoint->getResultVarLayout()) + { + writer << ",\n\"result:\": "; + emitReflectionParamJSON(writer, resultVarLayout); + } + + if (entryPoint->getStage() == SLANG_STAGE_COMPUTE) + { + SlangUInt threadGroupSize[3]; + entryPoint->getComputeThreadGroupSize(3, threadGroupSize); + + writer << ",\n\"threadGroupSize\": ["; + for (int ii = 0; ii < 3; ++ii) + { + if (ii != 0) + writer << ", "; + writer << threadGroupSize[ii]; + } + writer << "]"; + } + + // If code generation has been performed, print out the parameter usage by this entry point. + if ((request->getCompileFlags() & SLANG_COMPILE_FLAG_NO_CODEGEN) == 0) + { + writer << ",\n\"bindings\": [\n"; + writer.indent(); + + auto programParameterCount = programReflection->getParameterCount(); + for (auto pp : makeRange(programParameterCount)) + { + if (pp != 0) + writer << ",\n"; + + auto parameter = programReflection->getParameterByIndex(pp); + emitEntryPointParamJSON(writer, parameter, request, entryPointIndex); + } + + writer.dedent(); + writer << "\n]"; + } + + writer.dedent(); + writer << "\n}"; +} + +static void emitReflectionJSON( + PrettyWriter& writer, + SlangCompileRequest* request, + slang::ShaderReflection* programReflection) +{ + writer << "{\n"; + writer.indent(); + writer << "\"parameters\": [\n"; + writer.indent(); + + auto parameterCount = programReflection->getParameterCount(); + for (auto pp : makeRange(parameterCount)) + { + if (pp != 0) + writer << ",\n"; + + auto parameter = programReflection->getParameterByIndex(pp); + emitReflectionParamJSON(writer, parameter); + } + + writer.dedent(); + writer << "\n]"; + + auto entryPointCount = programReflection->getEntryPointCount(); + if (entryPointCount) + { + writer << ",\n\"entryPoints\": [\n"; + writer.indent(); + + for (auto ee : makeRange(entryPointCount)) + { + if (ee != 0) + writer << ",\n"; + + emitReflectionEntryPointJSON(writer, request, programReflection, (int)ee); + } + + writer.dedent(); + writer << "\n]"; + } + + auto genParamCount = programReflection->getTypeParameterCount(); + if (genParamCount) + { + writer << ",\n\"typeParams\":\n"; + writer << "[\n"; + writer.indent(); + for (auto ee : makeRange(genParamCount)) + { + if (ee != 0) + writer << ",\n"; + + auto typeParam = programReflection->getTypeParameterByIndex(ee); + emitReflectionTypeParamJSON(writer, typeParam); + } + writer.dedent(); + writer << "\n]"; + } + + { + SlangUInt count = programReflection->getHashedStringCount(); + if (count) + { + writer << ",\n\"hashedStrings\": {\n"; + writer.indent(); + + for (SlangUInt i = 0; i < count; ++i) + { + if (i) + { + writer << ",\n"; + } + + size_t charsCount; + const char* chars = programReflection->getHashedString(i, &charsCount); + const int hash = spComputeStringHash(chars, charsCount); + + writer.writeEscapedString(UnownedStringSlice(chars, charsCount)); + writer << ": "; + + writer << hash; + } + + writer.dedent(); + writer << "\n}\n"; + } + } + + writer.dedent(); + writer << "\n}\n"; +} + +void emitReflectionJSON( + SlangCompileRequest* request, + SlangReflection* reflection, + PrettyWriter& writer) +{ + auto programReflection = (slang::ShaderReflection*)reflection; + emitReflectionJSON(writer, request, programReflection); +} + +} // namespace Slang + + +extern "C" +{ + SLANG_API SlangResult spReflection_ToJson( + SlangReflection* reflection, + SlangCompileRequest* request, + ISlangBlob** outBlob) + { + using namespace Slang; + PrettyWriter writer; + emitReflectionJSON(request, reflection, writer); + *outBlob = StringBlob::moveCreate(writer.getBuilder()).detach(); + return SLANG_OK; + } +} diff --git a/source/slang/slang-reflection-json.h b/source/slang/slang-reflection-json.h new file mode 100644 index 000000000..bf62b8744 --- /dev/null +++ b/source/slang/slang-reflection-json.h @@ -0,0 +1,17 @@ +#ifndef SLANG_REFLECTION_JSON_H +#define SLANG_REFLECTION_JSON_H + +#include "../compiler-core/slang-pretty-writer.h" +#include "slang.h" + +namespace Slang +{ + +void emitReflectionJSON( + SlangCompileRequest* request, + SlangReflection* reflection, + PrettyWriter& writer); + +} + +#endif diff --git a/source/slang/slang.cpp b/source/slang/slang.cpp index eb938f49c..25419fb33 100644 --- a/source/slang/slang.cpp +++ b/source/slang/slang.cpp @@ -31,6 +31,7 @@ #include "slang-parameter-binding.h" #include "slang-parser.h" #include "slang-preprocessor.h" +#include "slang-reflection-json.h" #include "slang-repro.h" #include "slang-serialize-ast.h" #include "slang-serialize-container.h" @@ -6857,6 +6858,22 @@ SlangResult EndToEndCompileRequest::compile() } } + auto reflectionPath = getOptionSet().getStringOption(CompilerOptionName::EmitReflectionJSON); + if (reflectionPath.getLength() != 0) + { + auto bufferWriter = PrettyWriter(); + emitReflectionJSON(this, this->getReflection(), bufferWriter); + if (reflectionPath == "-") + { + auto builder = bufferWriter.getBuilder(); + StdWriters::getOut().write(builder.getBuffer(), builder.getLength()); + } + else if (SLANG_FAILED(File::writeAllText(reflectionPath, bufferWriter.getBuilder()))) + { + getSink()->diagnose(SourceLoc(), Diagnostics::unableToWriteFile, reflectionPath); + } + } + return res; } |
