diff options
| author | Theresa Foley <10618364+tangent-vector@users.noreply.github.com> | 2024-12-12 13:30:43 -0800 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-12-12 13:30:43 -0800 |
| commit | 1fb79ac4f922c3f9c3ecf8f4533c12ec7bdeb37d (patch) | |
| tree | f44c62a7e88dd7b44fa5b36a02345305a169e6f5 | |
| parent | 9c82ed36946941eb1df3186f00903593aab556c3 (diff) | |
Add an example for using the reflection API (#5839)
* Add an example for using the reflection API
The example program is meant to accompany a document that goes into more detail about the mental model behind the reflection API and the way this program drives it.
Ideally this program can land before the document goes live, and then the document can be published with a link to the example.
After that, the example could be updated to include links into the live document.
Along with adding the example program, this change also adds some convenience functions to the reflection API to avoid cases where the program would otherwise need to cast between
`slang::ParameterCategory` and `SlangParameterCategory`.
* format code
* fixup: error noticed by clang
---------
Co-authored-by: slangbot <186143334+slangbot@users.noreply.github.com>
| -rw-r--r-- | examples/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | examples/reflection-api/README.md | 6 | ||||
| -rw-r--r-- | examples/reflection-api/compute-simple.slang | 21 | ||||
| -rw-r--r-- | examples/reflection-api/main.cpp | 1481 | ||||
| -rw-r--r-- | examples/reflection-api/raster-simple.slang | 88 | ||||
| -rw-r--r-- | include/slang.h | 45 |
6 files changed, 1638 insertions, 4 deletions
diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 960db6b39..e46f41e7a 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -83,6 +83,7 @@ if(SLANG_ENABLE_EXAMPLES) example(platform-test WIN32_EXECUTABLE) example(ray-tracing WIN32_EXECUTABLE) example(ray-tracing-pipeline WIN32_EXECUTABLE) + example(reflection-api) example(shader-object) example(shader-toy WIN32_EXECUTABLE) example(triangle WIN32_EXECUTABLE) diff --git a/examples/reflection-api/README.md b/examples/reflection-api/README.md new file mode 100644 index 000000000..9dc5f1745 --- /dev/null +++ b/examples/reflection-api/README.md @@ -0,0 +1,6 @@ +Slang Reflection API Example +============================ + +This example shows basic usage of the Slang reflection API to +traverse the parameters of a program, including type and +layout information. diff --git a/examples/reflection-api/compute-simple.slang b/examples/reflection-api/compute-simple.slang new file mode 100644 index 000000000..3d8872180 --- /dev/null +++ b/examples/reflection-api/compute-simple.slang @@ -0,0 +1,21 @@ +// compute-simple.slang + +struct ImageProcessingOptions +{ + float3 tintColor; + float blurRadius; + + bool useLookupTable; + StructuredBuffer<float4> lookupTable; +} + +[shader("compute")] +[numthreads(8, 8)] +void processImage( + uint3 threadID : SV_DispatchThreadID, + uniform Texture2D inputImage, + uniform RWTexture2D outputImage, + uniform ImageProcessingOptions options) +{ + /* actual logic would go here */ +} diff --git a/examples/reflection-api/main.cpp b/examples/reflection-api/main.cpp new file mode 100644 index 000000000..9d773fffd --- /dev/null +++ b/examples/reflection-api/main.cpp @@ -0,0 +1,1481 @@ +// main.cpp + +// Reflection API Example Program +// ============================== +// +// This file provides the application code for the `reflection-api` example. +// This example uses the Slang reflection API to travserse the structure +// of the parameters of a Slang program and their types. +// +// This program is a companion to a document on the Slang reflection API. +// Once that document is published, this paragraph will be updated to +// properly link to that document. +// +// Boilerplate +// ----------- +// +// The following lines are boilerplate common to set up this example +// to use the infrastructure for example programs in the Slang +// repository. +// + +#include "slang-com-ptr.h" +#include "slang.h" +typedef SlangResult Result; + +#include "core/slang-basic.h" +#include "examples/example-base/example-base.h" +using Slang::ComPtr; +using Slang::String; +using Slang::List; + +static const ExampleResources resourceBase("reflection-api"); + +// Configuration +// ------------- +// +// For simplicity, this example uses a hard-coded list of shader programs +// to compile, each represented as the name of a `.slang` file, along with +// a hard-coded list of targets to compile and reflect the programs for. +// + +static const char* kSourceFileNames[] = { + "raster-simple.slang", + "compute-simple.slang", +}; + +static const struct +{ + SlangCompileTarget format; + const char* profile; +} kTargets[] = { + {SLANG_DXIL, "sm_6_0"}, + {SLANG_SPIRV, "sm_6_0"}, +}; +static const int kTargetCount = SLANG_COUNT_OF(kTargets); + +// The `ReflectingPrinting` Type +// ------------------------- +// +// We wrap most of the code for this example in a `struct` +// type, in order to provide a bit more freedom in order +// of declaration. +// +// When possible, we will follow the order of declarations +// in the accompanying document, to help readers who want +// to following along in the code while reading. +// +struct ReflectingPrinting +{ + // Scoping things in a type allows us to declare functions + // out of order more easily, but we still have to forward-declare + // types when they will be used before they are declared. + // + struct AccessPath; + + // Output Formatting + // ----------------- + // + // This example program outputs reflection information in a format + // that is (or at least is intended to be) compatible with YAML. + // + // We do not want the code to be overly complicated with issues + // around formatting, so the details of the actual printing logic + // are largely left until later. However, there are a pair of + // macros that help to keep things tidy that we need to introduce + // here, before they are used. + // +#define WITH_ARRAY() for (int _i = (beginArray(), 1); _i; _i = (endArray(), 0)) + +#define SCOPED_OBJECT() ScopedObject scopedObject##__COUNTER__(this) + + // Compiling a Program + // ------------------- + // + Result compileAndReflectProgram(slang::ISession* session, const char* sourceFileName) + { + SCOPED_OBJECT(); + printComment("program"); + + key("file name"); + printQuotedString(sourceFileName); + String sourceFilePath = resourceBase.resolveResource(sourceFileName); + + ComPtr<slang::IBlob> diagnostics; + Result result = SLANG_OK; + + // ### Loading a Module + // + + ComPtr<slang::IModule> module; + module = session->loadModule(sourceFilePath.getBuffer(), diagnostics.writeRef()); + diagnoseIfNeeded(diagnostics); + if (!module) + return SLANG_FAIL; + + List<ComPtr<slang::IComponentType>> componentsToLink; + + // ### Finding Entry Points + // + + key("defined entry points"); + int definedEntryPointCount = module->getDefinedEntryPointCount(); + WITH_ARRAY() + for (int i = 0; i < definedEntryPointCount; i++) + { + ComPtr<slang::IEntryPoint> entryPoint; + SLANG_RETURN_ON_FAIL(module->getDefinedEntryPoint(i, entryPoint.writeRef())); + + element(); + SCOPED_OBJECT(); + key("name"); + printQuotedString(entryPoint->getFunctionReflection()->getName()); + + componentsToLink.add(ComPtr<slang::IComponentType>(entryPoint.get())); + } + + // ### Composing and Linking + // + + ComPtr<slang::IComponentType> composed; + result = session->createCompositeComponentType( + (slang::IComponentType**)componentsToLink.getBuffer(), + componentsToLink.getCount(), + composed.writeRef(), + diagnostics.writeRef()); + diagnoseIfNeeded(diagnostics); + SLANG_RETURN_ON_FAIL(result); + + ComPtr<slang::IComponentType> program; + result = composed->link(program.writeRef(), diagnostics.writeRef()); + diagnoseIfNeeded(diagnostics); + SLANG_RETURN_ON_FAIL(result); + + key("layouts"); + WITH_ARRAY() + for (int targetIndex = 0; targetIndex < kTargetCount; ++targetIndex) + { + element(); + + // ### Getting the Program Layout + // + slang::ProgramLayout* programLayout = + program->getLayout(targetIndex, diagnostics.writeRef()); + diagnoseIfNeeded(diagnostics); + if (!programLayout) + { + result = SLANG_FAIL; + continue; + } + + SLANG_RETURN_ON_FAIL( + collectEntryPointMetadata(program, targetIndex, definedEntryPointCount)); + + _programLayout = programLayout; + auto targetFormat = kTargets[targetIndex].format; + printProgramLayout(programLayout, targetFormat); + } + + return result; + } + slang::ProgramLayout* _programLayout = nullptr; + + Result compileAndReflectPrograms(slang::ISession* session) + { + Result result = SLANG_OK; + + WITH_ARRAY() + for (auto fileName : kSourceFileNames) + { + element(); + auto programResult = compileAndReflectProgram(session, fileName); + if (SLANG_FAILED(programResult)) + { + result = programResult; + } + } + + return result; + } + + // Types and Variables + // ------------------- + // + // ### Variables + // + void printVariable(slang::VariableReflection* variable) + { + SCOPED_OBJECT(); + + const char* name = variable->getName(); + slang::TypeReflection* type = variable->getType(); + + key("name"); + printQuotedString(name); + key("type"); + printType(type); + } + + // ### Types + // + void printType(slang::TypeReflection* type) + { + SCOPED_OBJECT(); + + const char* name = type->getName(); + slang::TypeReflection::Kind kind = type->getKind(); + + key("name"); + printQuotedString(name); + key("kind"); + printTypeKind(kind); + + // There is information that we would like to + // print for both types and type layouts, so + // we will factor the common logic into a + // subroutine so that we can share the code. + // + printCommonTypeInfo(type); + + switch (type->getKind()) + { + default: + break; + + // #### Structure Types + // + case slang::TypeReflection::Kind::Struct: + { + key("fields"); + int fieldCount = type->getFieldCount(); + + WITH_ARRAY(); + for (int f = 0; f < fieldCount; f++) + { + element(); + auto field = type->getFieldByIndex(f); + + printVariable(field); + } + } + break; + + // #### Array Types + // #### Vector Types + // #### Matrix Types + // + case slang::TypeReflection::Kind::Array: + case slang::TypeReflection::Kind::Vector: + case slang::TypeReflection::Kind::Matrix: + { + key("element type"); + printType(type->getElementType()); + } + break; + + // #### Resource Types + // + case slang::TypeReflection::Kind::Resource: + { + key("result type"); + printType(type->getResourceResultType()); + } + break; + + // #### Single-Element Container Types + // + case slang::TypeReflection::Kind::ConstantBuffer: + case slang::TypeReflection::Kind::ParameterBlock: + case slang::TypeReflection::Kind::TextureBuffer: + case slang::TypeReflection::Kind::ShaderStorageBuffer: + { + key("element type"); + printType(type->getElementType()); + } + break; + } + } + + // #### Array Types + // + void printPossiblyUnbounded(size_t value) + { + if (value == ~size_t(0)) + { + printf("unbounded"); + } + else + { + printf("%u", unsigned(value)); + } + } + + void printCommonTypeInfo(slang::TypeReflection* type) + { + switch (type->getKind()) + { + // #### Scalar Types + // + case slang::TypeReflection::Kind::Scalar: + { + key("scalar type"); + printScalarType(type->getScalarType()); + } + break; + + // #### Array Types + // + case slang::TypeReflection::Kind::Array: + { + key("element count"); + printPossiblyUnbounded(type->getElementCount()); + } + break; + + // #### Vector Types + // + case slang::TypeReflection::Kind::Vector: + { + key("element count"); + print(type->getElementCount()); + } + break; + + // #### Matrix Types + // + case slang::TypeReflection::Kind::Matrix: + { + key("row count"); + print(type->getRowCount()); + + key("column count"); + print(type->getColumnCount()); + } + break; + + // #### Resource Types + // + case slang::TypeReflection::Kind::Resource: + { + key("shape"); + printResourceShape(type->getResourceShape()); + + key("access"); + printResourceAccess(type->getResourceAccess()); + } + break; + + default: + break; + } + } + + // Layout for Types and Variables + // ------------------------------ + // + // ### Variable Layouts + // + void printVariableLayout(slang::VariableLayoutReflection* variableLayout, AccessPath accessPath) + { + SCOPED_OBJECT(); + + key("name"); + printQuotedString(variableLayout->getName()); + + printOffsets(variableLayout, accessPath); + + printVaryingParameterInfo(variableLayout); + + ExtendedAccessPath variablePath(accessPath, variableLayout); + + key("type layout"); + printTypeLayout(variableLayout->getTypeLayout(), variablePath); + } + + // #### Offsets + + void printRelativeOffsets(slang::VariableLayoutReflection* variableLayout) + { + key("relative"); + int usedLayoutUnitCount = variableLayout->getCategoryCount(); + WITH_ARRAY(); + for (int i = 0; i < usedLayoutUnitCount; ++i) + { + element(); + + auto layoutUnit = variableLayout->getCategoryByIndex(i); + printOffset(variableLayout, layoutUnit); + } + } + + void printOffset( + slang::VariableLayoutReflection* variableLayout, + slang::ParameterCategory layoutUnit) + { + printOffset( + layoutUnit, + variableLayout->getOffset(layoutUnit), + variableLayout->getBindingSpace(layoutUnit)); + } + + void printOffset(slang::ParameterCategory layoutUnit, size_t offset, size_t spaceOffset) + { + SCOPED_OBJECT(); + + key("value"); + print(offset); + key("unit"); + printLayoutUnit(layoutUnit); + + // #### Spaces / Sets + + switch (layoutUnit) + { + default: + break; + + case slang::ParameterCategory::ConstantBuffer: + case slang::ParameterCategory::ShaderResource: + case slang::ParameterCategory::UnorderedAccess: + case slang::ParameterCategory::SamplerState: + case slang::ParameterCategory::DescriptorTableSlot: + key("space"); + print(spaceOffset); + break; + } + } + + // ### Type Layouts + // + void printTypeLayout(slang::TypeLayoutReflection* typeLayout, AccessPath accessPath) + { + SCOPED_OBJECT(); + + key("name"); + printQuotedString(typeLayout->getName()); + key("kind"); + printTypeKind(typeLayout->getKind()); + printCommonTypeInfo(typeLayout->getType()); + + printSizes(typeLayout); + + printKindSpecificInfo(typeLayout, accessPath); + } + + // #### Size + // + void printSizes(slang::TypeLayoutReflection* typeLayout) + { + key("size"); + + int usedLayoutUnitCount = typeLayout->getCategoryCount(); + WITH_ARRAY() + for (int i = 0; i < usedLayoutUnitCount; ++i) + { + element(); + + auto layoutUnit = typeLayout->getCategoryByIndex(i); + printSize(typeLayout, layoutUnit); + } + + // #### Alignment and Stride + if (typeLayout->getSize() != 0) + { + key("alignment in bytes"); + print(typeLayout->getAlignment()); + + key("stride in bytes"); + print(typeLayout->getStride()); + } + } + + void printSize(slang::TypeLayoutReflection* typeLayout, slang::ParameterCategory layoutUnit) + { + printSize(layoutUnit, typeLayout->getSize(layoutUnit)); + } + + void printSize(slang::ParameterCategory layoutUnit, size_t size) + { + SCOPED_OBJECT(); + + key("value"); + printPossiblyUnbounded(size); + key("unit"); + printLayoutUnit(layoutUnit); + } + + // #### Kind-Specific Information + // + void printKindSpecificInfo(slang::TypeLayoutReflection* typeLayout, AccessPath accessPath) + { + switch (typeLayout->getKind()) + { + // #### Structure Type Layouts + // + case slang::TypeReflection::Kind::Struct: + { + key("fields"); + + int fieldCount = typeLayout->getFieldCount(); + WITH_ARRAY() + for (int f = 0; f < fieldCount; f++) + { + element(); + + auto field = typeLayout->getFieldByIndex(f); + printVariableLayout(field, accessPath); + } + } + break; + + // #### Array Type Layouts + // + case slang::TypeReflection::Kind::Array: + { + key("element type layout"); + printTypeLayout(typeLayout->getElementTypeLayout(), AccessPath()); + } + break; + + // #### Matrix Type Layouts + // + case slang::TypeReflection::Kind::Matrix: + { + key("matrix layout mode"); + printMatrixLayoutMode(typeLayout->getMatrixLayoutMode()); + + key("element type layout"); + printTypeLayout(typeLayout->getElementTypeLayout(), AccessPath()); + } + break; + + case slang::TypeReflection::Kind::Vector: + { + key("element type layout"); + printTypeLayout(typeLayout->getElementTypeLayout(), AccessPath()); + } + break; + + // #### Single-Element Containers + // + case slang::TypeReflection::Kind::ConstantBuffer: + case slang::TypeReflection::Kind::ParameterBlock: + case slang::TypeReflection::Kind::TextureBuffer: + case slang::TypeReflection::Kind::ShaderStorageBuffer: + { + auto containerVarLayout = typeLayout->getContainerVarLayout(); + auto elementVarLayout = typeLayout->getElementVarLayout(); + + key("container"); + { + SCOPED_OBJECT(); + printOffsets(containerVarLayout, accessPath); + } + + AccessPath innerOffsets = accessPath; + innerOffsets.deepestConstantBufer = innerOffsets.leaf; + if (containerVarLayout->getTypeLayout()->getSize( + slang::ParameterCategory::SubElementRegisterSpace) != 0) + { + innerOffsets.deepestParameterBlock = innerOffsets.leaf; + } + + key("content"); + { + SCOPED_OBJECT(); + + printOffsets(elementVarLayout, innerOffsets); + + ExtendedAccessPath elementOffsets(innerOffsets, elementVarLayout); + + key("type layout"); + printTypeLayout(elementVarLayout->getTypeLayout(), elementOffsets); + } + } + break; + + case slang::TypeReflection::Kind::Resource: + { + if ((typeLayout->getResourceShape() & SLANG_RESOURCE_BASE_SHAPE_MASK) == + SLANG_STRUCTURED_BUFFER) + { + key("element type layout"); + printTypeLayout(typeLayout->getElementTypeLayout(), accessPath); + } + else + { + key("result type"); + printType(typeLayout->getResourceResultType()); + } + } + break; + + default: + break; + } + } + + // Programs and Scopes + // ------------------- + // + void printProgramLayout(slang::ProgramLayout* programLayout, SlangCompileTarget targetFormat) + { + SCOPED_OBJECT(); + + key("target"); + printTargetFormat(targetFormat); + + AccessPath rootOffsets; + rootOffsets.valid = true; + + key("global scope"); + { + SCOPED_OBJECT(); + printScope(programLayout->getGlobalParamsVarLayout(), rootOffsets); + } + + key("entry points"); + int entryPointCount = programLayout->getEntryPointCount(); + WITH_ARRAY() + for (int i = 0; i < entryPointCount; ++i) + { + element(); + printEntryPointLayout(programLayout->getEntryPointByIndex(i), rootOffsets); + } + } + + // ### Global Scope + // + void printScope(slang::VariableLayoutReflection* scopeVarLayout, AccessPath accessPath) + { + ExtendedAccessPath scopeOffsets(accessPath, scopeVarLayout); + + auto scopeTypeLayout = scopeVarLayout->getTypeLayout(); + switch (scopeTypeLayout->getKind()) + { + // #### Parameters are Grouped Into a Structure + // + case slang::TypeReflection::Kind::Struct: + { + key("parameters"); + + int paramCount = scopeTypeLayout->getFieldCount(); + for (int i = 0; i < paramCount; i++) + { + element(); + + auto param = scopeTypeLayout->getFieldByIndex(i); + + printVariableLayout(param, scopeOffsets); + } + } + break; + + // #### Wrapped in a Constant Buffer If Needed + // + case slang::TypeReflection::Kind::ConstantBuffer: + key("automatically-introduced constant buffer"); + { + SCOPED_OBJECT(); + printOffsets(scopeTypeLayout->getContainerVarLayout(), scopeOffsets); + } + + printScope(scopeTypeLayout->getElementVarLayout(), scopeOffsets); + break; + + // #### Wrapped in a Parameter Block If Needed + // + case slang::TypeReflection::Kind::ParameterBlock: + key("automatically-introduced parameter block"); + { + SCOPED_OBJECT(); + printOffsets(scopeTypeLayout->getContainerVarLayout(), scopeOffsets); + } + + printScope(scopeTypeLayout->getElementVarLayout(), scopeOffsets); + break; + + default: + // Note that this default case is never expected to + // arise with the current Slang compiler and reflection + // API, but we include it here as a kind of failsafe. + // + key("variable layout"); + printVariableLayout(scopeVarLayout, accessPath); + break; + } + } + + // ### Entry Points + // + void printEntryPointLayout(slang::EntryPointReflection* entryPointLayout, AccessPath accessPath) + { + SCOPED_OBJECT(); + + key("stage"); + printStage(entryPointLayout->getStage()); + + printStageSpecificInfo(entryPointLayout); + + printScope(entryPointLayout->getVarLayout(), accessPath); + + auto resultVariableLayout = entryPointLayout->getResultVarLayout(); + if (resultVariableLayout->getTypeLayout()->getKind() != slang::TypeReflection::Kind::None) + { + key("result"); + printVariableLayout(resultVariableLayout, accessPath); + } + } + + // #### Stage-Specific Information + // + void printStageSpecificInfo(slang::EntryPointReflection* entryPointLayout) + { + switch (entryPointLayout->getStage()) + { + default: + break; + + case SLANG_STAGE_COMPUTE: + { + static const int kAxisCount = 3; + SlangUInt sizes[kAxisCount]; + entryPointLayout->getComputeThreadGroupSize(kAxisCount, sizes); + + key("thread group size"); + SCOPED_OBJECT(); + key("x"); + print(sizes[0]); + key("y"); + print(sizes[1]); + key("z"); + print(sizes[2]); + } + break; + + case SLANG_STAGE_FRAGMENT: + key("uses any sample-rate inputs"); + printBool(entryPointLayout->usesAnySampleRateInput()); + break; + } + } + + // #### Varying Parameters + // + void printVaryingParameterInfo(slang::VariableLayoutReflection* variableLayout) + { + if (auto semanticName = variableLayout->getSemanticName()) + { + key("semantic"); + SCOPED_OBJECT(); + key("name"); + printQuotedString(semanticName); + key("index"); + print(variableLayout->getSemanticIndex()); + } + } + + // Calculating Cumulative Offsets + // ------------------------------ + // + struct CumulativeOffset + { + size_t value = 0; + size_t space = 0; + }; + + // ### Access Paths + + struct AccessPathNode + { + slang::VariableLayoutReflection* variableLayout = nullptr; + AccessPathNode* outer = nullptr; + }; + + struct AccessPath + { + AccessPath() {} + + bool valid = false; + AccessPathNode* deepestConstantBufer = nullptr; + AccessPathNode* deepestParameterBlock = nullptr; + AccessPathNode* leaf = nullptr; + }; + + void printCumulativeOffsets( + slang::VariableLayoutReflection* variableLayout, + AccessPath accessPath) + { + key("cumulative"); + + int usedLayoutUnitCount = variableLayout->getCategoryCount(); + WITH_ARRAY(); + for (int i = 0; i < usedLayoutUnitCount; ++i) + { + element(); + + auto layoutUnit = variableLayout->getCategoryByIndex(i); + printCumulativeOffset(variableLayout, layoutUnit, accessPath); + } + } + + CumulativeOffset calculateCumulativeOffset( + slang::VariableLayoutReflection* variableLayout, + slang::ParameterCategory layoutUnit, + AccessPath accessPath) + { + CumulativeOffset result = calculateCumulativeOffset(layoutUnit, accessPath); + result.value += variableLayout->getOffset(layoutUnit); + result.space += variableLayout->getBindingSpace(layoutUnit); + return result; + } + + void printCumulativeOffset( + slang::VariableLayoutReflection* variableLayout, + slang::ParameterCategory layoutUnit, + AccessPath accessPath) + { + CumulativeOffset cumulativeOffset = + calculateCumulativeOffset(variableLayout, layoutUnit, accessPath); + + printOffset(layoutUnit, cumulativeOffset.value, cumulativeOffset.space); + } + + // ### Tracking Access Paths + + struct ExtendedAccessPath : AccessPath + { + ExtendedAccessPath(AccessPath const& base, slang::VariableLayoutReflection* variableLayout) + : AccessPath(base) + { + if (!valid) + return; + + element.variableLayout = variableLayout; + element.outer = leaf; + + leaf = &element; + } + + AccessPathNode element; + }; + + // ### Accumulating Offsets Along An Access Path + + CumulativeOffset calculateCumulativeOffset( + slang::ParameterCategory layoutUnit, + AccessPath accessPath) + { + CumulativeOffset result; + switch (layoutUnit) + { + // #### Layout Units That Don't Require Special Handling + // + default: + for (auto node = accessPath.leaf; node != nullptr; node = node->outer) + { + result.value += node->variableLayout->getOffset(layoutUnit); + } + break; + + // #### Bytes + // + case slang::ParameterCategory::Uniform: + for (auto node = accessPath.leaf; node != accessPath.deepestConstantBufer; + node = node->outer) + { + result.value += node->variableLayout->getOffset(layoutUnit); + } + break; + + // #### Layout Units That Care About Spaces + // + case slang::ParameterCategory::ConstantBuffer: + case slang::ParameterCategory::ShaderResource: + case slang::ParameterCategory::UnorderedAccess: + case slang::ParameterCategory::SamplerState: + case slang::ParameterCategory::DescriptorTableSlot: + for (auto node = accessPath.leaf; node != accessPath.deepestParameterBlock; + node = node->outer) + { + result.value += node->variableLayout->getOffset(layoutUnit); + result.space += node->variableLayout->getBindingSpace(layoutUnit); + } + for (auto node = accessPath.deepestParameterBlock; node != nullptr; node = node->outer) + { + result.space += node->variableLayout->getOffset( + slang::ParameterCategory::SubElementRegisterSpace); + } + break; + } + return result; + } + + // Determining Whether Parameters Are Used + // --------------------------------------- + + Result collectEntryPointMetadata( + slang::IComponentType* program, + int targetIndex, + int entryPointCount) + { + _metadataForEntryPoints.setCount(entryPointCount); + for (int entryPointIndex = 0; entryPointIndex < entryPointCount; entryPointIndex++) + { + ComPtr<slang::IMetadata> entryPointMetadata; + ComPtr<slang::IBlob> diagnostics; + SLANG_RETURN_ON_FAIL(program->getEntryPointMetadata( + entryPointIndex, + targetIndex, + entryPointMetadata.writeRef(), + diagnostics.writeRef())); + diagnoseIfNeeded(diagnostics); + + _metadataForEntryPoints[entryPointIndex] = entryPointMetadata; + } + return SLANG_OK; + } + Slang::List<ComPtr<slang::IMetadata>> _metadataForEntryPoints; + + typedef unsigned int StageMask; + + StageMask calculateParameterStageMask( + slang::ParameterCategory layoutUnit, + CumulativeOffset offset) + { + unsigned mask = 0; + auto entryPointCount = _metadataForEntryPoints.getCount(); + for (int i = 0; i < entryPointCount; ++i) + { + bool isUsed = false; + _metadataForEntryPoints[i]->isParameterLocationUsed( + SlangParameterCategory(layoutUnit), + offset.space, + offset.value, + isUsed); + if (isUsed) + { + auto entryPointStage = _programLayout->getEntryPointByIndex(i)->getStage(); + + mask |= 1 << unsigned(entryPointStage); + } + } + return mask; + } + + StageMask calculateStageMask( + slang::VariableLayoutReflection* variableLayout, + AccessPath accessPath) + { + StageMask mask = 0; + + int usedLayoutUnitCount = variableLayout->getCategoryCount(); + for (int i = 0; i < usedLayoutUnitCount; ++i) + { + auto layoutUnit = variableLayout->getCategoryByIndex(i); + auto offset = calculateCumulativeOffset(variableLayout, layoutUnit, accessPath); + + mask |= calculateParameterStageMask(layoutUnit, offset); + } + + return mask; + } + + void printStageUsage(slang::VariableLayoutReflection* variableLayout, AccessPath accessPath) + { + StageMask stageMask = calculateStageMask(variableLayout, accessPath); + + key("used by stages"); + WITH_ARRAY() + for (int i = 0; i < SLANG_STAGE_COUNT; i++) + { + if (stageMask & (1 << i)) + { + element(); + printStage(SlangStage(i)); + } + } + } + + void printOffsets(slang::VariableLayoutReflection* variableLayout, AccessPath accessPath) + { + key("offset"); + { + SCOPED_OBJECT(); + printRelativeOffsets(variableLayout); + + if (accessPath.valid) + { + printCumulativeOffsets(variableLayout, accessPath); + } + } + + + if (accessPath.valid) + { + printStageUsage(variableLayout, accessPath); + } + } + + // Formatting + // ---------- + // + // Here we'll cover the logic for how we implement + // the various formatting operations used in the + // code above. + // + // ### Indentation + // + // We track a global indentation level, and whenever + // we begin a new line, we'll emit a corresponding + // amount of space (two spaces per indent, consistent + // with typical YAML formatting). + + int indentation = 0; + + void printIndentation() + { + for (int i = 1; i < indentation; ++i) + { + printf(" "); + } + } + + // ### Objects and Arrays + // + // Both objects and arrays can be marked up purely + // with indentation in YAML. If we eventually + // change the output format to something like JSON, + // these operations would need to do more actual + // work. + + void beginObject() { indentation++; } + + void endObject() { indentation--; } + + void beginArray() { indentation++; } + + void endArray() { indentation--; } + + // #### Scope-Based Objects + // + // In order to make it easier to keep the `beginObject()` + // and `endObject()` calls properly paired, we introduce + // a helper type that uses an RAII idiom to automatically + // pair up the calls. + // + struct ScopedObject + { + ScopedObject(ReflectingPrinting* outer) + : outer(outer) + { + outer->beginObject(); + } + + ~ScopedObject() { outer->endObject(); } + + ReflectingPrinting* outer = nullptr; + }; + + // ### Starting New Lines + // + // Typically, when we are about to emit a key + // in an object, or an element in an array, + // we need to start a new line (and print + // the appropriate indentation). + // + void newLine() + { + printf("\n"); + printIndentation(); + } + + + // The main exception is that if we've just + // emitted the `- ` for an array element then + // we don't need to start a new line if + // the next thing we emit is an object key. + // + // We *also* don't need to start a new line + // at the very beginning of the output, so + // we handle that by setting the intial state + // *as if* we have just started an array element. + + bool afterArrayElement = true; + + // ### Array Elements + // + void element() + { + newLine(); + printf("- "); + afterArrayElement = true; + } + + // ### Object Keys + // + void key(char const* key) + { + if (!afterArrayElement) + { + newLine(); + } + afterArrayElement = false; + + printf("%s: ", key); + } + + // ### Printing Simple Values + // + // Simple scalar values like strings, + // `bool`s, and numbers don't need + // much special handling. + + void printQuotedString(char const* text) + { + if (text) + { + printf("\"%s\"", text); + } + else + { + printf("null"); + } + } + + void printBool(bool value) { printf(value ? "true" : "false"); } + + void print(size_t value) { printf("%u", unsigned(value)); } + + // YAML supports comments, but JSON doesn't. + // This function could be stubbed out if + // we switch up the output format. + // + void printComment(char const* text) { printf("# %s", text); } + + + // Printing Enumerants + // ------------------- + // + // Here we'll gather all the logic for printing the various + // `enum` types that we've worked with in the logic above. + + void printTypeKind(slang::TypeReflection::Kind kind) + { + switch (kind) + { +#define CASE(TAG) \ + case slang::TypeReflection::Kind::TAG: \ + printf("%s", #TAG); \ + break + + CASE(None); + CASE(Struct); + CASE(Array); + CASE(Matrix); + CASE(Vector); + CASE(Scalar); + CASE(ConstantBuffer); + CASE(Resource); + CASE(SamplerState); + CASE(TextureBuffer); + CASE(ShaderStorageBuffer); + CASE(ParameterBlock); + CASE(GenericTypeParameter); + CASE(Interface); + CASE(OutputStream); + CASE(Specialized); + CASE(Feedback); + CASE(Pointer); + CASE(DynamicResource); +#undef CASE + + default: + printf("%d # unexpected enumerant", int(kind)); + break; + } + } + + void printResourceShape(SlangResourceShape shape) + { + SCOPED_OBJECT(); + + key("base"); + auto baseShape = shape & SLANG_RESOURCE_BASE_SHAPE_MASK; + switch (baseShape) + { +#define CASE(TAG) \ + case SLANG_##TAG: \ + printf("%s", #TAG); \ + break + + CASE(TEXTURE_1D); + CASE(TEXTURE_2D); + CASE(TEXTURE_3D); + CASE(TEXTURE_CUBE); + CASE(TEXTURE_BUFFER); + CASE(STRUCTURED_BUFFER); + CASE(BYTE_ADDRESS_BUFFER); + CASE(RESOURCE_UNKNOWN); + CASE(ACCELERATION_STRUCTURE); + CASE(TEXTURE_SUBPASS); +#undef CASE + + default: + printf("%d # unexpected enumerant", int(baseShape)); + break; + } + +#define CASE(TAG) \ + do \ + { \ + if (shape & SLANG_TEXTURE_##TAG##_FLAG) \ + { \ + key(#TAG); \ + printf("true"); \ + } \ + } while (0) + + CASE(FEEDBACK); + CASE(SHADOW); + CASE(ARRAY); + CASE(MULTISAMPLE); +#undef CASE + } + + void printResourceAccess(SlangResourceAccess access) + { + switch (access) + { +#define CASE(TAG) \ + case SLANG_RESOURCE_ACCESS_##TAG: \ + printf("%s", #TAG); \ + break + + CASE(NONE); + CASE(READ); + CASE(READ_WRITE); + CASE(RASTER_ORDERED); + CASE(APPEND); + CASE(CONSUME); + CASE(WRITE); + CASE(FEEDBACK); +#undef CASE + + default: + printf("%d # unexpected enumerant", int(access)); + break; + } + } + + void printLayoutUnit(slang::ParameterCategory layoutUnit) + { + switch (layoutUnit) + { +#define CASE(TAG, DESCRIPTION) \ + case slang::ParameterCategory::TAG: \ + printf("%s # %s", #TAG, DESCRIPTION); \ + break + + CASE(ConstantBuffer, "constant buffer slots"); + CASE(ShaderResource, "texture slots"); + CASE(UnorderedAccess, "uav slots"); + CASE(VaryingInput, "varying input slots"); + CASE(VaryingOutput, "varying output slots"); + CASE(SamplerState, "sampler slots"); + CASE(Uniform, "bytes"); + CASE(DescriptorTableSlot, "bindings"); + CASE(SpecializationConstant, "specialization constant ids"); + CASE(PushConstantBuffer, "push-constant buffers"); + CASE(RegisterSpace, "register space offset for a variable"); + CASE(GenericResource, "generic resources"); + CASE(RayPayload, "ray payloads"); + CASE(HitAttributes, "hit attributes"); + CASE(CallablePayload, "callable payloads"); + CASE(ShaderRecord, "shader records"); + CASE(ExistentialTypeParam, "existential type parameters"); + CASE(ExistentialObjectParam, "existential object parameters"); + CASE(SubElementRegisterSpace, "register spaces / descriptor sets"); + CASE(InputAttachmentIndex, "subpass input attachments"); + CASE(MetalArgumentBufferElement, "Metal argument buffer elements"); + CASE(MetalAttribute, "Metal attributes"); + CASE(MetalPayload, "Metal payloads"); +#undef CASE + + default: + printf("%d # unknown enumerant", int(layoutUnit)); + break; + } + } + + void printStage(SlangStage stage) + { + switch (stage) + { +#define CASE(NAME) \ + case SLANG_STAGE_##NAME: \ + printf(#NAME); \ + break + + CASE(NONE); + CASE(VERTEX); + CASE(HULL); + CASE(DOMAIN); + CASE(GEOMETRY); + CASE(FRAGMENT); + CASE(COMPUTE); + CASE(RAY_GENERATION); + CASE(INTERSECTION); + CASE(ANY_HIT); + CASE(CLOSEST_HIT); + CASE(MISS); + CASE(CALLABLE); + CASE(MESH); + CASE(AMPLIFICATION); +#undef CASE + + default: + printf("%d # unexpected enumerant", int(stage)); + break; + }; + } + void printTargetFormat(SlangCompileTarget targetFormat) + { + switch (targetFormat) + { +#define CASE(TAG) \ + case SLANG_##TAG: \ + printf("%s", #TAG); \ + break + + CASE(TARGET_UNKNOWN); + CASE(TARGET_NONE); + CASE(GLSL); + CASE(GLSL_VULKAN_DEPRECATED); + CASE(GLSL_VULKAN_ONE_DESC_DEPRECATED); + CASE(HLSL); + CASE(SPIRV); + CASE(SPIRV_ASM); + CASE(DXBC); + CASE(DXBC_ASM); + CASE(DXIL); + CASE(DXIL_ASM); + CASE(C_SOURCE); + CASE(CPP_SOURCE); + CASE(HOST_EXECUTABLE); + CASE(SHADER_SHARED_LIBRARY); + CASE(SHADER_HOST_CALLABLE); + CASE(CUDA_SOURCE); + CASE(PTX); + CASE(CUDA_OBJECT_CODE); + CASE(OBJECT_CODE); + CASE(HOST_CPP_SOURCE); + CASE(HOST_HOST_CALLABLE); + CASE(CPP_PYTORCH_BINDING); + CASE(METAL); + CASE(METAL_LIB); + CASE(METAL_LIB_ASM); + CASE(HOST_SHARED_LIBRARY); + CASE(WGSL); + CASE(WGSL_SPIRV_ASM); + CASE(WGSL_SPIRV); +#undef CASE + + default: + printf("%d # unhandled enumerant", int(targetFormat)); + } + } + + void printScalarType(slang::TypeReflection::ScalarType scalarType) + { + switch (scalarType) + { +#define CASE(TAG) \ + case slang::TypeReflection::TAG: \ + printf("%s", #TAG); \ + break + + CASE(None); + CASE(Void); + CASE(Bool); + CASE(Int32); + CASE(UInt32); + CASE(Int64); + CASE(UInt64); + CASE(Float16); + CASE(Float32); + CASE(Float64); + CASE(Int8); + CASE(UInt8); + CASE(Int16); + CASE(UInt16); +#undef CASE + + default: + printf("%d # unhandled enumerant", int(scalarType)); + } + } + + void printMatrixLayoutMode(SlangMatrixLayoutMode mode) + { + switch (mode) + { +#define CASE(TAG) \ + case SLANG_MATRIX_LAYOUT_##TAG: \ + printf("%s", #TAG); \ + break + + CASE(MODE_UNKNOWN); + CASE(ROW_MAJOR); + CASE(COLUMN_MAJOR); +#undef CASE + + default: + printf("%d # unhandled enumerant", int(mode)); + } + } +}; + +struct ExampleProgram : public TestBase +{ + Result execute(int argc, char* argv[]) + { + parseOption(argc, argv); + + ComPtr<slang::IGlobalSession> globalSession; + SLANG_RETURN_ON_FAIL(slang::createGlobalSession(globalSession.writeRef())); + + Slang::List<slang::TargetDesc> targetDescs; + for (auto target : kTargets) + { + auto profile = globalSession->findProfile(target.profile); + + slang::TargetDesc targetDesc; + targetDesc.format = target.format; + targetDesc.profile = profile; + targetDescs.add(targetDesc); + } + + slang::SessionDesc sessionDesc; + sessionDesc.targetCount = targetDescs.getCount(); + sessionDesc.targets = targetDescs.getBuffer(); + + ComPtr<slang::ISession> session; + SLANG_RETURN_ON_FAIL(globalSession->createSession(sessionDesc, session.writeRef())); + + ReflectingPrinting printingContext; + printingContext.compileAndReflectPrograms(session); + + return SLANG_OK; + } +}; + +int main(int argc, char* argv[]) +{ + ExampleProgram app; + if (SLANG_FAILED(app.execute(argc, argv))) + { + return -1; + } + return 0; +} diff --git a/examples/reflection-api/raster-simple.slang b/examples/reflection-api/raster-simple.slang new file mode 100644 index 000000000..a44410e64 --- /dev/null +++ b/examples/reflection-api/raster-simple.slang @@ -0,0 +1,88 @@ +// raster-simple.slang + +struct AssembledVertex +{ + float3 position : POSITION; + float3 normal : NORMAL; + float2 uv : TEXCOORD; +}; + +struct RasterVertex +{ + float3 worldPosition; + float3 worldNormal; + float2 uv; +}; + +struct Model +{ + float3x4 modelToWorld; + float3x4 modelToWorld_inverseTranspose; +} + +struct Material +{ + Texture2D<float3> albedoMap; + Texture2D<float3> normalMap; + Texture2D<float> glossMap; + SamplerState sampler; + float2 uvScale; + float2 uvBias; +} + +struct Camera +{ + float3x4 worldToView; + float3x4 worldToView_inverseTranspose; + + float4x4 viewToProj; +} + +struct DirectionalLight +{ + float3 intensity; + float3 direction; +} + +struct Environment +{ + TextureCube environmentMap; + DirectionalLight light; +} + +uniform Model model; +uniform ParameterBlock<Material> material; +uniform ConstantBuffer<Camera> camera; +uniform ParameterBlock<Environment> environment; + +[shader("vertex")] +[require(sm_6_0)] +void vertexMain( + in AssembledVertex assembledVertex : A, + out RasterVertex rasterVertex : R, + in uint vertexID : SV_VertexID, + out float4 projPosition : SV_Position) +{ + float3 worldPosition = mul(model.modelToWorld, float4(assembledVertex.position,1)); + + rasterVertex.worldPosition = worldPosition; + rasterVertex.worldNormal = mul(model.modelToWorld_inverseTranspose, float4(assembledVertex.normal,0)); + rasterVertex.uv = assembledVertex.uv; + + float3 viewPosition = mul(camera.worldToView, float4(worldPosition,1)); + projPosition = mul(camera.viewToProj, float4(viewPosition,1)); +} + +[shader("fragment")] +[require(sm_6_0)] +float4 fragmentMain( + in RasterVertex vertex : R) + : SV_Target0 +{ + float3 normal = vertex.worldNormal; + + float3 albedo = material.albedoMap.Sample(material.sampler, vertex.uv); + + float3 color = albedo * max(0, dot(normal, environment.light.direction)); + return float4(color, 1); +} diff --git a/include/slang.h b/include/slang.h index 24db0a6ff..05fb78775 100644 --- a/include/slang.h +++ b/include/slang.h @@ -800,6 +800,8 @@ typedef uint32_t SlangSizeT; SLANG_STAGE_CALLABLE, SLANG_STAGE_MESH, SLANG_STAGE_AMPLIFICATION, + // + SLANG_STAGE_COUNT, // alias: SLANG_STAGE_PIXEL = SLANG_STAGE_FRAGMENT, @@ -2428,21 +2430,43 @@ struct TypeLayoutReflection (SlangReflectionTypeLayout*)this); } - size_t getSize(SlangParameterCategory category = SLANG_PARAMETER_CATEGORY_UNIFORM) + size_t getSize(SlangParameterCategory category) { return spReflectionTypeLayout_GetSize((SlangReflectionTypeLayout*)this, category); } - size_t getStride(SlangParameterCategory category = SLANG_PARAMETER_CATEGORY_UNIFORM) + size_t getStride(SlangParameterCategory category) { return spReflectionTypeLayout_GetStride((SlangReflectionTypeLayout*)this, category); } - int32_t getAlignment(SlangParameterCategory category = SLANG_PARAMETER_CATEGORY_UNIFORM) + int32_t getAlignment(SlangParameterCategory category) { return spReflectionTypeLayout_getAlignment((SlangReflectionTypeLayout*)this, category); } + size_t getSize(slang::ParameterCategory category = slang::ParameterCategory::Uniform) + { + return spReflectionTypeLayout_GetSize( + (SlangReflectionTypeLayout*)this, + (SlangParameterCategory)category); + } + + size_t getStride(slang::ParameterCategory category = slang::ParameterCategory::Uniform) + { + return spReflectionTypeLayout_GetStride( + (SlangReflectionTypeLayout*)this, + (SlangParameterCategory)category); + } + + int32_t getAlignment(slang::ParameterCategory category = slang::ParameterCategory::Uniform) + { + return spReflectionTypeLayout_getAlignment( + (SlangReflectionTypeLayout*)this, + (SlangParameterCategory)category); + } + + unsigned int getFieldCount() { return spReflectionTypeLayout_GetFieldCount((SlangReflectionTypeLayout*)this); @@ -2848,10 +2872,17 @@ struct VariableLayoutReflection } - size_t getOffset(SlangParameterCategory category = SLANG_PARAMETER_CATEGORY_UNIFORM) + size_t getOffset(SlangParameterCategory category) { return spReflectionVariableLayout_GetOffset((SlangReflectionVariableLayout*)this, category); } + size_t getOffset(slang::ParameterCategory category = slang::ParameterCategory::Uniform) + { + return spReflectionVariableLayout_GetOffset( + (SlangReflectionVariableLayout*)this, + (SlangParameterCategory)category); + } + TypeReflection* getType() { return getVariable()->getType(); } @@ -2869,6 +2900,12 @@ struct VariableLayoutReflection { return spReflectionVariableLayout_GetSpace((SlangReflectionVariableLayout*)this, category); } + size_t getBindingSpace(slang::ParameterCategory category) + { + return spReflectionVariableLayout_GetSpace( + (SlangReflectionVariableLayout*)this, + (SlangParameterCategory)category); + } char const* getSemanticName() { |
