summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authortareksander <57038324+tareksander@users.noreply.github.com>2024-11-11 20:32:00 +0100
committerGitHub <noreply@github.com>2024-11-11 11:32:00 -0800
commit7e51180ed80ce1c5718b65c6625dc12fe64c8bfa (patch)
tree6a6451b388d52ba01e8b4190dc498b435c5ea978
parent98dab05e80a90ddf64c9d31420a3b49ec49e31d1 (diff)
Reflection compiler option (#5507)
* Moved the pretty writer code from slang-reflection-test into core * Moved reflection test code into the slang codebase and added the compiler option -reflection-json to store the reflection data in a separate file. * Documented -reflection-json command line option * moved PrettyWriter from core to compiler-core * Fixed variable shadowing warning * Use File::writeAllText instead of OSFilesystem and write to stdout if - is used as the path * format code * Fixed linker error * Fix COM Ptr life time issues. * Move enum to the end. * Fix formatting. --------- Co-authored-by: slangbot <186143334+slangbot@users.noreply.github.com> Co-authored-by: Yong He <yonghe@outlook.com>
-rw-r--r--docs/command-line-slangc-reference.mdbin78338 -> 78686 bytes
-rw-r--r--include/slang-deprecated.h5
-rw-r--r--include/slang.h2
-rw-r--r--source/compiler-core/slang-pretty-writer.cpp85
-rw-r--r--source/compiler-core/slang-pretty-writer.h122
-rw-r--r--source/slang/slang-options.cpp13
-rw-r--r--source/slang/slang-reflection-json.cpp1214
-rw-r--r--source/slang/slang-reflection-json.h17
-rw-r--r--source/slang/slang.cpp17
-rw-r--r--tools/slang-reflection-test/slang-reflection-test-main.cpp1378
10 files changed, 1479 insertions, 1374 deletions
diff --git a/docs/command-line-slangc-reference.md b/docs/command-line-slangc-reference.md
index a6df65ab2..36493b2f9 100644
--- a/docs/command-line-slangc-reference.md
+++ b/docs/command-line-slangc-reference.md
Binary files differ
diff --git a/include/slang-deprecated.h b/include/slang-deprecated.h
index 1c1350f9f..3ce8f61bc 100644
--- a/include/slang-deprecated.h
+++ b/include/slang-deprecated.h
@@ -853,6 +853,11 @@ extern "C"
// Shader Reflection
+ SLANG_API SlangResult spReflection_ToJson(
+ SlangReflection* reflection,
+ SlangCompileRequest* request,
+ ISlangBlob** outBlob);
+
SLANG_API unsigned spReflection_GetParameterCount(SlangReflection* reflection);
SLANG_API SlangReflectionParameter* spReflection_GetParameterByIndex(
SlangReflection* reflection,
diff --git a/include/slang.h b/include/slang.h
index 2999996e9..0e6724373 100644
--- a/include/slang.h
+++ b/include/slang.h
@@ -1003,6 +1003,8 @@ typedef uint32_t SlangSizeT;
// Add this new option to the end of the list to avoid breaking ABI as much as possible.
// Setting of EmitSpirvDirectly or EmitSpirvViaGLSL will turn into this option internally.
EmitSpirvMethod, // enum SlangEmitSpirvMethod
+
+ EmitReflectionJSON, // bool
CountOf,
};
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;
}
diff --git a/tools/slang-reflection-test/slang-reflection-test-main.cpp b/tools/slang-reflection-test/slang-reflection-test-main.cpp
index 3bfa809fd..aa6f8012a 100644
--- a/tools/slang-reflection-test/slang-reflection-test-main.cpp
+++ b/tools/slang-reflection-test/slang-reflection-test-main.cpp
@@ -1,5 +1,6 @@
// slang-reflection-test-main.cpp
+#include "../../source/compiler-core/slang-pretty-writer.h"
#include "../../source/core/slang-char-util.h"
#include "../../source/core/slang-string-escape-util.h"
#include "../../source/core/slang-string-util.h"
@@ -14,1385 +15,14 @@
using namespace Slang;
-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);
-}
-
-
-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(PrettyWriter& writer, 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;
-};
-
-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"));
-}
-
-/// 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;
-};
-
-
-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, structTypeLayout->getName());
- }
- 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 parameterCount = entryPoint->getParameterCount();
- if (parameterCount)
- {
- writer << ",\n\"parameters\": [\n";
- writer.indent();
-
- for (auto pp : makeRange(parameterCount))
- {
- 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 parameterCount = programReflection->getParameterCount();
- for (auto pp : makeRange(parameterCount))
- {
- 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)
{
- auto programReflection = (slang::ShaderReflection*)reflection;
-
- PrettyWriter writer;
-
- emitReflectionJSON(writer, request, programReflection);
+ ComPtr<ISlangBlob> b;
- // Get the contents of the writer
- const auto slice = writer.getBuilder().getUnownedSlice();
+ spReflection_ToJson(reflection, request, b.writeRef());
// Output the writer content to out stream
- StdWriters::getOut().write(slice.begin(), slice.getLength());
+ StdWriters::getOut().write((const char*)b->getBufferPointer(), b->getBufferSize());
}
static SlangResult maybeDumpDiagnostic(SlangResult res, SlangCompileRequest* request)