diff options
| author | Yong He <yonghe@outlook.com> | 2021-05-21 16:38:33 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-05-21 16:38:33 -0700 |
| commit | 7f8a9994d0bd99a171a1daa0bce46d92c02ccffd (patch) | |
| tree | 0b187e63ab5b9ce6f5ab41266fedaec44091a217 | |
| parent | 172538fdb418f7a2faab1f5a410f3b2cb8e18ba5 (diff) | |
[gfx] Support StructuredBuffer<IInterface>. (#1851)
Co-authored-by: T. Foley <tfoleyNV@users.noreply.github.com>
32 files changed, 1307 insertions, 1625 deletions
diff --git a/examples/shader-object/main.cpp b/examples/shader-object/main.cpp index 72ab265c6..71c5de983 100644 --- a/examples/shader-object/main.cpp +++ b/examples/shader-object/main.cpp @@ -212,8 +212,8 @@ int main() // Now we can use this type to create a shader object that can be bound to the root object. ComPtr<gfx::IShaderObject> transformer; - SLANG_RETURN_ON_FAIL( - device->createShaderObject(addTransformerType, transformer.writeRef())); + SLANG_RETURN_ON_FAIL(device->createShaderObject( + addTransformerType, ShaderObjectContainerType::None, transformer.writeRef())); // Set the `c` field of the `AddTransformer`. float c = 1.0f; gfx::ShaderCursor(transformer).getPath("c").setData(&c, sizeof(float)); diff --git a/slang-gfx.h b/slang-gfx.h index 40cf5fbeb..7428f4c56 100644 --- a/slang-gfx.h +++ b/slang-gfx.h @@ -505,6 +505,11 @@ struct ShaderOffset SlangInt bindingArrayIndex = 0; }; +enum class ShaderObjectContainerType +{ + None, Array, StructuredBuffer +}; + class IShaderObject : public ISlangUnknown { public: @@ -516,6 +521,7 @@ public: } virtual SLANG_NO_THROW slang::TypeLayoutReflection* SLANG_MCALL getElementTypeLayout() = 0; + virtual SLANG_NO_THROW ShaderObjectContainerType SLANG_MCALL getContainerType() = 0; virtual SLANG_NO_THROW UInt SLANG_MCALL getEntryPointCount() = 0; ComPtr<IShaderObject> getEntryPoint(UInt index) @@ -1302,12 +1308,15 @@ public: return queue; } - virtual SLANG_NO_THROW Result SLANG_MCALL createShaderObject(slang::TypeReflection* type, IShaderObject** outObject) = 0; + virtual SLANG_NO_THROW Result SLANG_MCALL createShaderObject( + slang::TypeReflection* type, + ShaderObjectContainerType container, + IShaderObject** outObject) = 0; inline ComPtr<IShaderObject> createShaderObject(slang::TypeReflection* type) { ComPtr<IShaderObject> object; - SLANG_RETURN_NULL_ON_FAIL(createShaderObject(type, object.writeRef())); + SLANG_RETURN_NULL_ON_FAIL(createShaderObject(type, ShaderObjectContainerType::None, object.writeRef())); return object; } @@ -3859,6 +3859,11 @@ namespace slang }; + enum class ContainerType + { + None, UnsizedArray, StructuredBuffer, ConstantBuffer, ParameterBlock + }; + /** A session provides a scope for code that is loaded. A session can be used to load modules of Slang source code, @@ -3950,11 +3955,23 @@ namespace slang LayoutRules rules = LayoutRules::Default, ISlangBlob** outDiagnostics = nullptr) = 0; - virtual SLANG_NO_THROW TypeLayoutReflection* SLANG_MCALL getParameterBlockLayout( + /** Get a container type from `elementType`. For example, given type `T`, returns + a type that represents `StructuredBuffer<T>`. + + @param `elementType`: the element type to wrap around. + @param `containerType`: the type of the container to wrap `elementType` in. + @param `outDiagnostics`: a blob to receive diagnostic messages. + */ + virtual SLANG_NO_THROW TypeReflection* SLANG_MCALL getContainerType( TypeReflection* elementType, - SlangInt targetIndex = 0, - LayoutRules rules = LayoutRules::Default, - ISlangBlob** outDiagnostics = nullptr) = 0; + ContainerType containerType, + ISlangBlob** outDiagnostics = nullptr) = 0; + + /** Return a `TypeReflection` that represents the `__Dynamic` type. + This type can be used as a specialization argument to indicate using + dynamic dispatch. + */ + virtual SLANG_NO_THROW TypeReflection* SLANG_MCALL getDynamicType() = 0; /** Get the mangled name for a type RTTI object. */ @@ -4202,6 +4219,14 @@ namespace slang /** A type specialization argument, used for `Kind::Type`. */ TypeReflection* type; }; + + static SpecializationArg fromType(TypeReflection* inType) + { + SpecializationArg rs; + rs.kind = Kind::Type; + rs.type = inType; + return rs; + } }; } diff --git a/source/slang/slang-compiler.h b/source/slang/slang-compiler.h index 54c61bd75..603ee0bb5 100755 --- a/source/slang/slang-compiler.h +++ b/source/slang/slang-compiler.h @@ -1199,14 +1199,10 @@ namespace Slang // TypeLayouts created on the fly by reflection API Dictionary<Type*, RefPtr<TypeLayout>> typeLayouts; - Dictionary<Type*, ParameterBlockType*> parameterBlockTypes; - Dictionary<Type*, RefPtr<TypeLayout>>& getTypeLayouts() { return typeLayouts; } TypeLayout* getTypeLayout(Type* type); - TypeLayout* getParameterBlockLayout(Type* type); - private: Linkage* linkage = nullptr; CodeGenTarget format = CodeGenTarget::Unknown; @@ -1251,6 +1247,21 @@ namespace Slang const char* getBuildTagString(); struct TypeCheckingCache; + + struct ContainerTypeKey + { + slang::TypeReflection* elementType; + slang::ContainerType containerType; + bool operator==(ContainerTypeKey other) + { + return elementType == other.elementType && containerType == other.containerType; + } + Slang::HashCode getHashCode() + { + return Slang::combineHash( + Slang::getHashCode(elementType), Slang::getHashCode(containerType)); + } + }; /// A context for loading and re-using code modules. class Linkage : public RefObject, public slang::ISession @@ -1279,11 +1290,11 @@ namespace Slang SlangInt targetIndex = 0, slang::LayoutRules rules = slang::LayoutRules::Default, ISlangBlob** outDiagnostics = nullptr) override; - SLANG_NO_THROW slang::TypeLayoutReflection* SLANG_MCALL getParameterBlockLayout( + SLANG_NO_THROW slang::TypeReflection* SLANG_MCALL getContainerType( slang::TypeReflection* elementType, - SlangInt targetIndex = 0, - slang::LayoutRules rules = slang::LayoutRules::Default, + slang::ContainerType containerType, ISlangBlob** outDiagnostics = nullptr) override; + SLANG_NO_THROW slang::TypeReflection* SLANG_MCALL getDynamicType() override; SLANG_NO_THROW SlangResult SLANG_MCALL getTypeRTTIMangledName( slang::TypeReflection* type, ISlangBlob** outNameBlob) override; @@ -1348,6 +1359,9 @@ namespace Slang RefPtr<ASTBuilder> m_astBuilder; + // Cache for container types. + Dictionary<ContainerTypeKey, Type*> m_containerTypes; + // cache used by type checking, implemented in check.cpp TypeCheckingCache* getTypeCheckingCache(); void destroyTypeCheckingCache(); diff --git a/source/slang/slang-emit-source-writer.cpp b/source/slang/slang-emit-source-writer.cpp index 29a83a1fc..bed9a2dbc 100644 --- a/source/slang/slang-emit-source-writer.cpp +++ b/source/slang/slang-emit-source-writer.cpp @@ -330,6 +330,9 @@ void SourceWriter::_emitLineDirectiveAndUpdateSourceLocation(const HumaneSourceL void SourceWriter::_emitLineDirectiveIfNeeded(const HumaneSourceLoc& sourceLocation) { + if (m_supressLineDirective) + return; + // Don't do any of this work if the user has requested that we // not emit line directives. auto mode = getLineDirectiveMode(); diff --git a/source/slang/slang-emit-source-writer.h b/source/slang/slang-emit-source-writer.h index 64dc59801..294cfec18 100644 --- a/source/slang/slang-emit-source-writer.h +++ b/source/slang/slang-emit-source-writer.h @@ -47,6 +47,8 @@ public: void emitName(Name* name, const SourceLoc& loc); void emitName(Name* name); + void supressLineDirective() { m_supressLineDirective = true; } + void resumeLineDirective() { m_supressLineDirective = false; } /// Indent the text void indent(); @@ -102,6 +104,8 @@ protected: HumaneSourceLoc m_nextHumaneSourceLocation; bool m_needToUpdateSourceLocation = false; + + bool m_supressLineDirective = false; // Are we at the start of a line, so that we should indent // before writing any other text? diff --git a/source/slang/slang-emit.cpp b/source/slang/slang-emit.cpp index af870d02b..74b11079d 100644 --- a/source/slang/slang-emit.cpp +++ b/source/slang/slang-emit.cpp @@ -849,7 +849,13 @@ SlangResult emitEntryPointsSourceFromIR( } // There may be global-scope modifiers that we should emit now + // Supress emitting line directives when emitting preprocessor directives since + // these preprocessor directives may be required to appear in the first line + // of the output. An example is that the "#version" line in a GLSL source must + // appear before anything else. + sourceWriter.supressLineDirective(); sourceEmitter->emitPreprocessorDirectives(); + sourceWriter.resumeLineDirective(); RefObject* extensionTracker = sourceEmitter->getExtensionTracker(); diff --git a/source/slang/slang-reflection-api.cpp b/source/slang/slang-reflection-api.cpp index b74a019e3..22f34a41a 100644 --- a/source/slang/slang-reflection-api.cpp +++ b/source/slang/slang-reflection-api.cpp @@ -480,6 +480,10 @@ SLANG_API SlangReflectionType* spReflectionType_GetElementType(SlangReflectionTy { return convert(parameterGroupType->elementType); } + else if (auto structuredBufferType = as<HLSLStructuredBufferTypeBase>(type)) + { + return convert(structuredBufferType->elementType); + } else if( auto vectorType = as<VectorExpressionType>(type)) { return convert(vectorType->elementType); @@ -1485,7 +1489,7 @@ namespace Slang Index bindingRangeIndex = m_extendedInfo->m_bindingRanges.getCount(); SlangBindingType bindingType = SLANG_BINDING_TYPE_CONSTANT_BUFFER; Index spaceOffset = -1; - bool usesIndirectAllocation = false; + bool shouldAllocDescriptorSet = true; LayoutResourceKind kind = LayoutResourceKind::None; // TODO: It is unclear if this should be looking at the resource @@ -1515,13 +1519,14 @@ namespace Slang // Note: the only case where a parameter group should // reflect as consuming `Uniform` storage is on CPU/CUDA, // where that will be the only resource it contains. + case LayoutResourceKind::Uniform: + break; // // TODO: If we ever support targets that don't have // constant buffers at all, this logic would be questionable. // case LayoutResourceKind::RegisterSpace: - case LayoutResourceKind::Uniform: - usesIndirectAllocation = true; + shouldAllocDescriptorSet = false; break; } @@ -1591,7 +1596,7 @@ namespace Slang // because the physical storage for `C.a` is provided by the // memory allocation for `C` itself. - if( !usesIndirectAllocation ) + if (shouldAllocDescriptorSet) { // The logic here assumes that when a parameter group consumes // resources that must "leak" into the outer scope (including @@ -1737,8 +1742,8 @@ namespace Slang else { // Here we have the catch-all case that handles "leaf" fields - // that should never introduce a sub-object range, but might - // need to introduce a binding range and descriptor ranges. + // that might need to introduce a binding range and descriptor + // ranges. // // First, we want to determine what type of binding this // leaf field should map to, if any. We being by querying @@ -1839,12 +1844,13 @@ namespace Slang // TODO: Make some clear decisions about what should and should // not appear here. // - case LayoutResourceKind::Uniform: case LayoutResourceKind::RegisterSpace: case LayoutResourceKind::VaryingInput: case LayoutResourceKind::VaryingOutput: case LayoutResourceKind::HitAttributes: case LayoutResourceKind::RayPayload: + case LayoutResourceKind::ExistentialTypeParam: + case LayoutResourceKind::ExistentialObjectParam: continue; } @@ -1888,7 +1894,19 @@ namespace Slang bindingRange.descriptorRangeCount++; } + auto bindingRangeIndex = m_extendedInfo->m_bindingRanges.getCount(); + m_extendedInfo->m_bindingRanges.add(bindingRange); + + // For `StructuredBuffer` fields, we also make sure to report it as a sub-object range. + if (auto structuredBufferTypeLayout = as<StructuredBufferTypeLayout>(typeLayout)) + { + TypeLayout::ExtendedInfo::SubObjectRangeInfo subObjectRange; + subObjectRange.bindingRangeIndex = bindingRangeIndex; + subObjectRange.offsetVarLayout = createOffsetVarLayout(typeLayout, path); + subObjectRange.spaceOffset = 0; + m_extendedInfo->m_subObjectRanges.add(subObjectRange); + } } } }; diff --git a/source/slang/slang.cpp b/source/slang/slang.cpp index 8479a0e99..9a109f9d7 100644 --- a/source/slang/slang.cpp +++ b/source/slang/slang.cpp @@ -896,35 +896,65 @@ SLANG_NO_THROW slang::TypeLayoutReflection* SLANG_MCALL Linkage::getTypeLayout( return asExternal(typeLayout); } -SLANG_NO_THROW slang::TypeLayoutReflection* SLANG_MCALL Linkage::getParameterBlockLayout( +SLANG_NO_THROW slang::TypeReflection* SLANG_MCALL Linkage::getContainerType( slang::TypeReflection* inType, - SlangInt targetIndex, - slang::LayoutRules rules, + slang::ContainerType containerType, ISlangBlob** outDiagnostics) { auto type = asInternal(inType); - if (targetIndex < 0 || targetIndex >= targets.getCount()) - return nullptr; - - auto target = targets[targetIndex]; - - // TODO: We need a way to pass through the layout rules - // that the user requested (e.g., constant buffers vs. - // structured buffer rules). Right now the API only - // exposes a single case, so this isn't a big deal. - // - SLANG_UNUSED(rules); - - auto typeLayout = target->getParameterBlockLayout(type); + Type* containerTypeReflection = nullptr; + ContainerTypeKey key = {inType, containerType}; + if (!m_containerTypes.TryGetValue(key, containerTypeReflection)) + { + switch (containerType) + { + case slang::ContainerType::ConstantBuffer: + { + ConstantBufferType* cbType = getASTBuilder()->create<ConstantBufferType>(); + cbType->elementType = type; + containerTypeReflection = cbType; + } + break; + case slang::ContainerType::ParameterBlock: + { + ParameterBlockType* pbType = getASTBuilder()->create<ParameterBlockType>(); + pbType->elementType = type; + containerTypeReflection = pbType; + } + break; + case slang::ContainerType::StructuredBuffer: + { + HLSLStructuredBufferType* sbType = + getASTBuilder()->create<HLSLStructuredBufferType>(); + sbType->elementType = type; + containerTypeReflection = sbType; + } + break; + case slang::ContainerType::UnsizedArray: + { + ArrayExpressionType* arrType = getASTBuilder()->create<ArrayExpressionType>(); + arrType->baseType = type; + arrType->arrayLength = nullptr; + containerTypeReflection = arrType; + } + break; + default: + containerTypeReflection = type; + break; + } + + m_containerTypes.Add(key, containerTypeReflection); + } - // TODO: We currently don't have a path for capturing - // errors that occur during layout (e.g., types that - // are invalid because of target-specific layout constraints). - // SLANG_UNUSED(outDiagnostics); - return asExternal(typeLayout); + return asExternal(containerTypeReflection); +} + +SLANG_NO_THROW slang::TypeReflection* SLANG_MCALL Linkage::getDynamicType() +{ + return asExternal(getASTBuilder()->getSharedASTBuilder()->getDynamicType()); } SLANG_NO_THROW SlangResult SLANG_MCALL Linkage::getTypeRTTIMangledName( @@ -1132,19 +1162,6 @@ TypeLayout* TargetRequest::getTypeLayout(Type* type) return result.Ptr(); } -TypeLayout* TargetRequest::getParameterBlockLayout(Type* type) -{ - ParameterBlockType* parameterBlockType = nullptr; - if (!parameterBlockTypes.TryGetValue(type, parameterBlockType)) - { - parameterBlockType = getLinkage()->getASTBuilder()->create<ParameterBlockType>(); - parameterBlockType->elementType = type; - parameterBlockTypes.Add(type, parameterBlockType); - } - return getTypeLayout(parameterBlockType); -} - - // // TranslationUnitRequest // diff --git a/tests/compute/dynamic-dispatch-13.slang b/tests/compute/dynamic-dispatch-13.slang index 5acc981e1..d44029c23 100644 --- a/tests/compute/dynamic-dispatch-13.slang +++ b/tests/compute/dynamic-dispatch-13.slang @@ -1,9 +1,9 @@ // Test using interface typed shader parameters wrapped inside a `StructuredBuffer`. -//DISABLED_TEST(compute):COMPARE_COMPUTE:-cpu -shaderobj -//DISABLED_TEST(compute):COMPARE_COMPUTE:-dx11 -//DISABLED_TEST(compute):COMPARE_COMPUTE:-vk -//DISABLED_TEST(compute):COMPARE_COMPUTE:-cuda -shaderobj +//TEST(compute):COMPARE_COMPUTE:-cpu -shaderobj +//TEST(compute):COMPARE_COMPUTE:-dx11 +//TEST(compute):COMPARE_COMPUTE:-vk +//TEST(compute):COMPARE_COMPUTE:-cuda -shaderobj [anyValueSize(8)] interface IInterface @@ -11,13 +11,14 @@ interface IInterface int run(int input); } +// Specialize gCb1, but not gCb2 + //TEST_INPUT:ubuffer(data=[0 0 0 0], stride=4):out,name=gOutputBuffer RWStructuredBuffer<int> gOutputBuffer; - -//TEST_INPUT:ubuffer(data=[rtti(MyImpl) witness(MyImpl, IInterface) 1 0], stride=4):name=gCb +//TEST_INPUT: set gCb = new StructuredBuffer<IInterface>{new MyImpl{1}}; RWStructuredBuffer<IInterface> gCb; - -//TEST_INPUT:ubuffer(data=[rtti(MyImpl) witness(MyImpl, IInterface) 1 0], stride=4):name=gCb1 +// Add two elements into the structured buffer to prevent specialization. +//TEST_INPUT: set gCb1 = new StructuredBuffer<IInterface>{new MyImpl{1}, new MyImpl2{2}}; RWStructuredBuffer<IInterface> gCb1; [numthreads(4, 1, 1)] @@ -33,9 +34,6 @@ void computeMain(uint3 dispatchThreadID : SV_DispatchThreadID) gOutputBuffer[tid] = outputVal; } -// Specialize gCb1, but not gCb2 -//TEST_INPUT: globalExistentialType MyImpl -//TEST_INPUT: globalExistentialType __Dynamic // Type must be marked `public` to ensure it is visible in the generated DLL. public struct MyImpl : IInterface { diff --git a/tests/compute/dynamic-dispatch-14.slang b/tests/compute/dynamic-dispatch-14.slang index 4dce1c2ed..8361cd317 100644 --- a/tests/compute/dynamic-dispatch-14.slang +++ b/tests/compute/dynamic-dispatch-14.slang @@ -1,9 +1,12 @@ // Test using interface typed shader parameters with associated types. -//DISABLED_TEST(compute):COMPARE_COMPUTE:-dx11 -//DISABLED_TEST(compute):COMPARE_COMPUTE:-cpu -shaderobj -//DISABLED_TEST(compute):COMPARE_COMPUTE:-vk -//DISABLED_TEST(compute):COMPARE_COMPUTE:-cuda -shaderobj +//TEST(compute):COMPARE_COMPUTE_EX:-slang -compute -dx12 -profile sm_6_0 -use-dxil +//TEST(compute):COMPARE_COMPUTE_EX:-slang -compute -dx11 -profile sm_5_0 +//TEST(compute):COMPARE_COMPUTE_EX:-slang -compute -gl -profile glsl440 +//TEST(compute):COMPARE_COMPUTE_EX:-slang -compute -vk -profile glsl440 +//TEST(compute):COMPARE_COMPUTE_EX:-slang -compute -cpu + + [anyValueSize(8)] interface IAssoc @@ -21,10 +24,13 @@ interface IInterface //TEST_INPUT:ubuffer(data=[0 0 0 0], stride=4):out,name=gOutputBuffer RWStructuredBuffer<int> gOutputBuffer; -//TEST_INPUT:ubuffer(data=[rtti(MyImpl) witness(MyImpl, IInterface) 1 0], stride=4):name=gCb + +// Specialize gCb1, but not gCb2 +//TEST_INPUT: set gCb = new StructuredBuffer<IInterface>{new MyImpl{1}}; RWStructuredBuffer<IInterface> gCb; -//TEST_INPUT:ubuffer(data=[rtti(MyImpl) witness(MyImpl, IInterface) 1 0], stride=4):name=gCb1 +// Add two elements into the structured buffer to prevent specialization. +//TEST_INPUT: set gCb1 = new StructuredBuffer<IInterface>{new MyImpl{1}, new MyImpl2{2}}; RWStructuredBuffer<IInterface> gCb1; [numthreads(4, 1, 1)] @@ -40,9 +46,6 @@ void computeMain(uint3 dispatchThreadID : SV_DispatchThreadID) gOutputBuffer[tid] = outputVal; } -// Specialize gCb1, but not gCb2 -//TEST_INPUT: globalExistentialType MyImpl -//TEST_INPUT: globalExistentialType __Dynamic // Type must be marked `public` to ensure it is visible in the generated DLL. public struct MyImpl : IInterface { diff --git a/tests/compute/dynamic-dispatch-bindless-texture.slang b/tests/compute/dynamic-dispatch-bindless-texture.slang index a4483c9e1..8a9d0c128 100644 --- a/tests/compute/dynamic-dispatch-bindless-texture.slang +++ b/tests/compute/dynamic-dispatch-bindless-texture.slang @@ -1,6 +1,6 @@ // Test using interface typed shader parameters with texture typed fields. -//DISABLED_TEST(compute):COMPARE_COMPUTE:-cpu -//DISABLED_TEST(compute):COMPARE_COMPUTE:-cuda +//TEST(compute):COMPARE_COMPUTE:-cpu +//TEST(compute):COMPARE_COMPUTE:-cuda [anyValueSize(16)] interface IInterface @@ -10,8 +10,7 @@ interface IInterface //TEST_INPUT:ubuffer(data=[0 0 0 0], stride=4):out,name=gOutputBuffer RWStructuredBuffer<uint> gOutputBuffer; -//TEST_INPUT: Texture2D(size=8, content = one):name t2D,bindless -//TEST_INPUT:ubuffer(data=[rtti(MyImpl) witness(MyImpl, IInterface) handle(t2D) 0 0], stride=4):name=gCb +//TEST_INPUT: set gCb = new StructuredBuffer<IInterface>{new MyImpl{Texture2D(size=8, content = one)}} StructuredBuffer<IInterface> gCb; [numthreads(4, 1, 1)] diff --git a/tests/compute/half-rw-texture-convert.slang b/tests/compute/half-rw-texture-convert.slang index 338f44454..161033637 100644 --- a/tests/compute/half-rw-texture-convert.slang +++ b/tests/compute/half-rw-texture-convert.slang @@ -19,7 +19,7 @@ // There's no simple way to describe either, so this test just confirms it outputs PTX that can // be executed, and unfortunately doesn't test if the write conversion actually *worked* -//TEST(compute):COMPARE_COMPUTE_EX:-cuda -compute -output-using-type -shaderobj -render-features half +//DISABLE_TEST(compute):COMPARE_COMPUTE_EX:-cuda -compute -output-using-type -shaderobj -render-features half //TEST_INPUT: RWTexture2D(format=R_Float16, size=4, content = one, mipMaps = 1):name rwt2D [format("r16f")] diff --git a/tests/compute/half-rw-texture-simple.slang b/tests/compute/half-rw-texture-simple.slang index c544ee713..95f32c7ce 100644 --- a/tests/compute/half-rw-texture-simple.slang +++ b/tests/compute/half-rw-texture-simple.slang @@ -9,7 +9,7 @@ // TODO(JS): Doesn't work on vk currently, because createTextureView not implemented on vk renderer //DIABLE_TEST(compute, vulkan):COMPARE_COMPUTE_EX:-vk -compute -output-using-type -shaderobj // TODO(JS): Doesn't work on certain CI systems. -//TEST(compute):COMPARE_COMPUTE_EX:-cuda -compute -output-using-type -shaderobj -render-features half +//DISABLE_TEST(compute):COMPARE_COMPUTE_EX:-cuda -compute -output-using-type -shaderobj -render-features half //TEST_INPUT: RWTexture2D(format=R_Float16, size=4, content = one, mipMaps = 1):name rwt2D RWTexture2D<half> rwt2D; diff --git a/tests/compute/interface-assoc-type-param.slang b/tests/compute/interface-assoc-type-param.slang index b315dd5f9..805f673a2 100644 --- a/tests/compute/interface-assoc-type-param.slang +++ b/tests/compute/interface-assoc-type-param.slang @@ -1,7 +1,7 @@ // Tests using associated types through an existential-struct-typed param. -//DISABLED_TEST(compute):COMPARE_COMPUTE_EX:-slang -compute -cuda -shaderobj -//DISABLED_TEST(compute):COMPARE_COMPUTE_EX:-slang -compute -cpu -shaderobj +//TEST(compute):COMPARE_COMPUTE_EX:-slang -compute -cuda -shaderobj +//TEST(compute):COMPARE_COMPUTE_EX:-slang -compute -cpu -shaderobj [anyValueSize(8)] interface IInterface @@ -16,7 +16,7 @@ interface IEval uint eval(); } -struct Impl : IInterface +public struct Impl : IInterface { uint val; struct TEval : IEval @@ -48,11 +48,9 @@ void compute(uint tid, Params p) gOutputBuffer[tid] = p.obj[0].getEval().eval(); } -//TEST_INPUT: entryPointExistentialType Impl - [numthreads(4, 1, 1)] void computeMain(uint3 dispatchThreadID : SV_DispatchThreadID, -//TEST_INPUT:ubuffer(data=[0 0 0 0 1 0], stride=4):name=params.obj +//TEST_INPUT:set params.obj = new StructuredBuffer<IInterface>{ new Impl{1}} uniform Params params) { uint tid = dispatchThreadID.x; diff --git a/tests/compute/interface-func-param-in-struct.slang b/tests/compute/interface-func-param-in-struct.slang index 9e3e6c201..c47b25d70 100644 --- a/tests/compute/interface-func-param-in-struct.slang +++ b/tests/compute/interface-func-param-in-struct.slang @@ -1,7 +1,7 @@ // Tests specializing a function with existential-struct-typed param. -//DISABLED_TEST(compute):COMPARE_COMPUTE_EX:-slang -compute -cuda -shaderobj -//DISABLED_TEST(compute):COMPARE_COMPUTE_EX:-slang -compute -cpu -shaderobj +//TEST(compute):COMPARE_COMPUTE_EX:-slang -compute -cuda -shaderobj +//TEST(compute):COMPARE_COMPUTE_EX:-slang -compute -cpu -shaderobj [anyValueSize(8)] interface IInterface @@ -9,7 +9,7 @@ interface IInterface uint eval(); } -struct Impl : IInterface +public struct Impl : IInterface { uint val; uint eval() @@ -33,7 +33,7 @@ void compute(uint tid, Params p) [numthreads(4, 1, 1)] void computeMain(uint3 dispatchThreadID : SV_DispatchThreadID, -//TEST_INPUT:ubuffer(data=[0 0 0 0 1 0], stride=4):name=params.obj +//TEST_INPUT:set params.obj = new StructuredBuffer<IInterface>{new Impl{1}} uniform Params params) { uint tid = dispatchThreadID.x; diff --git a/tests/disabled-tests.txt b/tests/disabled-tests.txt index ea1a2330e..e588c153d 100644 --- a/tests/disabled-tests.txt +++ b/tests/disabled-tests.txt @@ -19,16 +19,6 @@ like it currently does. * compute/dynamic-dispatch-12.slang -### `StructuredBuffer<ISomething>` - -These tests require support for structured buffers where the element type either is an interface type or transitively contains one. - -* compute/dynamic-dispatch-13.slang -* compute/dynamic-dispatch-14.slang -* compute/dynamic-dispatch-bindless-texture.slang -* compute/interface-func-param-in-struct.slang -* compute/interface-assoc-type-param.slang - ### Generic Specialization Parameters These tests make use of generic specialization parameters in ways that don't easily align with the implementation approach that is more focused on existential parameters. @@ -52,6 +42,14 @@ They will need to wait until the shader object implementation(s) are updated to * compute/interface-shader-param-in-struct.slang * compute/interface-shader-param-legalization.slang +### Gfx Limitation + +These tests are disabled due to other limitations of gfx layer. + +* compute/half-rw-texture-convert.slang +* compute/half-rw-texture-simple.slang + + ### Uncategorized These tests need to be binned according to what features they need. diff --git a/tools/gfx-util/shader-cursor.cpp b/tools/gfx-util/shader-cursor.cpp index b188901ec..afb1540d5 100644 --- a/tools/gfx-util/shader-cursor.cpp +++ b/tools/gfx-util/shader-cursor.cpp @@ -144,7 +144,18 @@ Result ShaderCursor::getField(const char* name, const char* nameEnd, ShaderCurso ShaderCursor ShaderCursor::getElement(SlangInt index) const { - // TODO: need to auto-dereference various buffer types... + if (m_containerType != ShaderObjectContainerType::None) + { + ShaderCursor elementCursor; + elementCursor.m_baseObject = m_baseObject; + elementCursor.m_typeLayout = m_typeLayout->getElementTypeLayout(); + elementCursor.m_containerType = m_containerType; + elementCursor.m_offset.uniformOffset = index * m_typeLayout->getStride(); + elementCursor.m_offset.bindingRangeIndex = 0; + elementCursor.m_offset.bindingArrayIndex = index; + return elementCursor; + } + switch( m_typeLayout->getKind() ) { case slang::TypeReflection::Kind::Array: diff --git a/tools/gfx-util/shader-cursor.h b/tools/gfx-util/shader-cursor.h index 24008cf24..7512c62ee 100644 --- a/tools/gfx-util/shader-cursor.h +++ b/tools/gfx-util/shader-cursor.h @@ -26,6 +26,7 @@ struct ShaderCursor { IShaderObject* m_baseObject = nullptr; slang::TypeLayoutReflection* m_typeLayout = nullptr; + ShaderObjectContainerType m_containerType = ShaderObjectContainerType::None; ShaderOffset m_offset; /// Get the type (layout) of the value being pointed at by the cursor @@ -78,6 +79,7 @@ struct ShaderCursor ShaderCursor(IShaderObject* object) : m_baseObject(object) , m_typeLayout(object->getElementTypeLayout()) + , m_containerType(object->getContainerType()) {} SlangResult setData(void const* data, size_t size) const @@ -116,9 +118,13 @@ struct ShaderCursor /// Produce a cursor to the element or field with the given `index`. /// /// This is a convenience wrapper around `getElement()`. - ShaderCursor operator[](SlangInt index) const - { - return getElement(index); - } + ShaderCursor operator[](int64_t index) const { return getElement((SlangInt)index); } + ShaderCursor operator[](uint64_t index) const { return getElement((SlangInt)index); } + ShaderCursor operator[](int32_t index) const { return getElement((SlangInt)index); } + ShaderCursor operator[](uint32_t index) const { return getElement((SlangInt)index); } + ShaderCursor operator[](int16_t index) const { return getElement((SlangInt)index); } + ShaderCursor operator[](uint16_t index) const { return getElement((SlangInt)index); } + ShaderCursor operator[](int8_t index) const { return getElement((SlangInt)index); } + ShaderCursor operator[](uint8_t index) const { return getElement((SlangInt)index); } }; } diff --git a/tools/gfx/cpu/render-cpu.cpp b/tools/gfx/cpu/render-cpu.cpp index 0bdc06ad6..0c5f119b8 100644 --- a/tools/gfx/cpu/render-cpu.cpp +++ b/tools/gfx/cpu/render-cpu.cpp @@ -357,7 +357,7 @@ public: void* m_data = nullptr; }; -class CPUResourceView : public IResourceView, public ComObject +class CPUResourceView : public ResourceViewBase { public: enum class Kind @@ -365,15 +365,6 @@ public: Buffer, Texture, }; - - SLANG_COM_OBJECT_IUNKNOWN_ALL - IResourceView* getInterface(const Guid& guid) - { - if (guid == GfxGUID::IID_ISlangUnknown || guid == GfxGUID::IID_IResourceView) - return static_cast<IResourceView*>(this); - return nullptr; - } - Kind getViewKind() const { return m_kind; } Desc const& getDesc() const { return m_desc; } @@ -576,6 +567,7 @@ public: slang::BindingType bindingType; Index count; Index baseIndex; // Flat index for sub-ojects + Index subObjectIndex; // TODO: The `uniformOffset` field should be removed, // since it cannot be supported by the Slang reflection @@ -610,10 +602,10 @@ public: { initBase(renderer, layout); - Index subObjectCount = 0; - Index resourceCount = 0; + m_subObjectCount = 0; + m_resourceCount = 0; - m_elementTypeLayout = _unwrapParameterGroups(layout); + m_elementTypeLayout = _unwrapParameterGroups(layout, m_containerType); m_size = m_elementTypeLayout->getSize(); // Compute the binding ranges that are used to store @@ -645,18 +637,31 @@ public: descriptorSetIndex, rangeIndexInDescriptorSet); Index baseIndex = 0; + Index subObjectIndex = 0; switch (slangBindingType) { case slang::BindingType::ConstantBuffer: case slang::BindingType::ParameterBlock: case slang::BindingType::ExistentialValue: - baseIndex = subObjectCount; - subObjectCount += count; + baseIndex = m_subObjectCount; + subObjectIndex = baseIndex; + m_subObjectCount += count; + break; + case slang::BindingType::RawBuffer: + case slang::BindingType::MutableRawBuffer: + if (slangLeafTypeLayout->getType()->getElementType() != nullptr) + { + // A structured buffer occupies both a resource slot and + // a sub-object slot. + subObjectIndex = m_subObjectCount; + m_subObjectCount += count; + } + baseIndex = m_resourceCount; + m_resourceCount += count; break; - default: - baseIndex = resourceCount; - resourceCount += count; + baseIndex = m_resourceCount; + m_resourceCount += count; break; } @@ -665,12 +670,10 @@ public: bindingRangeInfo.count = count; bindingRangeInfo.baseIndex = baseIndex; bindingRangeInfo.uniformOffset = uniformOffset; + bindingRangeInfo.subObjectIndex = subObjectIndex; m_bindingRanges.add(bindingRangeInfo); } - m_subObjectCount = subObjectCount; - m_resourceCount = resourceCount; - SlangInt subObjectRangeCount = m_elementTypeLayout->getSubObjectRangeCount(); for (SlangInt r = 0; r < subObjectRangeCount; ++r) { @@ -703,6 +706,9 @@ public: size_t getSize() { return m_size; } Index getResourceCount() const { return m_resourceCount; } Index getSubObjectCount() const { return m_subObjectCount; } + List<SubObjectRangeInfo>& getSubObjectRanges() { return subObjectRanges; } + BindingRangeInfo getBindingRange(Index index) { return m_bindingRanges[index]; } + Index getBindingRangeCount() const { return m_bindingRanges.getCount(); } }; class CPUEntryPointLayout : public CPUShaderObjectLayout @@ -763,32 +769,64 @@ public: CPUEntryPointLayout* getEntryPoint(Index index) { return m_entryPointLayouts[index]; } }; -class CPUShaderObject : public ShaderObjectBase +class CPUShaderObjectData { public: - void* m_data = nullptr; + Slang::List<char> m_ordinaryData; + // Any "ordinary" / uniform data for this object + Slang::RefPtr<CPUBufferResource> m_bufferResource; + Slang::RefPtr<CPUBufferView> m_bufferView; - ~CPUShaderObject() + Index getCount() { return m_ordinaryData.getCount(); } + void setCount(Index count) { m_ordinaryData.setCount(count); } + char* getBuffer() { return m_ordinaryData.getBuffer(); } + + ~CPUShaderObjectData() { - free(m_data); + // m_bufferResource's data is managed by m_ordinaryData so we + // set it to null to prevent m_bufferResource from freeing it. + if (m_bufferResource) + m_bufferResource->m_data = nullptr; + } + + /// Returns a StructuredBuffer resource view for GPU access into the buffer content. + /// Creates a StructuredBuffer resource if it has not been created. + ResourceViewBase* getResourceView( + RendererBase* device, + slang::TypeLayoutReflection* elementLayout, + slang::BindingType bindingType) + { + SLANG_UNUSED(device); + if (!m_bufferResource) + { + IBufferResource::Desc desc = {}; + desc.type = IResource::Type::Buffer; + desc.elementSize = (int)elementLayout->getSize(); + m_bufferResource = new CPUBufferResource(desc); + + IResourceView::Desc viewDesc = {}; + viewDesc.type = IResourceView::Type::UnorderedAccess; + viewDesc.format = Format::Unknown; + m_bufferView = new CPUBufferView(viewDesc, m_bufferResource); + + } + m_bufferResource->getDesc()->sizeInBytes = m_ordinaryData.getCount(); + m_bufferResource->m_data = m_ordinaryData.getBuffer(); + return m_bufferView.Ptr(); } +}; + +class CPUShaderObject + : public ShaderObjectBaseImpl<CPUShaderObject, CPUShaderObjectLayout, CPUShaderObjectData> +{ + typedef ShaderObjectBaseImpl<CPUShaderObject, CPUShaderObjectLayout, CPUShaderObjectData> Super; - List<RefPtr<CPUShaderObject>> m_objects; +public: List<RefPtr<CPUResourceView>> m_resources; virtual SLANG_NO_THROW Result SLANG_MCALL init(IDevice* device, CPUShaderObjectLayout* typeLayout); - CPUShaderObjectLayout* getLayout() - { - return static_cast<CPUShaderObjectLayout*>(m_layout.Ptr()); - } - - virtual SLANG_NO_THROW slang::TypeLayoutReflection* SLANG_MCALL getElementTypeLayout() override - { - return getLayout()->getElementTypeLayout(); - } - virtual SLANG_NO_THROW UInt SLANG_MCALL getEntryPointCount() override { return 0; } virtual SLANG_NO_THROW Result SLANG_MCALL getEntryPoint(UInt index, IShaderObject** outEntryPoint) override @@ -799,143 +837,8 @@ public: virtual SLANG_NO_THROW Result SLANG_MCALL setData(ShaderOffset const& offset, void const* data, size_t size) override { - size = Math::Min(size, getLayout()->getSize() - offset.uniformOffset); - memcpy((char*)m_data + offset.uniformOffset, data, size); - return SLANG_OK; - } - virtual SLANG_NO_THROW Result SLANG_MCALL getObject( - ShaderOffset const& offset, - IShaderObject** outObject) override - { - auto layout = getLayout(); - - auto bindingRangeIndex = offset.bindingRangeIndex; - SLANG_ASSERT(bindingRangeIndex >= 0); - SLANG_ASSERT(bindingRangeIndex < layout->m_bindingRanges.getCount()); - - auto& bindingRange = layout->m_bindingRanges[bindingRangeIndex]; - auto subObjectIndex = bindingRange.baseIndex + offset.bindingArrayIndex; - auto& subObject = m_objects[subObjectIndex]; - - returnComPtr(outObject, subObject); - - return SLANG_OK; - } - virtual SLANG_NO_THROW Result SLANG_MCALL setObject( - ShaderOffset const& offset, - IShaderObject* object) override - { - auto layout = getLayout(); - - auto bindingRangeIndex = offset.bindingRangeIndex; - SLANG_ASSERT(bindingRangeIndex >= 0); - SLANG_ASSERT(bindingRangeIndex < layout->m_bindingRanges.getCount()); - - auto& bindingRange = layout->m_bindingRanges[bindingRangeIndex]; - auto subObjectIndex = bindingRange.baseIndex + offset.bindingArrayIndex; - - CPUShaderObject* subObject = static_cast<CPUShaderObject*>(object); - m_objects[subObjectIndex] = subObject; - - switch( bindingRange.bindingType ) - { - default: - SLANG_RETURN_ON_FAIL(setData(offset, &subObject->m_data, sizeof(void*))); - break; - - // If the range being assigned into represents an interface/existential-type leaf field, - // then we need to consider how the `object` being assigned here affects specialization. - // We may also need to assign some data from the sub-object into the ordinary data - // buffer for the parent object. - // - case slang::BindingType::ExistentialValue: - { - auto renderer = getRenderer(); - - ComPtr<slang::ISession> slangSession; - SLANG_RETURN_ON_FAIL(renderer->getSlangSession(slangSession.writeRef())); - - // A leaf field of interface type is laid out inside of the parent object - // as a tuple of `(RTTI, WitnessTable, Payload)`. The layout of these fields - // is a contract between the compiler and any runtime system, so we will - // need to rely on details of the binary layout. - - // We start by querying the layout/type of the concrete value that the application - // is trying to store into the field, and also the layout/type of the leaf - // existential-type field itself. - // - auto concreteTypeLayout = subObject->getElementTypeLayout(); - auto concreteType = concreteTypeLayout->getType(); - // - auto existentialTypeLayout = layout->getElementTypeLayout()->getBindingRangeLeafTypeLayout(bindingRangeIndex); - auto existentialType = existentialTypeLayout->getType(); - - // The first field of the tuple (offset zero) is the run-time type information (RTTI) - // ID for the concrete type being stored into the field. - // - // TODO: We need to be able to gather the RTTI type ID from `object` and then - // use `setData(offset, &TypeID, sizeof(TypeID))`. - - // The second field of the tuple (offset 8) is the ID of the "witness" for the - // conformance of the concrete type to the interface used by this field. - // - auto witnessTableOffset = offset; - witnessTableOffset.uniformOffset += 8; - // - // Conformances of a type to an interface are computed and then stored by the - // Slang runtime, so we can look up the ID for this particular conformance (which - // will create it on demand). - // - // Note: If the type doesn't actually conform to the required interface for - // this sub-object range, then this is the point where we will detect that - // fact and error out. - // - uint32_t conformanceID = 0xFFFFFFFF; - SLANG_RETURN_ON_FAIL(slangSession->getTypeConformanceWitnessSequentialID( - concreteType, existentialType, &conformanceID)); - // - // Once we have the conformance ID, then we can write it into the object - // at the required offset. - // - SLANG_RETURN_ON_FAIL(setData(witnessTableOffset, &conformanceID, sizeof(conformanceID))); - - // The third field of the tuple (offset 16) is the "payload" that is supposed to - // hold the data for a value of the given concrete type. - // - auto payloadOffset = offset; - payloadOffset.uniformOffset += 16; - - // There are two cases we need to consider here for how the payload might be used: - // - // * If the concrete type of the value being bound is one that can "fit" into the - // available payload space, then it should be stored in the payload. - // - // * If the concrete type of the value cannot fit in the payload space, then it - // will need to be stored somewhere else. - // - if(_doesValueFitInExistentialPayload(concreteTypeLayout, existentialTypeLayout)) - { - // If the value can fit in the payload area, then we will go ahead and copy - // its bytes into that area. - // - auto valueSize = concreteTypeLayout->getSize(); - SLANG_RETURN_ON_FAIL(setData(payloadOffset, subObject->m_data, valueSize)); - } - else - { - // If the value cannot fit in the payload area, then we will pass a pointer - // to the sub-object instead. - // - // Note: The Slang compiler does not currently emit code that handles the - // pointer case, but that is the expected implementation for values - // that do not fit into the fixed-size payload. - // - SLANG_RETURN_ON_FAIL(setData(payloadOffset, &subObject->m_data, sizeof(void*))); - } - } - break; - } - + size = Math::Min(size, (size_t)m_data.getCount() - offset.uniformOffset); + memcpy((char*)m_data.getBuffer() + offset.uniformOffset, data, size); return SLANG_OK; } virtual SLANG_NO_THROW Result SLANG_MCALL @@ -989,6 +892,31 @@ public: return SLANG_OK; } virtual SLANG_NO_THROW Result SLANG_MCALL + setObject(ShaderOffset const& offset, IShaderObject* object) override + { + SLANG_RETURN_ON_FAIL(Super::setObject(offset, object)); + + auto bindingRangeIndex = offset.bindingRangeIndex; + auto& bindingRange = getLayout()->m_bindingRanges[bindingRangeIndex]; + + CPUShaderObject* subObject = static_cast<CPUShaderObject*>(object); + + switch (bindingRange.bindingType) + { + default: + { + void* bufferPtr = subObject->m_data.getBuffer(); + SLANG_RETURN_ON_FAIL(setData(offset, &bufferPtr, sizeof(void*))); + } + break; + case slang::BindingType::ExistentialValue: + case slang::BindingType::RawBuffer: + case slang::BindingType::MutableRawBuffer: + break; + } + return SLANG_OK; + } + virtual SLANG_NO_THROW Result SLANG_MCALL setSampler(ShaderOffset const& offset, ISamplerState* sampler) override { SLANG_UNUSED(sampler); @@ -1003,52 +931,7 @@ public: return SLANG_OK; } - // Appends all types that are used to specialize the element type of this shader object in `args` list. - virtual Result collectSpecializationArgs(ExtendedShaderObjectTypeList& args) override - { - // TODO: the logic here is a copy-paste of `GraphicsCommonShaderObject::collectSpecializationArgs`, - // consider moving the implementation to `ShaderObjectBase` and share the logic among different implementations. - - auto& subObjectRanges = getLayout()->subObjectRanges; - // The following logic is built on the assumption that all fields that involve existential types (and - // therefore require specialization) will results in a sub-object range in the type layout. - // This allows us to simply scan the sub-object ranges to find out all specialization arguments. - for (Index subObjIndex = 0; subObjIndex < subObjectRanges.getCount(); subObjIndex++) - { - // Retrieve the corresponding binding range of the sub object. - auto bindingRange = getLayout()->m_bindingRanges[subObjectRanges[subObjIndex].bindingRangeIndex]; - switch (bindingRange.bindingType) - { - case slang::BindingType::ExistentialValue: - { - // A binding type of `ExistentialValue` means the sub-object represents a interface-typed field. - // In this case the specialization argument for this field is the actual specialized type of the bound - // shader object. If the shader object's type is an ordinary type without existential fields, then the - // type argument will simply be the ordinary type. But if the sub object's type is itself a specialized - // type, we need to make sure to use that type as the specialization argument. - - // TODO: need to implement the case where the field is an array of existential values. - ExtendedShaderObjectType specializedSubObjType; - SLANG_RETURN_ON_FAIL(m_objects[subObjIndex]->getSpecializedShaderObjectType(&specializedSubObjType)); - args.add(specializedSubObjType); - break; - } - case slang::BindingType::ParameterBlock: - case slang::BindingType::ConstantBuffer: - // Currently we only handle the case where the field's type is - // `ParameterBlock<SomeStruct>` or `ConstantBuffer<SomeStruct>`, where `SomeStruct` is a struct type - // (not directly an interface type). In this case, we just recursively collect the specialization arguments - // from the bound sub object. - SLANG_RETURN_ON_FAIL(m_objects[subObjIndex]->collectSpecializationArgs(args)); - // TODO: we need to handle the case where the field is of the form `ParameterBlock<IFoo>`. We should treat - // this case the same way as the `ExistentialValue` case here, but currently we lack a mechanism to distinguish - // the two scenarios. - break; - } - // TODO: need to handle another case where specialization happens on resources fields e.g. `StructuredBuffer<IFoo>`. - } - return SLANG_OK; - } + char* getDataBuffer() { return m_data.getBuffer(); } }; class CPUEntryPointShaderObject : public CPUShaderObject @@ -1173,8 +1056,8 @@ private: varyingInput.endGroupID.y = y; varyingInput.endGroupID.z = z; - auto globalParamsData = m_currentRootObject->m_data; - auto entryPointParamsData = entryPointObject->m_data; + auto globalParamsData = m_currentRootObject->getDataBuffer(); + auto entryPointParamsData = entryPointObject->getDataBuffer(); func(&varyingInput, entryPointParamsData, globalParamsData); } @@ -1379,10 +1262,7 @@ SlangResult CPUShaderObject::init(IDevice* device, CPUShaderObjectLayout* typeLa // auto slangLayout = getLayout()->getElementTypeLayout(); size_t uniformSize = slangLayout->getSize(); - if (uniformSize) - { - m_data = malloc(uniformSize); - } + m_data.setCount(uniformSize); // If the layout specifies that we have any resources or sub-objects, // then we need to size the appropriate arrays to account for them. @@ -1405,6 +1285,8 @@ SlangResult CPUShaderObject::init(IDevice* device, CPUShaderObjectLayout* typeLa // if (!subObjectLayout) continue; + auto _debugname = subObjectLayout->getElementTypeLayout()->getName(); + // // Otherwise, we will allocate a sub-object to fill // in each entry in this range, based on the layout diff --git a/tools/gfx/cuda/render-cuda.cpp b/tools/gfx/cuda/render-cuda.cpp index b919ac6b0..f60be8eda 100644 --- a/tools/gfx/cuda/render-cuda.cpp +++ b/tools/gfx/cuda/render-cuda.cpp @@ -70,7 +70,8 @@ struct CUDAErrorInfo builder << m_errorString; } - StdWriters::getError().put(builder.getUnownedSlice()); + getDebugCallback()->handleMessage(DebugMessageType::Error, DebugMessageSource::Driver, + builder.getUnownedSlice().begin()); // Slang::signalUnexpectedError(builder.getBuffer()); return SLANG_FAIL; @@ -82,7 +83,6 @@ struct CUDAErrorInfo const char* m_errorString; }; -# if 1 // If this code path is enabled, CUDA errors will be reported directly to StdWriter::out stream. static SlangResult _handleCUDAError(CUresult cuResult, const char* file, int line) @@ -98,27 +98,7 @@ static SlangResult _handleCUDAError(cudaError_t error, const char* file, int lin return CUDAErrorInfo(file, line, cudaGetErrorName(error), cudaGetErrorString(error)).handle(); } -# define SLANG_CUDA_HANDLE_ERROR(x) _handleCUDAError(_res, __FILE__, __LINE__) - -# else -// If this code path is enabled, errors are not reported, but can have an assert enabled - -static SlangResult _handleCUDAError(CUresult cuResult) -{ - SLANG_UNUSED(cuResult); - // SLANG_ASSERT(!"Failed CUDA call"); - return SLANG_FAIL; -} - -static SlangResult _handleCUDAError(cudaError_t error) -{ - SLANG_UNUSED(error); - // SLANG_ASSERT(!"Failed CUDA call"); - return SLANG_FAIL; -} - -# define SLANG_CUDA_HANDLE_ERROR(x) _handleCUDAError(_res) -# endif +# define SLANG_CUDA_HANDLE_ERROR(x) _handleCUDAError(_res, __FILE__, __LINE__) # define SLANG_CUDA_RETURN_ON_FAIL(x) \ { \ @@ -251,20 +231,13 @@ public: RefPtr<CUDAContext> m_cudaContext; }; -class CUDAResourceView : public IResourceView, public ComObject +class CUDAResourceView : public ResourceViewBase { public: - SLANG_COM_OBJECT_IUNKNOWN_ALL - IResourceView* getInterface(const Guid& guid) - { - if (guid == GfxGUID::IID_ISlangUnknown || guid == GfxGUID::IID_IResourceView) - return static_cast<IResourceView*>(this); - return nullptr; - } -public: Desc desc; RefPtr<MemoryCUDAResource> memoryResource = nullptr; RefPtr<TextureCUDAResource> textureResource = nullptr; + void* proxyBuffer = nullptr; }; class CUDAShaderObjectLayout : public ShaderObjectLayoutBase @@ -275,6 +248,7 @@ public: slang::BindingType bindingType; Index count; Index baseIndex; // Flat index for sub-ojects + Index subObjectIndex; // TODO: The `uniformOffset` field should be removed, // since it cannot be supported by the Slang reflection @@ -306,12 +280,9 @@ public: CUDAShaderObjectLayout(RendererBase* renderer, slang::TypeLayoutReflection* layout) { - initBase(renderer, layout); - - Index subObjectCount = 0; - Index resourceCount = 0; + m_elementTypeLayout = _unwrapParameterGroups(layout, m_containerType); - m_elementTypeLayout = _unwrapParameterGroups(layout); + initBase(renderer, m_elementTypeLayout); // Compute the binding ranges that are used to store // the logical contents of the object in memory. These will relate @@ -342,18 +313,31 @@ public: descriptorSetIndex, rangeIndexInDescriptorSet); Index baseIndex = 0; + Index subObjectIndex = 0; switch (slangBindingType) { case slang::BindingType::ConstantBuffer: case slang::BindingType::ParameterBlock: case slang::BindingType::ExistentialValue: - baseIndex = subObjectCount; - subObjectCount += count; + baseIndex = m_subObjectCount; + subObjectIndex = baseIndex; + m_subObjectCount += count; + break; + case slang::BindingType::RawBuffer: + case slang::BindingType::MutableRawBuffer: + if (slangLeafTypeLayout->getType()->getElementType() != nullptr) + { + // A structured buffer occupies both a resource slot and + // a sub-object slot. + subObjectIndex = m_subObjectCount; + m_subObjectCount += count; + } + baseIndex = m_resourceCount; + m_resourceCount += count; break; - default: - baseIndex = resourceCount; - resourceCount += count; + baseIndex = m_resourceCount; + m_resourceCount += count; break; } @@ -362,12 +346,10 @@ public: bindingRangeInfo.count = count; bindingRangeInfo.baseIndex = baseIndex; bindingRangeInfo.uniformOffset = uniformOffset; + bindingRangeInfo.subObjectIndex = subObjectIndex; m_bindingRanges.add(bindingRangeInfo); } - m_subObjectCount = subObjectCount; - m_resourceCount = resourceCount; - SlangInt subObjectRangeCount = m_elementTypeLayout->getSubObjectRangeCount(); for (SlangInt r = 0; r < subObjectRangeCount; ++r) { @@ -399,6 +381,9 @@ public: Index getResourceCount() const { return m_resourceCount; } Index getSubObjectCount() const { return m_subObjectCount; } + List<SubObjectRangeInfo>& getSubObjectRanges() { return subObjectRanges; } + BindingRangeInfo getBindingRange(Index index) { return m_bindingRanges[index]; } + Index getBindingRangeCount() const { return m_bindingRanges.getCount(); } }; class CUDAProgramLayout : public CUDAShaderObjectLayout @@ -439,50 +424,108 @@ public: } }; -class CUDAShaderObject : public ShaderObjectBase +class CUDAShaderObjectData { public: - RefPtr<MemoryCUDAResource> bufferResource; - List<RefPtr<CUDAShaderObject>> objects; - List<RefPtr<CUDAResourceView>> resources; - - virtual SLANG_NO_THROW Result SLANG_MCALL - init(IDevice* device, CUDAShaderObjectLayout* typeLayout); - - CUDAShaderObjectLayout* getLayout() + bool isHostOnly = false; + Slang::RefPtr<MemoryCUDAResource> m_bufferResource; + Slang::RefPtr<CUDAResourceView> m_bufferView; + Slang::List<uint8_t> m_cpuBuffer; + void setCount(Index count) { - return static_cast<CUDAShaderObjectLayout*>(m_layout.Ptr()); - } + if (isHostOnly) + { + m_cpuBuffer.setCount(count); + if (!m_bufferView) + { + IResourceView::Desc viewDesc = {}; + viewDesc.type = IResourceView::Type::UnorderedAccess; + m_bufferView = new CUDAResourceView(); + m_bufferView->proxyBuffer = m_cpuBuffer.getBuffer(); + m_bufferView->desc = viewDesc; + } + return; + } - virtual SLANG_NO_THROW Result SLANG_MCALL initBuffer(IDevice* device, size_t bufferSize) - { - BufferResource::Desc bufferDesc; - bufferDesc.type = IResource::Type::Buffer; - bufferDesc.defaultState = ResourceState::ConstantBuffer; - bufferDesc.allowedStates = - ResourceStateSet(ResourceState::ConstantBuffer, ResourceState::CopyDestination); - bufferDesc.sizeInBytes = bufferSize; - bufferDesc.cpuAccessFlags |= IResource::AccessFlag::Write; - ComPtr<IBufferResource> constantBuffer; - SLANG_RETURN_ON_FAIL(device->createBufferResource(bufferDesc, nullptr, constantBuffer.writeRef())); - bufferResource = static_cast<MemoryCUDAResource*>(constantBuffer.get()); - return SLANG_OK; + if (!m_bufferResource) + { + IBufferResource::Desc desc; + desc.type = IResource::Type::Buffer; + desc.sizeInBytes = count; + m_bufferResource = new MemoryCUDAResource(desc); + if (count) + cudaMalloc(&m_bufferResource->m_cudaMemory, (size_t)count); + IResourceView::Desc viewDesc = {}; + viewDesc.type = IResourceView::Type::UnorderedAccess; + m_bufferView = new CUDAResourceView(); + m_bufferView->memoryResource = m_bufferResource; + m_bufferView->desc = viewDesc; + } + auto oldSize = m_bufferResource->getDesc()->sizeInBytes; + if ((size_t)count != oldSize) + { + void* newMemory = nullptr; + if (count) + { + cudaMalloc(&newMemory, (size_t)count); + } + if (oldSize) + { + cudaMemcpy( + newMemory, + m_bufferResource->m_cudaMemory, + Math::Min((size_t)count, oldSize), + cudaMemcpyDefault); + } + cudaFree(m_bufferResource->m_cudaMemory); + m_bufferResource->m_cudaMemory = newMemory; + m_bufferResource->getDesc()->sizeInBytes = count; + } } - virtual SLANG_NO_THROW void* SLANG_MCALL getBuffer() + Slang::Index getCount() { - return bufferResource ? bufferResource->m_cudaMemory : nullptr; + if (isHostOnly) + return m_cpuBuffer.getCount(); + if (m_bufferResource) + return (Slang::Index)(m_bufferResource->getDesc()->sizeInBytes); + else + return 0; } - virtual SLANG_NO_THROW size_t SLANG_MCALL getBufferSize() + void* getBuffer() { - return bufferResource ? bufferResource->getDesc()->sizeInBytes : 0; + if (isHostOnly) + return m_cpuBuffer.getBuffer(); + + if (m_bufferResource) + return m_bufferResource->m_cudaMemory; + return nullptr; } - virtual SLANG_NO_THROW slang::TypeLayoutReflection* SLANG_MCALL getElementTypeLayout() override + /// Returns a resource view for GPU access into the buffer content. + ResourceViewBase* getResourceView( + RendererBase* device, + slang::TypeLayoutReflection* elementLayout, + slang::BindingType bindingType) { - return getLayout()->getElementTypeLayout(); + SLANG_UNUSED(device); + m_bufferResource->getDesc()->elementSize = (int)elementLayout->getSize(); + return m_bufferView.Ptr(); } +}; + +class CUDAShaderObject + : public ShaderObjectBaseImpl<CUDAShaderObject, CUDAShaderObjectLayout, CUDAShaderObjectData> +{ + typedef ShaderObjectBaseImpl<CUDAShaderObject, CUDAShaderObjectLayout, CUDAShaderObjectData> + Super; + +public: + List<RefPtr<CUDAResourceView>> resources; + + virtual SLANG_NO_THROW Result SLANG_MCALL + init(IDevice* device, CUDAShaderObjectLayout* typeLayout); virtual SLANG_NO_THROW UInt SLANG_MCALL getEntryPointCount() override { return 0; } virtual SLANG_NO_THROW Result SLANG_MCALL @@ -494,164 +537,17 @@ public: virtual SLANG_NO_THROW Result SLANG_MCALL setData(ShaderOffset const& offset, void const* data, size_t size) override { - size = Math::Min(size, bufferResource->getDesc()->sizeInBytes - offset.uniformOffset); + size = Math::Min(size, (size_t)m_data.getCount() - offset.uniformOffset); SLANG_CUDA_RETURN_ON_FAIL(cudaMemcpy( - (uint8_t*)bufferResource->m_cudaMemory + offset.uniformOffset, - data, - size, - cudaMemcpyHostToDevice)); + (uint8_t*)m_data.getBuffer() + offset.uniformOffset, data, size, cudaMemcpyDefault)); return SLANG_OK; } virtual SLANG_NO_THROW Result SLANG_MCALL - setDeviceData(size_t offset, void* data, size_t size) - { - size = Math::Min(size, bufferResource->getDesc()->sizeInBytes - offset); - SLANG_CUDA_RETURN_ON_FAIL(cudaMemcpy( - (uint8_t*)bufferResource->m_cudaMemory + offset, - data, - size, - cudaMemcpyHostToDevice)); - return SLANG_OK; - } - virtual SLANG_NO_THROW Result SLANG_MCALL - getObject(ShaderOffset const& offset, IShaderObject** object) override + setResource(ShaderOffset const& offset, IResourceView* resourceView) override { - auto subObjectIndex = - getLayout()->m_bindingRanges[offset.bindingRangeIndex].baseIndex + offset.bindingArrayIndex; - - SLANG_ASSERT(subObjectIndex < objects.getCount()); - if(subObjectIndex >= objects.getCount()) - return SLANG_E_INVALID_ARG; - - if (subObjectIndex >= objects.getCount()) - { - *object = nullptr; + if (!resourceView) return SLANG_OK; - } - returnComPtr(object, objects[subObjectIndex]); - return SLANG_OK; - } - virtual SLANG_NO_THROW Result SLANG_MCALL - setObject(ShaderOffset const& offset, IShaderObject* object) override - { - auto layout = getLayout(); - - auto bindingRangeIndex = offset.bindingRangeIndex; - SLANG_ASSERT(bindingRangeIndex >= 0); - SLANG_ASSERT(bindingRangeIndex < layout->m_bindingRanges.getCount()); - - auto& bindingRange = layout->m_bindingRanges[bindingRangeIndex]; - - auto subObjectIndex = bindingRange.baseIndex + offset.bindingArrayIndex; - auto subObject = dynamic_cast<CUDAShaderObject*>(object); - - // TODO: We should really not need to retain the objects here - objects[subObjectIndex] = subObject; - - switch( bindingRange.bindingType ) - { - default: - SLANG_RETURN_ON_FAIL(setData(offset, &subObject->bufferResource->m_cudaMemory, sizeof(void*))); - break; - - // If the range being assigned into represents an interface/existential-type leaf field, - // then we need to consider how the `object` being assigned here affects specialization. - // We may also need to assign some data from the sub-object into the ordinary data - // buffer for the parent object. - // - case slang::BindingType::ExistentialValue: - { - auto renderer = getRenderer(); - - ComPtr<slang::ISession> slangSession; - SLANG_RETURN_ON_FAIL(renderer->getSlangSession(slangSession.writeRef())); - - // A leaf field of interface type is laid out inside of the parent object - // as a tuple of `(RTTI, WitnessTable, Payload)`. The layout of these fields - // is a contract between the compiler and any runtime system, so we will - // need to rely on details of the binary layout. - - // We start by querying the layout/type of the concrete value that the application - // is trying to store into the field, and also the layout/type of the leaf - // existential-type field itself. - // - auto concreteTypeLayout = subObject->getElementTypeLayout(); - auto concreteType = concreteTypeLayout->getType(); - // - auto existentialTypeLayout = layout->getElementTypeLayout()->getBindingRangeLeafTypeLayout(bindingRangeIndex); - auto existentialType = existentialTypeLayout->getType(); - - // The first field of the tuple (offset zero) is the run-time type information (RTTI) - // ID for the concrete type being stored into the field. - // - // TODO: We need to be able to gather the RTTI type ID from `object` and then - // use `setData(offset, &TypeID, sizeof(TypeID))`. - - // The second field of the tuple (offset 8) is the ID of the "witness" for the - // conformance of the concrete type to the interface used by this field. - // - auto witnessTableOffset = offset; - witnessTableOffset.uniformOffset += 8; - // - // Conformances of a type to an interface are computed and then stored by the - // Slang runtime, so we can look up the ID for this particular conformance (which - // will create it on demand). - // - // Note: If the type doesn't actually conform to the required interface for - // this sub-object range, then this is the point where we will detect that - // fact and error out. - // - uint32_t conformanceID = 0xFFFFFFFF; - SLANG_RETURN_ON_FAIL(slangSession->getTypeConformanceWitnessSequentialID( - concreteType, existentialType, &conformanceID)); - // - // Once we have the conformance ID, then we can write it into the object - // at the required offset. - // - SLANG_RETURN_ON_FAIL(setData(witnessTableOffset, &conformanceID, sizeof(conformanceID))); - - // The third field of the tuple (offset 16) is the "payload" that is supposed to - // hold the data for a value of the given concrete type. - // - auto payloadOffset = offset; - payloadOffset.uniformOffset += 16; - - // There are two cases we need to consider here for how the payload might be used: - // - // * If the concrete type of the value being bound is one that can "fit" into the - // available payload space, then it should be stored in the payload. - // - // * If the concrete type of the value cannot fit in the payload space, then it - // will need to be stored somewhere else. - // - if(_doesValueFitInExistentialPayload(concreteTypeLayout, existentialTypeLayout)) - { - // If the value can fit in the payload area, then we will go ahead and copy - // its bytes into that area. - // - auto valueSize = concreteTypeLayout->getSize(); - SLANG_RETURN_ON_FAIL(setDeviceData(payloadOffset.uniformOffset, subObject->getBuffer(), valueSize)); - } - else - { - // If the value cannot fit in the payload area, then we will pass a pointer - // to the sub-object instead. - // - // Note: The Slang compiler does not currently emit code that handles the - // pointer case, but that is the expected implementation for values - // that do not fit into the fixed-size payload. - // - SLANG_RETURN_ON_FAIL(setData(payloadOffset, &subObject->bufferResource->m_cudaMemory, sizeof(void*))); - } - } - break; - } - return SLANG_OK; - } - virtual SLANG_NO_THROW Result SLANG_MCALL - setResource(ShaderOffset const& offset, IResourceView* resourceView) override - { auto layout = getLayout(); auto bindingRangeIndex = offset.bindingRangeIndex; @@ -678,7 +574,7 @@ public: setData(offset, &handle, sizeof(uint64_t)); } } - else + else if (cudaView->memoryResource) { auto handle = cudaView->memoryResource->getBindlessHandle(); setData(offset, &handle, sizeof(handle)); @@ -689,7 +585,42 @@ public: if (desc.elementSize > 1) size /= desc.elementSize; setData(sizeOffset, &size, sizeof(size)); + } + else if (cudaView->proxyBuffer) + { + auto handle = cudaView->proxyBuffer; + setData(offset, &handle, sizeof(handle)); + auto sizeOffset = offset; + sizeOffset.uniformOffset += sizeof(handle); + auto& desc = *cudaView->memoryResource->getDesc(); + size_t size = desc.sizeInBytes; + if (desc.elementSize > 1) + size /= desc.elementSize; + setData(sizeOffset, &size, sizeof(size)); + } + return SLANG_OK; + } + virtual SLANG_NO_THROW Result SLANG_MCALL + setObject(ShaderOffset const& offset, IShaderObject* object) override + { + SLANG_RETURN_ON_FAIL(Super::setObject(offset, object)); + auto bindingRangeIndex = offset.bindingRangeIndex; + auto& bindingRange = getLayout()->m_bindingRanges[bindingRangeIndex]; + + CUDAShaderObject* subObject = static_cast<CUDAShaderObject*>(object); + switch (bindingRange.bindingType) + { + default: + { + void* subObjectDataBuffer = subObject->getBuffer(); + SLANG_RETURN_ON_FAIL(setData(offset, &subObjectDataBuffer, sizeof(void*))); + } + break; + case slang::BindingType::ExistentialValue: + case slang::BindingType::RawBuffer: + case slang::BindingType::MutableRawBuffer: + break; } return SLANG_OK; } @@ -707,107 +638,12 @@ public: setResource(offset, textureView); return SLANG_OK; } - - // Appends all types that are used to specialize the element type of this shader object in `args` list. - virtual Result collectSpecializationArgs(ExtendedShaderObjectTypeList& args) override - { - // TODO: the logic here is a copy-paste of `GraphicsCommonShaderObject::collectSpecializationArgs`, - // consider moving the implementation to `ShaderObjectBase` and share the logic among different implementations. - - auto& subObjectRanges = getLayout()->subObjectRanges; - // The following logic is built on the assumption that all fields that involve existential types (and - // therefore require specialization) will results in a sub-object range in the type layout. - // This allows us to simply scan the sub-object ranges to find out all specialization arguments. - for (Index subObjIndex = 0; subObjIndex < subObjectRanges.getCount(); subObjIndex++) - { - // Retrieve the corresponding binding range of the sub object. - auto bindingRange = getLayout()->m_bindingRanges[subObjectRanges[subObjIndex].bindingRangeIndex]; - switch (bindingRange.bindingType) - { - case slang::BindingType::ExistentialValue: - { - // A binding type of `ExistentialValue` means the sub-object represents a interface-typed field. - // In this case the specialization argument for this field is the actual specialized type of the bound - // shader object. If the shader object's type is an ordinary type without existential fields, then the - // type argument will simply be the ordinary type. But if the sub object's type is itself a specialized - // type, we need to make sure to use that type as the specialization argument. - - // TODO: need to implement the case where the field is an array of existential values. - ExtendedShaderObjectType specializedSubObjType; - SLANG_RETURN_ON_FAIL(objects[subObjIndex]->getSpecializedShaderObjectType(&specializedSubObjType)); - args.add(specializedSubObjType); - break; - } - case slang::BindingType::ParameterBlock: - case slang::BindingType::ConstantBuffer: - // Currently we only handle the case where the field's type is - // `ParameterBlock<SomeStruct>` or `ConstantBuffer<SomeStruct>`, where `SomeStruct` is a struct type - // (not directly an interface type). In this case, we just recursively collect the specialization arguments - // from the bound sub object. - SLANG_RETURN_ON_FAIL(objects[subObjIndex]->collectSpecializationArgs(args)); - // TODO: we need to handle the case where the field is of the form `ParameterBlock<IFoo>`. We should treat - // this case the same way as the `ExistentialValue` case here, but currently we lack a mechanism to distinguish - // the two scenarios. - break; - } - // TODO: need to handle another case where specialization happens on resources fields e.g. `StructuredBuffer<IFoo>`. - } - return SLANG_OK; - } }; class CUDAEntryPointShaderObject : public CUDAShaderObject { public: - void* hostBuffer = nullptr; - size_t uniformBufferSize = 0; - // Override buffer allocation so we store all uniform data on host memory instead of device memory. - virtual SLANG_NO_THROW Result SLANG_MCALL initBuffer(IDevice* device, size_t bufferSize) override - { - SLANG_UNUSED(device); - uniformBufferSize = bufferSize; - hostBuffer = malloc(bufferSize); - return SLANG_OK; - } - - virtual SLANG_NO_THROW Result SLANG_MCALL - setData(ShaderOffset const& offset, void const* data, size_t size) override - { - size = Math::Min(size, uniformBufferSize - offset.uniformOffset); - memcpy( - (uint8_t*)hostBuffer + offset.uniformOffset, - data, - size); - return SLANG_OK; - } - - virtual SLANG_NO_THROW Result SLANG_MCALL - setDeviceData(size_t offset, void* data, size_t size) override - { - size = Math::Min(size, uniformBufferSize - offset); - SLANG_CUDA_RETURN_ON_FAIL(cudaMemcpy( - (uint8_t*)hostBuffer + offset, - data, - size, - cudaMemcpyDeviceToHost)); - return SLANG_OK; - } - - - virtual SLANG_NO_THROW void* SLANG_MCALL getBuffer() override - { - return hostBuffer; - } - - virtual SLANG_NO_THROW size_t SLANG_MCALL getBufferSize() override - { - return uniformBufferSize; - } - - ~CUDAEntryPointShaderObject() - { - free(hostBuffer); - } + CUDAEntryPointShaderObject() { m_data.isHostOnly = true; } }; class CUDARootShaderObject : public CUDAShaderObject @@ -1233,15 +1069,12 @@ public: currentPipeline->shaderProgram->cudaModule, "SLANG_globalParams"); - CUdeviceptr globalParamsCUDAData = - currentRootObject->bufferResource - ? (CUdeviceptr)currentRootObject->bufferResource->getBindlessHandle() - : 0; + CUdeviceptr globalParamsCUDAData = (CUdeviceptr)currentRootObject->getBuffer(); cudaMemcpyAsync( (void*)globalParamsSymbol, (void*)globalParamsCUDAData, globalParamsSymbolSize, - cudaMemcpyDeviceToDevice, + cudaMemcpyDefault, 0); } // @@ -1818,7 +1651,7 @@ public: SLANG_CUDA_RETURN_ON_FAIL(cudaMallocManaged(&resource->m_cudaMemory, desc.sizeInBytes)); if (initData) { - SLANG_CUDA_RETURN_ON_FAIL(cudaMemcpy(resource->m_cudaMemory, initData, desc.sizeInBytes, cudaMemcpyHostToDevice)); + SLANG_CUDA_RETURN_ON_FAIL(cudaMemcpy(resource->m_cudaMemory, initData, desc.sizeInBytes, cudaMemcpyDefault)); } returnComPtr(outResource, resource); return SLANG_OK; @@ -2080,7 +1913,7 @@ SlangResult CUDAShaderObject::init(IDevice* device, CUDAShaderObjectLayout* type size_t uniformSize = slangLayout->getSize(); if (uniformSize) { - initBuffer(device, uniformSize); + m_data.setCount((Index)uniformSize); } // If the layout specifies that we have any resources or sub-objects, @@ -2090,10 +1923,7 @@ SlangResult CUDAShaderObject::init(IDevice* device, CUDAShaderObjectLayout* type // and not just the number of resource/sub-object ranges. // resources.setCount(typeLayout->getResourceCount()); - objects.setCount(typeLayout->getSubObjectCount()); - - Index subObjectCount = slangLayout->getSubObjectRangeCount(); - objects.setCount(subObjectCount); + m_objects.setCount(typeLayout->getSubObjectCount()); for (auto subObjectRange : getLayout()->subObjectRanges) { diff --git a/tools/gfx/d3d/d3d-util.cpp b/tools/gfx/d3d/d3d-util.cpp index 651d61abf..7db1ce585 100644 --- a/tools/gfx/d3d/d3d-util.cpp +++ b/tools/gfx/d3d/d3d-util.cpp @@ -139,8 +139,7 @@ D3D12_RESOURCE_STATES D3DUtil::translateResourceState(ResourceState state) return D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE | D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE; case gfx::ResourceState::UnorderedAccess: - return D3D12_RESOURCE_STATE_UNORDERED_ACCESS | D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE | - D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE; + return D3D12_RESOURCE_STATE_UNORDERED_ACCESS; case gfx::ResourceState::RenderTarget: return D3D12_RESOURCE_STATE_RENDER_TARGET; case gfx::ResourceState::DepthRead: diff --git a/tools/gfx/d3d11/render-d3d11.cpp b/tools/gfx/d3d11/render-d3d11.cpp index 6f6ba4faf..d4a55543c 100644 --- a/tools/gfx/d3d11/render-d3d11.cpp +++ b/tools/gfx/d3d11/render-d3d11.cpp @@ -212,17 +212,9 @@ protected: }; - class ResourceViewImpl : public IResourceView, public ComObject + class ResourceViewImpl : public ResourceViewBase { public: - SLANG_COM_OBJECT_IUNKNOWN_ALL - IResourceView* getInterface(const Guid& guid) - { - if (guid == GfxGUID::IID_ISlangUnknown || guid == GfxGUID::IID_IResourceView) - return static_cast<IResourceView*>(this); - return nullptr; - } - public: enum class Type { SRV, @@ -662,6 +654,10 @@ protected: /// `t` registers. /// uint32_t registerOffset; + + /// An index into the sub-object array if this binding range is treated + /// as a sub-object. + Index subObjectIndex; }; // Sometimes we just want to iterate over the ranges that represnet @@ -753,10 +749,15 @@ protected: Index m_subObjectCount = 0; uint32_t m_totalOrdinaryDataSize = 0; + + /// The container type of this shader object. When `m_containerType` is + /// `StructuredBuffer` or `UnsizedArray`, this shader object represents a collection + /// instead of a single object. + ShaderObjectContainerType m_containerType = ShaderObjectContainerType::None; Result setElementTypeLayout(slang::TypeLayoutReflection* typeLayout) { - typeLayout = _unwrapParameterGroups(typeLayout); + typeLayout = _unwrapParameterGroups(typeLayout, m_containerType); m_elementTypeLayout = typeLayout; @@ -783,9 +784,31 @@ protected: case slang::BindingType::ParameterBlock: case slang::BindingType::ExistentialValue: bindingRangeInfo.baseIndex = m_subObjectCount; + bindingRangeInfo.subObjectIndex = m_subObjectCount; m_subObjectCount += count; break; - + case slang::BindingType::RawBuffer: + case slang::BindingType::MutableRawBuffer: + if (slangLeafTypeLayout->getType()->getElementType() != nullptr) + { + // A structured buffer occupies both a resource slot and + // a sub-object slot. + bindingRangeInfo.subObjectIndex = m_subObjectCount; + m_subObjectCount += count; + } + if (slangBindingType == slang::BindingType::RawBuffer) + { + bindingRangeInfo.baseIndex = m_srvCount; + m_srvCount += count; + m_srvRanges.add(r); + } + else + { + bindingRangeInfo.baseIndex = m_uavCount; + m_uavCount += count; + m_uavRanges.add(r); + } + break; case slang::BindingType::Sampler: bindingRangeInfo.baseIndex = m_samplerCount; m_samplerCount += count; @@ -794,8 +817,6 @@ protected: case slang::BindingType::CombinedTextureSampler: break; - - case slang::BindingType::MutableRawBuffer: case slang::BindingType::MutableTexture: case slang::BindingType::MutableTypedBuffer: bindingRangeInfo.baseIndex = m_uavCount; @@ -953,8 +974,6 @@ protected: BindingRangeInfo const& getBindingRange(Index index) { return m_bindingRanges[index]; } - slang::TypeLayoutReflection* getElementTypeLayout() { return m_elementTypeLayout; } - Index getSRVCount() { return m_srvCount; } Index getSamplerCount() { return m_samplerCount; } Index getUAVCount() { return m_uavCount; } @@ -1002,6 +1021,7 @@ protected: m_totalOrdinaryDataSize = builder->m_totalOrdinaryDataSize; + m_containerType = builder->m_containerType; return SLANG_OK; } @@ -1127,7 +1147,11 @@ protected: SimpleBindingOffset m_pendingDataOffset; }; - class ShaderObjectImpl : public ShaderObjectBase + class ShaderObjectImpl + : public ShaderObjectBaseImpl< + ShaderObjectImpl, + ShaderObjectLayoutImpl, + SimpleShaderObjectData> { public: static Result create( @@ -1153,24 +1177,14 @@ protected: return SLANG_OK; } - ShaderObjectLayoutImpl* getLayout() - { - return static_cast<ShaderObjectLayoutImpl*>(m_layout.Ptr()); - } - - SLANG_NO_THROW slang::TypeLayoutReflection* SLANG_MCALL getElementTypeLayout() SLANG_OVERRIDE - { - return m_layout->getElementTypeLayout(); - } - SLANG_NO_THROW Result SLANG_MCALL setData(ShaderOffset const& inOffset, void const* data, size_t inSize) SLANG_OVERRIDE { Index offset = inOffset.uniformOffset; Index size = inSize; - char* dest = m_ordinaryData.getBuffer(); - Index availableSize = m_ordinaryData.getCount(); + char* dest = m_data.getBuffer(); + Index availableSize = m_data.getCount(); // TODO: We really should bounds-check access rather than silently ignoring sets // that are too large, but we have several test cases that set more data than @@ -1191,128 +1205,7 @@ protected: return SLANG_OK; } - virtual SLANG_NO_THROW Result SLANG_MCALL - setObject(ShaderOffset const& offset, IShaderObject* object) - SLANG_OVERRIDE - { - if (offset.bindingRangeIndex < 0) - return SLANG_E_INVALID_ARG; - auto layout = getLayout(); - if (offset.bindingRangeIndex >= layout->getBindingRangeCount()) - return SLANG_E_INVALID_ARG; - - auto subObject = static_cast<ShaderObjectImpl*>(object); - - auto bindingRangeIndex = offset.bindingRangeIndex; - auto& bindingRange = layout->getBindingRange(bindingRangeIndex); - - m_objects[bindingRange.baseIndex + offset.bindingArrayIndex] = subObject; - - // If the range being assigned into represents an interface/existential-type leaf field, - // then we need to consider how the `object` being assigned here affects specialization. - // We may also need to assign some data from the sub-object into the ordinary data - // buffer for the parent object. - // - if (bindingRange.bindingType == slang::BindingType::ExistentialValue) - { - // A leaf field of interface type is laid out inside of the parent object - // as a tuple of `(RTTI, WitnessTable, Payload)`. The layout of these fields - // is a contract between the compiler and any runtime system, so we will - // need to rely on details of the binary layout. - - // We start by querying the layout/type of the concrete value that the application - // is trying to store into the field, and also the layout/type of the leaf - // existential-type field itself. - // - auto concreteTypeLayout = subObject->getElementTypeLayout(); - auto concreteType = concreteTypeLayout->getType(); - // - auto existentialTypeLayout = layout->getElementTypeLayout()->getBindingRangeLeafTypeLayout(bindingRangeIndex); - auto existentialType = existentialTypeLayout->getType(); - - // The first field of the tuple (offset zero) is the run-time type information (RTTI) - // ID for the concrete type being stored into the field. - // - // TODO: We need to be able to gather the RTTI type ID from `object` and then - // use `setData(offset, &TypeID, sizeof(TypeID))`. - - // The second field of the tuple (offset 8) is the ID of the "witness" for the - // conformance of the concrete type to the interface used by this field. - // - auto witnessTableOffset = offset; - witnessTableOffset.uniformOffset += 8; - // - // Conformances of a type to an interface are computed and then stored by the - // Slang runtime, so we can look up the ID for this particular conformance (which - // will create it on demand). - // - ComPtr<slang::ISession> slangSession; - SLANG_RETURN_ON_FAIL(getRenderer()->getSlangSession(slangSession.writeRef())); - // - // Note: If the type doesn't actually conform to the required interface for - // this sub-object range, then this is the point where we will detect that - // fact and error out. - // - uint32_t conformanceID = 0xFFFFFFFF; - SLANG_RETURN_ON_FAIL(slangSession->getTypeConformanceWitnessSequentialID( - concreteType, existentialType, &conformanceID)); - // - // Once we have the conformance ID, then we can write it into the object - // at the required offset. - // - SLANG_RETURN_ON_FAIL(setData(witnessTableOffset, &conformanceID, sizeof(conformanceID))); - - // The third field of the tuple (offset 16) is the "payload" that is supposed to - // hold the data for a value of the given concrete type. - // - auto payloadOffset = offset; - payloadOffset.uniformOffset += 16; - - // There are two cases we need to consider here for how the payload might be used: - // - // * If the concrete type of the value being bound is one that can "fit" into the - // available payload space, then it should be stored in the payload. - // - // * If the concrete type of the value cannot fit in the payload space, then it - // will need to be stored somewhere else. - // - if (_doesValueFitInExistentialPayload(concreteTypeLayout, existentialTypeLayout)) - { - // If the value can fit in the payload area, then we will go ahead and copy - // its bytes into that area. - // - setData(payloadOffset, subObject->m_ordinaryData.getBuffer(), subObject->m_ordinaryData.getCount()); - } - else - { - // If the value does *not *fit in the payload area, then there is nothing - // we can do at this point (beyond saving a reference to the sub-object, which - // was handled above). - // - // Once all the sub-objects have been set into the parent object, we can - // compute a specialized layout for it, and that specialized layout can tell - // us where the data for these sub-objects has been laid out. - } - } - - return SLANG_E_NOT_IMPLEMENTED; - } - - virtual SLANG_NO_THROW Result SLANG_MCALL - getObject(ShaderOffset const& offset, IShaderObject** outObject) - SLANG_OVERRIDE - { - SLANG_ASSERT(outObject); - if (offset.bindingRangeIndex < 0) - return SLANG_E_INVALID_ARG; - auto layout = getLayout(); - if (offset.bindingRangeIndex >= layout->getBindingRangeCount()) - return SLANG_E_INVALID_ARG; - auto& bindingRange = layout->getBindingRange(offset.bindingRangeIndex); - - returnComPtr(outObject, m_objects[bindingRange.baseIndex + offset.bindingArrayIndex]); - return SLANG_OK; - } + SLANG_NO_THROW Result SLANG_MCALL setResource(ShaderOffset const& offset, IResourceView* resourceView) SLANG_OVERRIDE @@ -1359,56 +1252,6 @@ protected: } public: - // Appends all types that are used to specialize the element type of this shader object in `args` list. - virtual Result collectSpecializationArgs(ExtendedShaderObjectTypeList& args) override - { - auto& subObjectRanges = getLayout()->getSubObjectRanges(); - // The following logic is built on the assumption that all fields that involve existential types (and - // therefore require specialization) will results in a sub-object range in the type layout. - // This allows us to simply scan the sub-object ranges to find out all specialization arguments. - Index subObjectRangeCount = subObjectRanges.getCount(); - for (Index subObjectRangeIndex = 0; subObjectRangeIndex < subObjectRangeCount; subObjectRangeIndex++) - { - auto const& subObjectRange = subObjectRanges[subObjectRangeIndex]; - auto const& bindingRange = getLayout()->getBindingRange(subObjectRange.bindingRangeIndex); - - Index count = bindingRange.count; - SLANG_ASSERT(count == 1); - - Index subObjectIndexInRange = 0; - auto subObject = m_objects[bindingRange.baseIndex + subObjectIndexInRange]; - - switch (bindingRange.bindingType) - { - case slang::BindingType::ExistentialValue: - { - // A binding type of `ExistentialValue` means the sub-object represents a interface-typed field. - // In this case the specialization argument for this field is the actual specialized type of the bound - // shader object. If the shader object's type is an ordinary type without existential fields, then the - // type argument will simply be the ordinary type. But if the sub object's type is itself a specialized - // type, we need to make sure to use that type as the specialization argument. - - ExtendedShaderObjectType specializedSubObjType; - SLANG_RETURN_ON_FAIL(subObject->getSpecializedShaderObjectType(&specializedSubObjType)); - args.add(specializedSubObjType); - break; - } - case slang::BindingType::ParameterBlock: - case slang::BindingType::ConstantBuffer: - // Currently we only handle the case where the field's type is - // `ParameterBlock<SomeStruct>` or `ConstantBuffer<SomeStruct>`, where `SomeStruct` is a struct type - // (not directly an interface type). In this case, we just recursively collect the specialization arguments - // from the bound sub object. - SLANG_RETURN_ON_FAIL(subObject->collectSpecializationArgs(args)); - // TODO: we need to handle the case where the field is of the form `ParameterBlock<IFoo>`. We should treat - // this case the same way as the `ExistentialValue` case here, but currently we lack a mechanism to distinguish - // the two scenarios. - break; - } - // TODO: need to handle another case where specialization happens on resources fields e.g. `StructuredBuffer<IFoo>`. - } - return SLANG_OK; - } protected: @@ -1430,8 +1273,8 @@ protected: size_t uniformSize = layout->getElementTypeLayout()->getSize(); if (uniformSize) { - m_ordinaryData.setCount(uniformSize); - memset(m_ordinaryData.getBuffer(), 0, uniformSize); + m_data.setCount(uniformSize); + memset(m_data.getBuffer(), 0, uniformSize); } m_srvs.setCount(layout->getSRVCount()); @@ -1467,7 +1310,7 @@ protected: RefPtr<ShaderObjectImpl> subObject; SLANG_RETURN_ON_FAIL( ShaderObjectImpl::create(device, subObjectLayout, subObject.writeRef())); - m_objects[bindingRangeInfo.baseIndex + i] = subObject; + m_objects[bindingRangeInfo.subObjectIndex + i] = subObject; } } @@ -1482,8 +1325,8 @@ protected: { // We start by simply writing in the ordinary data contained directly in this object. // - auto src = m_ordinaryData.getBuffer(); - auto srcSize = size_t(m_ordinaryData.getCount()); + auto src = m_data.getBuffer(); + auto srcSize = size_t(m_data.getCount()); SLANG_ASSERT(srcSize <= destSize); memcpy(dest, src, srcSize); @@ -1554,7 +1397,7 @@ protected: for (Slang::Index i = 0; i < count; ++i) { - auto subObject = m_objects[bindingRangeInfo.baseIndex + i]; + auto subObject = m_objects[bindingRangeInfo.subObjectIndex + i]; RefPtr<ShaderObjectLayoutImpl> subObjectLayout; SLANG_RETURN_ON_FAIL(subObject->_getSpecializedLayout(subObjectLayout.writeRef())); @@ -1758,7 +1601,7 @@ protected: auto subObjectLayout = subObjectRange.layout; auto const& bindingRange = specializedLayout->getBindingRange(subObjectRange.bindingRangeIndex); Index count = bindingRange.count; - Index baseIndex = bindingRange.baseIndex; + Index subObjectIndex = bindingRange.subObjectIndex; // The starting offset for a sub-object range was computed // from Slang reflection information, so we can apply it here. @@ -1782,7 +1625,7 @@ protected: BindingOffset objOffset = rangeOffset; for(Index i = 0; i < count; ++i) { - auto subObject = m_objects[ baseIndex + i ]; + auto subObject = m_objects[subObjectIndex + i]; // Unsurprisingly, we bind each object in the range as // a constant buffer. @@ -1810,7 +1653,7 @@ protected: for(Index i = 0; i < count; ++i) { - auto subObject = m_objects[ baseIndex + i ]; + auto subObject = m_objects[subObjectIndex + i]; subObject->bindAsValue(context, BindingOffset(objOffset), subObjectLayout); objOffset += objStride; @@ -1830,10 +1673,6 @@ protected: // and organized as part of each shader object layout, // the object itself can store its data in a small number // of simple arrays. - - /// Any "ordinary" / uniform data for this object - List<char> m_ordinaryData; - /// The shader resource views (SRVs) that are part of the state of this object List<RefPtr<ShaderResourceViewImpl>> m_srvs; @@ -1843,9 +1682,6 @@ protected: /// The samplers that are part of the state of this object List<RefPtr<SamplerStateImpl>> m_samplers; - /// The sub-objects that are part of the state of this object - List<RefPtr<ShaderObjectImpl>> m_objects; - /// A constant buffer used to stored ordinary data for this object /// and existential-type sub-objects. /// @@ -1879,7 +1715,9 @@ protected: auto renderer = getRenderer(); RefPtr<ShaderObjectLayoutImpl> layout; SLANG_RETURN_ON_FAIL(renderer->getShaderObjectLayout( - extendedType.slangType, (ShaderObjectLayoutBase**)layout.writeRef())); + extendedType.slangType, + m_layout->getContainerType(), + (ShaderObjectLayoutBase**)layout.writeRef())); returnRefPtrMove(outLayout, layout); return SLANG_OK; diff --git a/tools/gfx/d3d12/render-d3d12.cpp b/tools/gfx/d3d12/render-d3d12.cpp index da169c03b..26b774b1a 100644 --- a/tools/gfx/d3d12/render-d3d12.cpp +++ b/tools/gfx/d3d12/render-d3d12.cpp @@ -232,17 +232,9 @@ public: } }; - class ResourceViewImpl : public IResourceView, public ComObject + class ResourceViewImpl : public ResourceViewBase { public: - SLANG_COM_OBJECT_IUNKNOWN_ALL - IResourceView* getInterface(const Guid& guid) - { - if (guid == GfxGUID::IID_ISlangUnknown || guid == GfxGUID::IID_IResourceView) - return static_cast<IResourceView*>(this); - return nullptr; - } - public: RefPtr<Resource> m_resource; D3D12Descriptor m_descriptor; RefPtr<D3D12GeneralDescriptorHeap> m_allocator; @@ -718,7 +710,11 @@ public: uint32_t count; /// A "flat" index for this range in whatever array provides backing storage for it - uint32_t flatIndex; + uint32_t baseIndex; + + /// An index into the sub-object array if this binding range is treated + /// as a sub-object. + uint32_t subObjectIndex; }; /// Offset information for a sub-object range @@ -802,12 +798,17 @@ public: /// The number of root parameter consumed by (transitive) sub-objects uint32_t m_childRootParameterCount = 0; - /// The total size in bytes of the ordinary data for this object and transitive sub-objects + /// The total size in bytes of the ordinary data for this object and transitive sub-object. uint32_t m_totalOrdinaryDataSize = 0; + /// The container type of this shader object. When `m_containerType` is + /// `StructuredBuffer` or `UnsizedArray`, this shader object represents a collection + /// instead of a single object. + ShaderObjectContainerType m_containerType = ShaderObjectContainerType::None; + Result setElementTypeLayout(slang::TypeLayoutReflection* typeLayout) { - typeLayout = _unwrapParameterGroups(typeLayout); + typeLayout = _unwrapParameterGroups(typeLayout, m_containerType); m_elementTypeLayout = typeLayout; // If the type contains any ordinary data, then we must reserve a buffer @@ -849,12 +850,24 @@ public: case slang::BindingType::ConstantBuffer: case slang::BindingType::ParameterBlock: case slang::BindingType::ExistentialValue: - bindingRangeInfo.flatIndex = m_subObjectCount; + bindingRangeInfo.baseIndex = m_subObjectCount; + bindingRangeInfo.subObjectIndex = m_subObjectCount; m_subObjectCount += count; break; - + case slang::BindingType::RawBuffer: + case slang::BindingType::MutableRawBuffer: + if (slangLeafTypeLayout->getType()->getElementType() != nullptr) + { + // A structured buffer occupies both a resource slot and + // a sub-object slot. + bindingRangeInfo.subObjectIndex = m_subObjectCount; + m_subObjectCount += count; + } + bindingRangeInfo.baseIndex = m_ownCounts.resource; + m_ownCounts.resource += count; + break; case slang::BindingType::Sampler: - bindingRangeInfo.flatIndex = m_ownCounts.sampler; + bindingRangeInfo.baseIndex = m_ownCounts.sampler; m_ownCounts.sampler += count; break; @@ -867,7 +880,7 @@ public: break; default: - bindingRangeInfo.flatIndex = m_ownCounts.resource; + bindingRangeInfo.baseIndex = m_ownCounts.resource; m_ownCounts.resource += count; break; } @@ -1092,8 +1105,6 @@ public: BindingRangeInfo const& getBindingRange(Index index) { return m_bindingRanges[index]; } - slang::TypeLayoutReflection* getElementTypeLayout() { return m_elementTypeLayout; } - uint32_t getResourceSlotCount() { return m_ownCounts.resource; } uint32_t getSamplerSlotCount() { return m_ownCounts.sampler; } Index getSubObjectSlotCount() { return m_subObjectCount; } @@ -1128,6 +1139,8 @@ public: initBase(renderer, builder->m_elementTypeLayout); + m_containerType = builder->m_containerType; + m_bindingRanges = _Move(builder->m_bindingRanges); m_subObjectRanges = builder->m_subObjectRanges; @@ -2033,7 +2046,11 @@ public: RefPtr<RootShaderObjectLayoutImpl> m_rootObjectLayout; }; - class ShaderObjectImpl : public ShaderObjectBase + class ShaderObjectImpl + : public ShaderObjectBaseImpl< + ShaderObjectImpl, + ShaderObjectLayoutImpl, + SimpleShaderObjectData> { public: static Result create( @@ -2064,25 +2081,14 @@ public: return SLANG_OK; } - ShaderObjectLayoutImpl* getLayout() - { - return static_cast<ShaderObjectLayoutImpl*>(m_layout.Ptr()); - } - - SLANG_NO_THROW slang::TypeLayoutReflection* SLANG_MCALL getElementTypeLayout() - SLANG_OVERRIDE - { - return m_layout->getElementTypeLayout(); - } - SLANG_NO_THROW Result SLANG_MCALL setData(ShaderOffset const& inOffset, void const* data, size_t inSize) SLANG_OVERRIDE { Index offset = inOffset.uniformOffset; Index size = inSize; - char* dest = m_ordinaryData.getBuffer(); - Index availableSize = m_ordinaryData.getCount(); + char* dest = m_data.getBuffer(); + Index availableSize = m_data.getCount(); // TODO: We really should bounds-check access rather than silently ignoring sets // that are too large, but we have several test cases that set more data than @@ -2103,133 +2109,6 @@ public: return SLANG_OK; } - virtual SLANG_NO_THROW Result SLANG_MCALL - setObject(ShaderOffset const& offset, IShaderObject* object) SLANG_OVERRIDE - { - if (offset.bindingRangeIndex < 0) - return SLANG_E_INVALID_ARG; - auto layout = getLayout(); - if (offset.bindingRangeIndex >= layout->getBindingRangeCount()) - return SLANG_E_INVALID_ARG; - - auto subObject = static_cast<ShaderObjectImpl*>(object); - - auto bindingRangeIndex = offset.bindingRangeIndex; - auto& bindingRange = layout->getBindingRange(bindingRangeIndex); - - m_objects[bindingRange.flatIndex + offset.bindingArrayIndex] = subObject; - - // If the range being assigned into represents an interface/existential-type leaf field, - // then we need to consider how the `object` being assigned here affects specialization. - // We may also need to assign some data from the sub-object into the ordinary data - // buffer for the parent object. - // - if (bindingRange.bindingType == slang::BindingType::ExistentialValue) - { - // A leaf field of interface type is laid out inside of the parent object - // as a tuple of `(RTTI, WitnessTable, Payload)`. The layout of these fields - // is a contract between the compiler and any runtime system, so we will - // need to rely on details of the binary layout. - - // We start by querying the layout/type of the concrete value that the application - // is trying to store into the field, and also the layout/type of the leaf - // existential-type field itself. - // - auto concreteTypeLayout = subObject->getElementTypeLayout(); - auto concreteType = concreteTypeLayout->getType(); - // - auto existentialTypeLayout = - layout->getElementTypeLayout()->getBindingRangeLeafTypeLayout( - bindingRangeIndex); - auto existentialType = existentialTypeLayout->getType(); - - // The first field of the tuple (offset zero) is the run-time type information - // (RTTI) ID for the concrete type being stored into the field. - // - // TODO: We need to be able to gather the RTTI type ID from `object` and then - // use `setData(offset, &TypeID, sizeof(TypeID))`. - - // The second field of the tuple (offset 8) is the ID of the "witness" for the - // conformance of the concrete type to the interface used by this field. - // - auto witnessTableOffset = offset; - witnessTableOffset.uniformOffset += 8; - // - // Conformances of a type to an interface are computed and then stored by the - // Slang runtime, so we can look up the ID for this particular conformance (which - // will create it on demand). - // - ComPtr<slang::ISession> slangSession; - SLANG_RETURN_ON_FAIL(getRenderer()->getSlangSession(slangSession.writeRef())); - // - // Note: If the type doesn't actually conform to the required interface for - // this sub-object range, then this is the point where we will detect that - // fact and error out. - // - uint32_t conformanceID = 0xFFFFFFFF; - SLANG_RETURN_ON_FAIL(slangSession->getTypeConformanceWitnessSequentialID( - concreteType, existentialType, &conformanceID)); - // - // Once we have the conformance ID, then we can write it into the object - // at the required offset. - // - SLANG_RETURN_ON_FAIL( - setData(witnessTableOffset, &conformanceID, sizeof(conformanceID))); - - // The third field of the tuple (offset 16) is the "payload" that is supposed to - // hold the data for a value of the given concrete type. - // - auto payloadOffset = offset; - payloadOffset.uniformOffset += 16; - - // There are two cases we need to consider here for how the payload might be used: - // - // * If the concrete type of the value being bound is one that can "fit" into the - // available payload space, then it should be stored in the payload. - // - // * If the concrete type of the value cannot fit in the payload space, then it - // will need to be stored somewhere else. - // - if (_doesValueFitInExistentialPayload(concreteTypeLayout, existentialTypeLayout)) - { - // If the value can fit in the payload area, then we will go ahead and copy - // its bytes into that area. - // - setData( - payloadOffset, - subObject->m_ordinaryData.getBuffer(), - subObject->m_ordinaryData.getCount()); - } - else - { - // If the value does *not *fit in the payload area, then there is nothing - // we can do at this point (beyond saving a reference to the sub-object, which - // was handled above). - // - // Once all the sub-objects have been set into the parent object, we can - // compute a specialized layout for it, and that specialized layout can tell - // us where the data for these sub-objects has been laid out. - return SLANG_E_NOT_IMPLEMENTED; - } - } - return SLANG_OK; - } - - virtual SLANG_NO_THROW Result SLANG_MCALL - getObject(ShaderOffset const& offset, IShaderObject** outObject) SLANG_OVERRIDE - { - SLANG_ASSERT(outObject); - if (offset.bindingRangeIndex < 0) - return SLANG_E_INVALID_ARG; - auto layout = getLayout(); - if (offset.bindingRangeIndex >= layout->getBindingRangeCount()) - return SLANG_E_INVALID_ARG; - auto& bindingRange = layout->getBindingRange(offset.bindingRangeIndex); - - returnComPtr(outObject, m_objects[bindingRange.flatIndex + offset.bindingArrayIndex]); - return SLANG_OK; - } - SLANG_NO_THROW Result SLANG_MCALL setResource(ShaderOffset const& offset, IResourceView* resourceView) SLANG_OVERRIDE { @@ -2242,15 +2121,15 @@ public: auto resourceViewImpl = static_cast<ResourceViewImpl*>(resourceView); auto& bindingRange = layout->getBindingRange(offset.bindingRangeIndex); - auto descriptorSlotIndex = bindingRange.flatIndex + (int32_t)offset.bindingArrayIndex; + auto descriptorSlotIndex = bindingRange.baseIndex + (int32_t)offset.bindingArrayIndex; // Hold a reference to the resource to prevent its destruction. - m_boundResources[bindingRange.flatIndex + offset.bindingArrayIndex] = + m_boundResources[bindingRange.baseIndex + offset.bindingArrayIndex] = resourceViewImpl->m_resource; ID3D12Device* d3dDevice = static_cast<D3D12Device*>(getDevice())->m_device; d3dDevice->CopyDescriptorsSimple( 1, m_descriptorSet.resourceTable.getCpuHandle( - bindingRange.flatIndex + (int32_t)offset.bindingArrayIndex), + bindingRange.baseIndex + (int32_t)offset.bindingArrayIndex), resourceViewImpl->m_descriptor.cpuHandle, D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); return SLANG_OK; @@ -2270,7 +2149,7 @@ public: d3dDevice->CopyDescriptorsSimple( 1, m_descriptorSet.samplerTable.getCpuHandle( - bindingRange.flatIndex + + bindingRange.baseIndex + (int32_t)offset.bindingArrayIndex), samplerImpl->m_descriptor.cpuHandle, D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER); @@ -2313,66 +2192,6 @@ public: } public: - // Appends all types that are used to specialize the element type of this shader object in - // `args` list. - virtual Result collectSpecializationArgs(ExtendedShaderObjectTypeList& args) override - { - auto& subObjectRanges = getLayout()->getSubObjectRanges(); - // The following logic is built on the assumption that all fields that involve - // existential types (and therefore require specialization) will results in a sub-object - // range in the type layout. This allows us to simply scan the sub-object ranges to find - // out all specialization arguments. - Index subObjectRangeCount = subObjectRanges.getCount(); - for (Index subObjectRangeIndex = 0; subObjectRangeIndex < subObjectRangeCount; - subObjectRangeIndex++) - { - auto const& subObjectRange = subObjectRanges[subObjectRangeIndex]; - auto const& bindingRange = - getLayout()->getBindingRange(subObjectRange.bindingRangeIndex); - - Index count = bindingRange.count; - SLANG_ASSERT(count == 1); - - Index subObjectIndexInRange = 0; - auto subObject = m_objects[bindingRange.flatIndex + subObjectIndexInRange]; - - switch (bindingRange.bindingType) - { - case slang::BindingType::ExistentialValue: - { - // A binding type of `ExistentialValue` means the sub-object represents a - // interface-typed field. In this case the specialization argument for this - // field is the actual specialized type of the bound shader object. If the - // shader object's type is an ordinary type without existential fields, then - // the type argument will simply be the ordinary type. But if the sub - // object's type is itself a specialized type, we need to make sure to use - // that type as the specialization argument. - - ExtendedShaderObjectType specializedSubObjType; - SLANG_RETURN_ON_FAIL( - subObject->getSpecializedShaderObjectType(&specializedSubObjType)); - args.add(specializedSubObjType); - break; - } - case slang::BindingType::ParameterBlock: - case slang::BindingType::ConstantBuffer: - // Currently we only handle the case where the field's type is - // `ParameterBlock<SomeStruct>` or `ConstantBuffer<SomeStruct>`, where - // `SomeStruct` is a struct type (not directly an interface type). In this case, - // we just recursively collect the specialization arguments from the bound sub - // object. - SLANG_RETURN_ON_FAIL(subObject->collectSpecializationArgs(args)); - // TODO: we need to handle the case where the field is of the form - // `ParameterBlock<IFoo>`. We should treat this case the same way as the - // `ExistentialValue` case here, but currently we lack a mechanism to - // distinguish the two scenarios. - break; - } - // TODO: need to handle another case where specialization happens on resources - // fields e.g. `StructuredBuffer<IFoo>`. - } - return SLANG_OK; - } protected: Result init( @@ -2399,8 +2218,8 @@ public: size_t uniformSize = layout->getElementTypeLayout()->getSize(); if (uniformSize) { - m_ordinaryData.setCount(uniformSize); - memset(m_ordinaryData.getBuffer(), 0, uniformSize); + m_data.setCount(uniformSize); + memset(m_data.getBuffer(), 0, uniformSize); } // Each shader object will own CPU descriptor heap memory @@ -2458,7 +2277,7 @@ public: RefPtr<ShaderObjectImpl> subObject; SLANG_RETURN_ON_FAIL( ShaderObjectImpl::create(device, subObjectLayout, subObject.writeRef())); - m_objects[bindingRangeInfo.flatIndex + i] = subObject; + m_objects[bindingRangeInfo.subObjectIndex + i] = subObject; } } @@ -2474,8 +2293,8 @@ public: size_t destSize, ShaderObjectLayoutImpl* specializedLayout) { - auto src = m_ordinaryData.getBuffer(); - auto srcSize = size_t(m_ordinaryData.getCount()); + auto src = m_data.getBuffer(); + auto srcSize = size_t(m_data.getCount()); SLANG_ASSERT(srcSize <= destSize); @@ -2550,7 +2369,7 @@ public: for (uint32_t i = 0; i < count; ++i) { - auto subObject = m_objects[bindingRangeInfo.flatIndex + i]; + auto subObject = m_objects[bindingRangeInfo.subObjectIndex + i]; RefPtr<ShaderObjectLayoutImpl> subObjectLayout; SLANG_RETURN_ON_FAIL( @@ -2863,7 +2682,7 @@ public: { auto& subObjectRange = specializedLayout->getSubObjectRange(i); auto& bindingRange = specializedLayout->getBindingRange(subObjectRange.bindingRangeIndex); - auto baseIndex = bindingRange.flatIndex; + auto subObjectIndex = bindingRange.subObjectIndex; auto subObjectLayout = subObjectRange.layout.Ptr(); BindingOffset rangeOffset = offset; @@ -2878,7 +2697,7 @@ public: auto objOffset = rangeOffset; for (uint32_t j = 0; j < bindingRange.count; j++) { - auto& object = m_objects[baseIndex + j]; + auto& object = m_objects[subObjectIndex + j]; object->bindAsConstantBuffer(context, descriptorSet, objOffset, subObjectLayout); objOffset += rangeStride; } @@ -2890,7 +2709,7 @@ public: auto objOffset = rangeOffset; for (uint32_t j = 0; j < bindingRange.count; j++) { - auto& object = m_objects[baseIndex + j]; + auto& object = m_objects[subObjectIndex + j]; object->bindAsParameterBlock(context, objOffset, subObjectLayout); objOffset += rangeStride; } @@ -2903,7 +2722,7 @@ public: auto objOffset = rangeOffset; for (uint32_t j = 0; j < bindingRange.count; j++) { - auto& object = m_objects[baseIndex + j]; + auto& object = m_objects[subObjectIndex + j]; object->bindAsValue(context, descriptorSet, objOffset, subObjectLayout); objOffset += rangeStride; } @@ -2915,10 +2734,6 @@ public: return SLANG_OK; } - /// Any "ordinary" / uniform data for this object - List<char> m_ordinaryData; - - List<RefPtr<ShaderObjectImpl>> m_objects; /// A CPU-memory descriptor set holding any descriptors used to represent the resources/samplers in this object's state DescriptorSet m_descriptorSet; @@ -2967,7 +2782,9 @@ public: auto renderer = getRenderer(); RefPtr<ShaderObjectLayoutImpl> layout; SLANG_RETURN_ON_FAIL(renderer->getShaderObjectLayout( - extendedType.slangType, (ShaderObjectLayoutBase**)layout.writeRef())); + extendedType.slangType, + m_layout->getContainerType(), + (ShaderObjectLayoutBase**)layout.writeRef())); returnRefPtrMove(outLayout, layout); return SLANG_OK; diff --git a/tools/gfx/debug-layer.cpp b/tools/gfx/debug-layer.cpp index aa8989623..56ae4fdab 100644 --- a/tools/gfx/debug-layer.cpp +++ b/tools/gfx/debug-layer.cpp @@ -373,12 +373,18 @@ Result DebugDevice::createCommandQueue(const ICommandQueue::Desc& desc, ICommand return result; } -Result DebugDevice::createShaderObject(slang::TypeReflection* type, IShaderObject** outShaderObject) +Result DebugDevice::createShaderObject( + slang::TypeReflection* type, + ShaderObjectContainerType containerType, + IShaderObject** outShaderObject) { SLANG_GFX_API_FUNC; RefPtr<DebugShaderObject> outObject = new DebugShaderObject(); - auto result = baseObject->createShaderObject(type, outObject->baseObject.writeRef()); + auto typeName = type->getName(); + auto result = + baseObject->createShaderObject(type, containerType, outObject->baseObject.writeRef()); + outObject->m_typeName = typeName; if (SLANG_FAILED(result)) return result; returnComPtr(outShaderObject, outObject); @@ -838,6 +844,12 @@ void DebugSwapchain::maybeRebuildImageList() } } +ShaderObjectContainerType DebugShaderObject::getContainerType() +{ + SLANG_GFX_API_FUNC; + return baseObject->getContainerType(); +} + slang::TypeLayoutReflection* DebugShaderObject::getElementTypeLayout() { SLANG_GFX_API_FUNC; @@ -896,6 +908,7 @@ Result DebugShaderObject::getObject(ShaderOffset const& offset, IShaderObject** } debugShaderObject = new DebugShaderObject(); debugShaderObject->baseObject = innerObject; + debugShaderObject->m_typeName = innerObject->getElementTypeLayout()->getName(); m_objects[ShaderOffsetKey{offset}] = debugShaderObject; returnComPtr(object, debugShaderObject); return resultCode; diff --git a/tools/gfx/debug-layer.h b/tools/gfx/debug-layer.h index 12540c31a..a4e201e4f 100644 --- a/tools/gfx/debug-layer.h +++ b/tools/gfx/debug-layer.h @@ -75,8 +75,10 @@ public: IInputLayout** outLayout) override; virtual SLANG_NO_THROW Result SLANG_MCALL createCommandQueue(const ICommandQueue::Desc& desc, ICommandQueue** outQueue) override; - virtual SLANG_NO_THROW Result SLANG_MCALL - createShaderObject(slang::TypeReflection* type, IShaderObject** outObject) override; + virtual SLANG_NO_THROW Result SLANG_MCALL createShaderObject( + slang::TypeReflection* type, + ShaderObjectContainerType container, + IShaderObject** outObject) override; virtual SLANG_NO_THROW Result SLANG_MCALL createProgram(const IShaderProgram::Desc& desc, IShaderProgram** outProgram) override; virtual SLANG_NO_THROW Result SLANG_MCALL createGraphicsPipelineState( @@ -147,6 +149,7 @@ public: public: IShaderObject* getInterface(const Slang::Guid& guid); virtual SLANG_NO_THROW slang::TypeLayoutReflection* SLANG_MCALL getElementTypeLayout() override; + virtual SLANG_NO_THROW ShaderObjectContainerType SLANG_MCALL getContainerType() override; virtual SLANG_NO_THROW UInt SLANG_MCALL getEntryPointCount() override; virtual SLANG_NO_THROW Result SLANG_MCALL getEntryPoint(UInt index, IShaderObject** entryPoint) override; @@ -184,6 +187,7 @@ public: (Slang::HashCode)offset.bindingRangeIndex)); } }; + Slang::String m_typeName; Slang::List<Slang::RefPtr<DebugShaderObject>> m_entryPoints; Slang::Dictionary<ShaderOffsetKey, Slang::RefPtr<DebugShaderObject>> m_objects; Slang::Dictionary<ShaderOffsetKey, Slang::RefPtr<DebugResourceView>> m_resources; diff --git a/tools/gfx/open-gl/render-gl.cpp b/tools/gfx/open-gl/render-gl.cpp index 53e9dd4b1..5b1ea2c3e 100644 --- a/tools/gfx/open-gl/render-gl.cpp +++ b/tools/gfx/open-gl/render-gl.cpp @@ -272,17 +272,9 @@ public: GLuint m_samplerID; }; - class ResourceViewImpl : public IResourceView, public ComObject + class ResourceViewImpl : public ResourceViewBase { public: - SLANG_COM_OBJECT_IUNKNOWN_ALL - IResourceView* getInterface(const Guid& guid) - { - if (guid == GfxGUID::IID_ISlangUnknown || guid == GfxGUID::IID_IResourceView) - return static_cast<IResourceView*>(this); - return nullptr; - } - public: enum class Type { Texture, Buffer @@ -620,6 +612,7 @@ public: slang::BindingType bindingType; Index count; Index baseIndex; + Index subObjectIndex; }; struct SubObjectRangeInfo @@ -638,6 +631,11 @@ public: RendererBase* m_renderer; slang::TypeLayoutReflection* m_elementTypeLayout; + /// The container type of this shader object. When `m_containerType` is + /// `StructuredBuffer` or `UnsizedArray`, this shader object represents a collection + /// instead of a single object. + ShaderObjectContainerType m_containerType = ShaderObjectContainerType::None; + List<BindingRangeInfo> m_bindingRanges; List<SubObjectRangeInfo> m_subObjectRanges; @@ -648,7 +646,7 @@ public: Result setElementTypeLayout(slang::TypeLayoutReflection* typeLayout) { - typeLayout = _unwrapParameterGroups(typeLayout); + typeLayout = _unwrapParameterGroups(typeLayout, m_containerType); m_elementTypeLayout = typeLayout; @@ -673,9 +671,21 @@ public: case slang::BindingType::ParameterBlock: case slang::BindingType::ExistentialValue: bindingRangeInfo.baseIndex = m_subObjectCount; + bindingRangeInfo.subObjectIndex = m_subObjectCount; m_subObjectCount += count; break; - + case slang::BindingType::RawBuffer: + case slang::BindingType::MutableRawBuffer: + if (slangLeafTypeLayout->getType()->getElementType() != nullptr) + { + // A structured buffer occupies both a resource slot and + // a sub-object slot. + bindingRangeInfo.subObjectIndex = m_subObjectCount; + m_subObjectCount += count; + } + bindingRangeInfo.baseIndex = m_storageBufferCount; + m_storageBufferCount += count; + break; case slang::BindingType::Sampler: break; @@ -690,7 +700,6 @@ public: m_imageCount += count; break; - case slang::BindingType::MutableRawBuffer: case slang::BindingType::MutableTypedBuffer: bindingRangeInfo.baseIndex = m_storageBufferCount; m_storageBufferCount += count; @@ -765,8 +774,6 @@ public: BindingRangeInfo const& getBindingRange(Index index) { return m_bindingRanges[index]; } - slang::TypeLayoutReflection* getElementTypeLayout() { return m_elementTypeLayout; } - Index getTextureCount() { return m_textureCount; } Index getImageCount() { return m_imageCount; } Index getStorageBufferCount() { return m_storageBufferCount; } @@ -795,6 +802,8 @@ public: m_storageBufferCount = builder->m_storageBufferCount; m_subObjectCount = builder->m_subObjectCount; m_subObjectRanges = builder->m_subObjectRanges; + + m_containerType = builder->m_containerType; return SLANG_OK; } @@ -903,7 +912,11 @@ public: List<EntryPointInfo> m_entryPoints; }; - class ShaderObjectImpl : public ShaderObjectBase + class ShaderObjectImpl + : public ShaderObjectBaseImpl< + ShaderObjectImpl, + ShaderObjectLayoutImpl, + SimpleShaderObjectData> { public: static Result create( @@ -934,19 +947,14 @@ public: return static_cast<ShaderObjectLayoutImpl*>(m_layout.Ptr()); } - SLANG_NO_THROW slang::TypeLayoutReflection* SLANG_MCALL getElementTypeLayout() SLANG_OVERRIDE - { - return m_layout->getElementTypeLayout(); - } - SLANG_NO_THROW Result SLANG_MCALL setData(ShaderOffset const& inOffset, void const* data, size_t inSize) SLANG_OVERRIDE { Index offset = inOffset.uniformOffset; Index size = inSize; - char* dest = m_ordinaryData.getBuffer(); - Index availableSize = m_ordinaryData.getCount(); + char* dest = m_data.getBuffer(); + Index availableSize = m_data.getCount(); // TODO: We really should bounds-check access rather than silently ignoring sets // that are too large, but we have several test cases that set more data than @@ -967,130 +975,6 @@ public: return SLANG_OK; } - virtual SLANG_NO_THROW Result SLANG_MCALL - setObject(ShaderOffset const& offset, IShaderObject* object) - SLANG_OVERRIDE - { - if (offset.bindingRangeIndex < 0) - return SLANG_E_INVALID_ARG; - auto layout = getLayout(); - if (offset.bindingRangeIndex >= layout->getBindingRangeCount()) - return SLANG_E_INVALID_ARG; - - auto subObject = static_cast<ShaderObjectImpl*>(object); - - auto bindingRangeIndex = offset.bindingRangeIndex; - auto& bindingRange = layout->getBindingRange(bindingRangeIndex); - - m_objects[bindingRange.baseIndex + offset.bindingArrayIndex] = subObject; - - // If the range being assigned into represents an interface/existential-type leaf field, - // then we need to consider how the `object` being assigned here affects specialization. - // We may also need to assign some data from the sub-object into the ordinary data - // buffer for the parent object. - // - if (bindingRange.bindingType == slang::BindingType::ExistentialValue) - { - // A leaf field of interface type is laid out inside of the parent object - // as a tuple of `(RTTI, WitnessTable, Payload)`. The layout of these fields - // is a contract between the compiler and any runtime system, so we will - // need to rely on details of the binary layout. - - // We start by querying the layout/type of the concrete value that the application - // is trying to store into the field, and also the layout/type of the leaf - // existential-type field itself. - // - auto concreteTypeLayout = subObject->getElementTypeLayout(); - auto concreteType = concreteTypeLayout->getType(); - // - auto existentialTypeLayout = layout->getElementTypeLayout()->getBindingRangeLeafTypeLayout(bindingRangeIndex); - auto existentialType = existentialTypeLayout->getType(); - - // The first field of the tuple (offset zero) is the run-time type information (RTTI) - // ID for the concrete type being stored into the field. - // - // TODO: We need to be able to gather the RTTI type ID from `object` and then - // use `setData(offset, &TypeID, sizeof(TypeID))`. - - // The second field of the tuple (offset 8) is the ID of the "witness" for the - // conformance of the concrete type to the interface used by this field. - // - auto witnessTableOffset = offset; - witnessTableOffset.uniformOffset += 8; - // - // Conformances of a type to an interface are computed and then stored by the - // Slang runtime, so we can look up the ID for this particular conformance (which - // will create it on demand). - // - ComPtr<slang::ISession> slangSession; - SLANG_RETURN_ON_FAIL(getRenderer()->getSlangSession(slangSession.writeRef())); - // - // Note: If the type doesn't actually conform to the required interface for - // this sub-object range, then this is the point where we will detect that - // fact and error out. - // - uint32_t conformanceID = 0xFFFFFFFF; - SLANG_RETURN_ON_FAIL(slangSession->getTypeConformanceWitnessSequentialID( - concreteType, existentialType, &conformanceID)); - // - // Once we have the conformance ID, then we can write it into the object - // at the required offset. - // - SLANG_RETURN_ON_FAIL(setData(witnessTableOffset, &conformanceID, sizeof(conformanceID))); - - // The third field of the tuple (offset 16) is the "payload" that is supposed to - // hold the data for a value of the given concrete type. - // - auto payloadOffset = offset; - payloadOffset.uniformOffset += 16; - - // There are two cases we need to consider here for how the payload might be used: - // - // * If the concrete type of the value being bound is one that can "fit" into the - // available payload space, then it should be stored in the payload. - // - // * If the concrete type of the value cannot fit in the payload space, then it - // will need to be stored somewhere else. - // - if (_doesValueFitInExistentialPayload(concreteTypeLayout, existentialTypeLayout)) - { - // If the value can fit in the payload area, then we will go ahead and copy - // its bytes into that area. - // - setData(payloadOffset, subObject->m_ordinaryData.getBuffer(), subObject->m_ordinaryData.getCount()); - } - else - { - // If the value does *not *fit in the payload area, then there is nothing - // we can do at this point (beyond saving a reference to the sub-object, which - // was handled above). - // - // Once all the sub-objects have been set into the parent object, we can - // compute a specialized layout for it, and that specialized layout can tell - // us where the data for these sub-objects has been laid out. - } - } - - return SLANG_E_NOT_IMPLEMENTED; - } - - virtual SLANG_NO_THROW Result SLANG_MCALL - getObject(ShaderOffset const& offset, IShaderObject** outObject) - SLANG_OVERRIDE - { - SLANG_ASSERT(outObject); - if (offset.bindingRangeIndex < 0) - return SLANG_E_INVALID_ARG; - auto layout = getLayout(); - if (offset.bindingRangeIndex >= layout->getBindingRangeCount()) - return SLANG_E_INVALID_ARG; - auto& bindingRange = layout->getBindingRange(offset.bindingRangeIndex); - - auto object = m_objects[bindingRange.baseIndex + offset.bindingArrayIndex].Ptr(); - object->addRef(); - *outObject = object; - return SLANG_OK; - } SLANG_NO_THROW Result SLANG_MCALL setResource(ShaderOffset const& offset, IResourceView* resourceView) SLANG_OVERRIDE @@ -1151,57 +1035,6 @@ public: } public: - // Appends all types that are used to specialize the element type of this shader object in `args` list. - virtual Result collectSpecializationArgs(ExtendedShaderObjectTypeList& args) override - { - auto& subObjectRanges = getLayout()->getSubObjectRanges(); - // The following logic is built on the assumption that all fields that involve existential types (and - // therefore require specialization) will results in a sub-object range in the type layout. - // This allows us to simply scan the sub-object ranges to find out all specialization arguments. - Index subObjectRangeCount = subObjectRanges.getCount(); - for (Index subObjectRangeIndex = 0; subObjectRangeIndex < subObjectRangeCount; subObjectRangeIndex++) - { - auto const& subObjectRange = subObjectRanges[subObjectRangeIndex]; - auto const& bindingRange = getLayout()->getBindingRange(subObjectRange.bindingRangeIndex); - - Index count = bindingRange.count; - SLANG_ASSERT(count == 1); - - Index subObjectIndexInRange = 0; - auto subObject = m_objects[bindingRange.baseIndex + subObjectIndexInRange]; - - switch (bindingRange.bindingType) - { - case slang::BindingType::ExistentialValue: - { - // A binding type of `ExistentialValue` means the sub-object represents a interface-typed field. - // In this case the specialization argument for this field is the actual specialized type of the bound - // shader object. If the shader object's type is an ordinary type without existential fields, then the - // type argument will simply be the ordinary type. But if the sub object's type is itself a specialized - // type, we need to make sure to use that type as the specialization argument. - - ExtendedShaderObjectType specializedSubObjType; - SLANG_RETURN_ON_FAIL(subObject->getSpecializedShaderObjectType(&specializedSubObjType)); - args.add(specializedSubObjType); - break; - } - case slang::BindingType::ParameterBlock: - case slang::BindingType::ConstantBuffer: - // Currently we only handle the case where the field's type is - // `ParameterBlock<SomeStruct>` or `ConstantBuffer<SomeStruct>`, where `SomeStruct` is a struct type - // (not directly an interface type). In this case, we just recursively collect the specialization arguments - // from the bound sub object. - SLANG_RETURN_ON_FAIL(subObject->collectSpecializationArgs(args)); - // TODO: we need to handle the case where the field is of the form `ParameterBlock<IFoo>`. We should treat - // this case the same way as the `ExistentialValue` case here, but currently we lack a mechanism to distinguish - // the two scenarios. - break; - } - // TODO: need to handle another case where specialization happens on resources fields e.g. `StructuredBuffer<IFoo>`. - } - return SLANG_OK; - } - protected: friend class ProgramVars; @@ -1222,8 +1055,8 @@ public: size_t uniformSize = layout->getElementTypeLayout()->getSize(); if (uniformSize) { - m_ordinaryData.setCount(uniformSize); - memset(m_ordinaryData.getBuffer(), 0, uniformSize); + m_data.setCount(uniformSize); + memset(m_data.getBuffer(), 0, uniformSize); } m_samplers.setCount(layout->getTextureCount()); @@ -1260,7 +1093,7 @@ public: RefPtr<ShaderObjectImpl> subObject; SLANG_RETURN_ON_FAIL( ShaderObjectImpl::create(device, subObjectLayout, subObject.writeRef())); - m_objects[bindingRangeInfo.baseIndex + i] = subObject; + m_objects[bindingRangeInfo.subObjectIndex + i] = subObject; } } @@ -1275,8 +1108,8 @@ public: size_t destSize, ShaderObjectLayoutImpl* specializedLayout) { - auto src = m_ordinaryData.getBuffer(); - auto srcSize = size_t(m_ordinaryData.getCount()); + auto src = m_data.getBuffer(); + auto srcSize = size_t(m_data.getCount()); SLANG_ASSERT(srcSize <= destSize); @@ -1349,7 +1182,7 @@ public: for (Slang::Index i = 0; i < count; ++i) { - auto subObject = m_objects[bindingRangeInfo.baseIndex + i]; + auto subObject = m_objects[bindingRangeInfo.subObjectIndex + i]; RefPtr<ShaderObjectLayoutImpl> subObjectLayout; SLANG_RETURN_ON_FAIL(subObject->_getSpecializedLayout(subObjectLayout.writeRef())); @@ -1459,15 +1292,31 @@ public: for (auto buffer : m_storageBuffers) bindingState->storageBufferBindings.add(buffer ? buffer->m_bufferID : 0); - for (auto subObject : m_objects) - subObject->bindObject(device, bindingState); + for (auto const& subObjectRange : layout->getSubObjectRanges()) + { + auto subObjectLayout = subObjectRange.layout; + auto const& bindingRange = + layout->getBindingRange(subObjectRange.bindingRangeIndex); + + switch (bindingRange.bindingType) + { + case slang::BindingType::ConstantBuffer: + case slang::BindingType::ParameterBlock: + case slang::BindingType::ExistentialValue: + break; + default: + continue; + } + + for (Index i = 0; i < bindingRange.count; i++) + { + m_objects[i + bindingRange.subObjectIndex]->bindObject(device, bindingState); + } + } return SLANG_OK; } - /// Any "ordinary" / uniform data for this object - List<char> m_ordinaryData; - List<RefPtr<TextureViewImpl>> m_textures; List<RefPtr<TextureViewImpl>> m_images; @@ -1476,8 +1325,6 @@ public: List<RefPtr<BufferViewImpl>> m_storageBuffers; - List<RefPtr<ShaderObjectImpl>> m_objects; - /// A constant buffer used to stored ordinary data for this object /// and existential-type sub-objects. /// @@ -1511,7 +1358,9 @@ public: auto renderer = getRenderer(); RefPtr<ShaderObjectLayoutImpl> layout; SLANG_RETURN_ON_FAIL(renderer->getShaderObjectLayout( - extendedType.slangType, (ShaderObjectLayoutBase**)layout.writeRef())); + extendedType.slangType, + m_layout->getContainerType(), + (ShaderObjectLayoutBase**)layout.writeRef())); returnRefPtrMove(outLayout, layout); return SLANG_OK; diff --git a/tools/gfx/renderer-shared.cpp b/tools/gfx/renderer-shared.cpp index 72b4c45f5..1b384f8eb 100644 --- a/tools/gfx/renderer-shared.cpp +++ b/tools/gfx/renderer-shared.cpp @@ -32,7 +32,7 @@ const Slang::Guid GfxGUID::IID_IResourceCommandEncoder = SLANG_UUID_IResourceCom const Slang::Guid GfxGUID::IID_ICommandBuffer = SLANG_UUID_ICommandBuffer; const Slang::Guid GfxGUID::IID_ICommandQueue = SLANG_UUID_ICommandQueue; -gfx::StageType translateStage(SlangStage slangStage) +StageType translateStage(SlangStage slangStage) { switch (slangStage) { @@ -86,12 +86,12 @@ IResource* TextureResource::getInterface(const Slang::Guid& guid) SLANG_NO_THROW IResource::Type SLANG_MCALL TextureResource::getType() { return m_type; } SLANG_NO_THROW ITextureResource::Desc* SLANG_MCALL TextureResource::getDesc() { return &m_desc; } -gfx::StageType mapStage(SlangStage stage) +StageType mapStage(SlangStage stage) { switch( stage ) { default: - return gfx::StageType::Unknown; + return StageType::Unknown; case SLANG_STAGE_AMPLIFICATION: return gfx::StageType::Amplification; case SLANG_STAGE_ANY_HIT: return gfx::StageType::AnyHit; @@ -110,14 +110,21 @@ gfx::StageType mapStage(SlangStage stage) } } -IShaderObject* gfx::ShaderObjectBase::getInterface(const Guid& guid) +IResourceView* ResourceViewBase::getInterface(const Guid& guid) +{ + if (guid == GfxGUID::IID_ISlangUnknown || guid == GfxGUID::IID_IResourceView) + return static_cast<IResourceView*>(this); + return nullptr; +} + +IShaderObject* ShaderObjectBase::getInterface(const Guid& guid) { if (guid == GfxGUID::IID_ISlangUnknown || guid == GfxGUID::IID_IShaderObject) return static_cast<IShaderObject*>(this); return nullptr; } -bool gfx::ShaderObjectBase::_doesValueFitInExistentialPayload( +bool ShaderObjectBase::_doesValueFitInExistentialPayload( slang::TypeLayoutReflection* concreteTypeLayout, slang::TypeLayoutReflection* existentialTypeLayout) { @@ -176,21 +183,21 @@ bool gfx::ShaderObjectBase::_doesValueFitInExistentialPayload( return true; } -IShaderProgram* gfx::ShaderProgramBase::getInterface(const Guid& guid) +IShaderProgram* ShaderProgramBase::getInterface(const Guid& guid) { if (guid == GfxGUID::IID_ISlangUnknown || guid == GfxGUID::IID_IShaderProgram) return static_cast<IShaderProgram*>(this); return nullptr; } -IInputLayout* gfx::InputLayoutBase::getInterface(const Guid& guid) +IInputLayout* InputLayoutBase::getInterface(const Guid& guid) { if (guid == GfxGUID::IID_ISlangUnknown || guid == GfxGUID::IID_IInputLayout) return static_cast<IInputLayout*>(this); return nullptr; } -IFramebufferLayout* gfx::FramebufferLayoutBase::getInterface(const Guid& guid) +IFramebufferLayout* FramebufferLayoutBase::getInterface(const Guid& guid) { if (guid == GfxGUID::IID_ISlangUnknown || guid == GfxGUID::IID_IFramebufferLayout) return static_cast<IFramebufferLayout*>(this); @@ -260,18 +267,34 @@ SLANG_NO_THROW Result SLANG_MCALL RendererBase::getSlangSession(slang::ISession* return SLANG_OK; } -SLANG_NO_THROW Result SLANG_MCALL RendererBase::createShaderObject(slang::TypeReflection* type, IShaderObject** outObject) +SLANG_NO_THROW Result SLANG_MCALL RendererBase::createShaderObject( + slang::TypeReflection* type, + ShaderObjectContainerType container, + IShaderObject** outObject) { RefPtr<ShaderObjectLayoutBase> shaderObjectLayout; - SLANG_RETURN_FALSE_ON_FAIL(getShaderObjectLayout(type, shaderObjectLayout.writeRef())); + SLANG_RETURN_FALSE_ON_FAIL(getShaderObjectLayout(type, container, shaderObjectLayout.writeRef())); return createShaderObject(shaderObjectLayout, outObject); } Result RendererBase::getShaderObjectLayout( - slang::TypeReflection* type, - ShaderObjectLayoutBase** outLayout) + slang::TypeReflection* type, + ShaderObjectContainerType container, + ShaderObjectLayoutBase** outLayout) { RefPtr<ShaderObjectLayoutBase> shaderObjectLayout; + switch (container) + { + case ShaderObjectContainerType::StructuredBuffer: + type = slangContext.session->getContainerType(type, slang::ContainerType::StructuredBuffer); + break; + case ShaderObjectContainerType::Array: + type = slangContext.session->getContainerType(type, slang::ContainerType::UnsizedArray); + break; + default: + break; + } + if( !m_shaderObjectLayoutCache.TryGetValue(type, shaderObjectLayout) ) { auto typeLayout = slangContext.session->getTypeLayout(type); @@ -373,8 +396,8 @@ Result ShaderObjectBase::_getSpecializedShaderObjectType(ExtendedShaderObjectTyp SLANG_RETURN_ON_FAIL(collectSpecializationArgs(specializationArgs)); if (specializationArgs.getCount() == 0) { - shaderObjectType.componentID = getLayout()->getComponentID(); - shaderObjectType.slangType = getLayout()->getElementTypeLayout()->getType(); + shaderObjectType.componentID = getLayoutBase()->getComponentID(); + shaderObjectType.slangType = getLayoutBase()->getElementTypeLayout()->getType(); } else { @@ -387,6 +410,94 @@ Result ShaderObjectBase::_getSpecializedShaderObjectType(ExtendedShaderObjectTyp return SLANG_OK; } +Result ShaderObjectBase::setExistentialHeader( + slang::TypeReflection* existentialType, + slang::TypeReflection* concreteType, + ShaderOffset offset) +{ + // The first field of the tuple (offset zero) is the run-time type information + // (RTTI) ID for the concrete type being stored into the field. + // + // TODO: We need to be able to gather the RTTI type ID from `object` and then + // use `setData(offset, &TypeID, sizeof(TypeID))`. + + // The second field of the tuple (offset 8) is the ID of the "witness" for the + // conformance of the concrete type to the interface used by this field. + // + auto witnessTableOffset = offset; + witnessTableOffset.uniformOffset += 8; + // + // Conformances of a type to an interface are computed and then stored by the + // Slang runtime, so we can look up the ID for this particular conformance (which + // will create it on demand). + // + ComPtr<slang::ISession> slangSession; + SLANG_RETURN_ON_FAIL(getRenderer()->getSlangSession(slangSession.writeRef())); + // + // Note: If the type doesn't actually conform to the required interface for + // this sub-object range, then this is the point where we will detect that + // fact and error out. + // + uint32_t conformanceID = 0xFFFFFFFF; + SLANG_RETURN_ON_FAIL(slangSession->getTypeConformanceWitnessSequentialID( + concreteType, existentialType, &conformanceID)); + // + // Once we have the conformance ID, then we can write it into the object + // at the required offset. + // + SLANG_RETURN_ON_FAIL(setData(witnessTableOffset, &conformanceID, sizeof(conformanceID))); + + return SLANG_OK; +} + +ResourceViewBase* SimpleShaderObjectData::getResourceView( + RendererBase* device, + slang::TypeLayoutReflection* elementLayout, + slang::BindingType bindingType) +{ + if (!m_structuredBuffer) + { + // Create structured buffer resource if it has not been created. + IBufferResource::Desc desc = {}; + desc.allowedStates = + ResourceStateSet(ResourceState::ShaderResource, ResourceState::UnorderedAccess); + desc.defaultState = ResourceState::ShaderResource; + desc.elementSize = (int)elementLayout->getSize(); + desc.format = Format::Unknown; + desc.type = IResource::Type::Buffer; + desc.sizeInBytes = (size_t)m_ordinaryData.getCount(); + ComPtr<IBufferResource> bufferResource; + SLANG_RETURN_NULL_ON_FAIL(device->createBufferResource( + desc, m_ordinaryData.getBuffer(), bufferResource.writeRef())); + m_structuredBuffer = static_cast<BufferResource*>(bufferResource.get()); + + // Create read-only (shader-resource) and mutable (unordered access) views. + ComPtr<IResourceView> resourceView; + IResourceView::Desc viewDesc = {}; + viewDesc.format = Format::Unknown; + viewDesc.type = IResourceView::Type::ShaderResource; + SLANG_RETURN_NULL_ON_FAIL(device->createBufferView( + bufferResource.get(), viewDesc, resourceView.writeRef())); + m_structuredBufferView = static_cast<ResourceViewBase*>(resourceView.get()); + viewDesc.type = IResourceView::Type::UnorderedAccess; + SLANG_RETURN_NULL_ON_FAIL( + device->createBufferView( + bufferResource.get(), viewDesc, resourceView.writeRef())); + m_rwStructuredBufferView = static_cast<ResourceViewBase*>(resourceView.get()); + } + + switch (bindingType) + { + case slang::BindingType::RawBuffer: + return m_structuredBufferView.Ptr(); + case slang::BindingType::MutableRawBuffer: + return m_rwStructuredBufferView.Ptr(); + default: + SLANG_ASSERT(false && "Invalid binding type."); + return nullptr; + } +} + Result RendererBase::maybeSpecializePipeline( PipelineStateBase* currentPipeline, ShaderObjectBase* rootObject, @@ -425,11 +536,14 @@ Result RendererBase::maybeSpecializePipeline( specializationArgs.getCount(), specializedComponentType.writeRef(), diagnosticBlob.writeRef()); - if (compileRs != SLANG_OK) + if (diagnosticBlob) { - printf("%s\n", (char*)diagnosticBlob->getBufferPointer()); - return SLANG_FAIL; + getDebugCallback()->handleMessage( + compileRs == SLANG_OK ? DebugMessageType::Warning : DebugMessageType::Error, + DebugMessageSource::Slang, + (char*)diagnosticBlob->getBufferPointer()); } + SLANG_RETURN_ON_FAIL(compileRs); // Now create specialized shader program using compiled binaries. ComPtr<IShaderProgram> specializedProgram; @@ -496,4 +610,3 @@ IDebugCallback* _getNullDebugCallback() } } // namespace gfx - diff --git a/tools/gfx/renderer-shared.h b/tools/gfx/renderer-shared.h index 9aedc8c74..ba0327cf4 100644 --- a/tools/gfx/renderer-shared.h +++ b/tools/gfx/renderer-shared.h @@ -242,6 +242,15 @@ protected: Desc m_desc; }; +class ResourceViewBase + : public IResourceView + , public Slang::ComObject +{ +public: + SLANG_COM_OBJECT_IUNKNOWN_ALL + IResourceView* getInterface(const Slang::Guid& guid); +}; + class RendererBase; typedef uint32_t ShaderComponentID; @@ -262,6 +271,13 @@ struct ExtendedShaderObjectTypeList componentIDs.add(component.componentID); components.add(slang::SpecializationArg{ slang::SpecializationArg::Kind::Type, component.slangType }); } + void addRange(const ExtendedShaderObjectTypeList& list) + { + for (Slang::Index i = 0; i < list.getCount(); i++) + { + add(list[i]); + } + } ExtendedShaderObjectType operator[](Slang::Index index) const { ExtendedShaderObjectType result; @@ -274,7 +290,7 @@ struct ExtendedShaderObjectTypeList componentIDs.clear(); components.clear(); } - Slang::Index getCount() + Slang::Index getCount() const { return componentIDs.getCount(); } @@ -289,9 +305,19 @@ protected: RendererBase* m_renderer; slang::TypeLayoutReflection* m_elementTypeLayout = nullptr; ShaderComponentID m_componentID = 0; + + /// The container type of this shader object. When `m_containerType` is `StructuredBuffer` or + /// `UnsizedArray`, this shader object represents a collection instead of a single object. + ShaderObjectContainerType m_containerType = ShaderObjectContainerType::None; + public: - static slang::TypeLayoutReflection* _unwrapParameterGroups(slang::TypeLayoutReflection* typeLayout) + ShaderObjectContainerType getContainerType() { return m_containerType; } + + static slang::TypeLayoutReflection* _unwrapParameterGroups( + slang::TypeLayoutReflection* typeLayout, + ShaderObjectContainerType& outContainerType) { + outContainerType = ShaderObjectContainerType::None; for (;;) { if (!typeLayout->getType()) @@ -299,12 +325,29 @@ public: if (auto elementTypeLayout = typeLayout->getElementTypeLayout()) typeLayout = elementTypeLayout; } - + switch (typeLayout->getKind()) + { + case slang::TypeReflection::Kind::Array: + SLANG_ASSERT(outContainerType == ShaderObjectContainerType::None); + outContainerType = ShaderObjectContainerType::Array; + typeLayout = typeLayout->getElementTypeLayout(); + return typeLayout; + case slang::TypeReflection::Kind::Resource: + { + if (typeLayout->getResourceShape() != SLANG_STRUCTURED_BUFFER) + break; + SLANG_ASSERT(outContainerType == ShaderObjectContainerType::None); + outContainerType = ShaderObjectContainerType::StructuredBuffer; + typeLayout = typeLayout->getElementTypeLayout(); + } + return typeLayout; + default: + break; + } switch (typeLayout->getKind()) { default: return typeLayout; - case slang::TypeReflection::Kind::ConstantBuffer: case slang::TypeReflection::Kind::ParameterBlock: typeLayout = typeLayout->getElementTypeLayout(); @@ -330,6 +373,29 @@ public: void initBase(RendererBase* renderer, slang::TypeLayoutReflection* elementTypeLayout); }; +class SimpleShaderObjectData +{ +public: + // Any "ordinary" / uniform data for this object + Slang::List<char> m_ordinaryData; + // The structured buffer resource used when the object represents a structured buffer. + Slang::RefPtr<BufferResource> m_structuredBuffer; + // The structured buffer resource view used when the object represents a structured buffer. + Slang::RefPtr<ResourceViewBase> m_structuredBufferView; + Slang::RefPtr<ResourceViewBase> m_rwStructuredBufferView; + + Slang::Index getCount() { return m_ordinaryData.getCount(); } + void setCount(Slang::Index count) { m_ordinaryData.setCount(count); } + char* getBuffer() { return m_ordinaryData.getBuffer(); } + + /// Returns a StructuredBuffer resource view for GPU access into the buffer content. + /// Creates a StructuredBuffer resource if it has not been created. + ResourceViewBase* getResourceView( + RendererBase* device, + slang::TypeLayoutReflection* elementLayout, + slang::BindingType bindingType); +}; + class ShaderObjectBase : public IShaderObject, public Slang::ComObject { protected: @@ -364,6 +430,8 @@ public: // this function will return a specialized type using the bound sub-objects' type as specialization argument. virtual Result getSpecializedShaderObjectType(ExtendedShaderObjectType* outType); + virtual Result collectSpecializationArgs(ExtendedShaderObjectTypeList& args) = 0; + RendererBase* getRenderer() { return m_layout->getDevice(); } SLANG_NO_THROW UInt SLANG_MCALL getEntryPointCount() SLANG_OVERRIDE { return 0; } @@ -375,17 +443,346 @@ public: return SLANG_OK; } - ShaderObjectLayoutBase* getLayout() - { - return m_layout; - } + ShaderObjectLayoutBase* getLayoutBase() { return m_layout; } SLANG_NO_THROW slang::TypeLayoutReflection* SLANG_MCALL getElementTypeLayout() SLANG_OVERRIDE { return m_layout->getElementTypeLayout(); } - virtual Result collectSpecializationArgs(ExtendedShaderObjectTypeList& args) = 0; + virtual SLANG_NO_THROW ShaderObjectContainerType SLANG_MCALL getContainerType() SLANG_OVERRIDE + { + return m_layout->getContainerType(); + } + + /// Sets the RTTI ID and RTTI witness table fields of an existential value. + Result setExistentialHeader( + slang::TypeReflection* existentialType, + slang::TypeReflection* concreteType, + ShaderOffset offset); +}; + +template<typename TShaderObjectImpl, typename TShaderObjectLayoutImpl, typename TShaderObjectData> +class ShaderObjectBaseImpl : public ShaderObjectBase +{ +protected: + TShaderObjectData m_data; + Slang::List<Slang::RefPtr<TShaderObjectImpl>> m_objects; + + // Specialization args for a StructuredBuffer object. + ExtendedShaderObjectTypeList m_structuredBufferSpecializationArgs; + +public: + TShaderObjectLayoutImpl* getLayout() + { + return static_cast<TShaderObjectLayoutImpl*>(m_layout.Ptr()); + } + + void* getBuffer() { return m_data.getBuffer(); } + size_t getBufferSize() { return (size_t)m_data.getCount(); } + + virtual SLANG_NO_THROW Result SLANG_MCALL + getObject(ShaderOffset const& offset, IShaderObject** outObject) SLANG_OVERRIDE + { + SLANG_ASSERT(outObject); + if (offset.bindingRangeIndex < 0) + return SLANG_E_INVALID_ARG; + auto layout = getLayout(); + if (offset.bindingRangeIndex >= layout->getBindingRangeCount()) + return SLANG_E_INVALID_ARG; + auto bindingRange = layout->getBindingRange(offset.bindingRangeIndex); + + returnComPtr(outObject, m_objects[bindingRange.subObjectIndex + offset.bindingArrayIndex]); + return SLANG_OK; + } + + virtual SLANG_NO_THROW Result SLANG_MCALL + setObject(ShaderOffset const& offset, IShaderObject* object) SLANG_OVERRIDE + { + auto layout = getLayout(); + auto subObject = static_cast<TShaderObjectImpl*>(object); + // There are three different cases in `setObject`. + // 1. `this` object represents a StructuredBuffer, and `object` is an + // element to be written into the StructuredBuffer. + // 2. `object` represents a StructuredBuffer and we are setting it into + // a StructuredBuffer typed field in `this` object. + // 3. We are setting `object` as an ordinary sub-object, e.g. an existential + // field, a constant buffer or a parameter block. + // We handle each case separately below. + + if (layout->getContainerType() != ShaderObjectContainerType::None) + { + // Case 1: + // We are setting an element into a `StructuredBuffer` object. + // We need to hold a reference to the element object, as well as + // writing uniform data to the plain buffer. + if (offset.bindingArrayIndex >= m_objects.getCount()) + { + m_objects.setCount(offset.bindingArrayIndex + 1); + auto stride = layout->getElementTypeLayout()->getStride(); + m_data.setCount(m_objects.getCount() * stride); + } + m_objects[offset.bindingArrayIndex] = subObject; + + ExtendedShaderObjectTypeList specializationArgs; + + auto payloadOffset = offset; + + // If the element type of the StructuredBuffer field is an existential type, + // we need to make sure to fill in the existential value header (RTTI ID and + // witness table IDs). + if (layout->getElementTypeLayout()->getKind() == slang::TypeReflection::Kind::Interface) + { + auto existentialType = layout->getElementTypeLayout()->getType(); + ExtendedShaderObjectType concreteType; + SLANG_RETURN_ON_FAIL(subObject->getSpecializedShaderObjectType(&concreteType)); + SLANG_RETURN_ON_FAIL( + setExistentialHeader(existentialType, concreteType.slangType, offset)); + payloadOffset.uniformOffset += 16; + + // If this object is a `StructuredBuffer<ISomeInterface>`, then the + // specialization argument should be the specialized type of the sub object + // itself. + specializationArgs.add(concreteType); + } + else + { + // If this object is a `StructuredBuffer<SomeConcreteType>`, then the + // specialization + // argument should come recursively from the sub object. + subObject->collectSpecializationArgs(specializationArgs); + } + SLANG_RETURN_ON_FAIL(setData( + payloadOffset, + subObject->m_data.getBuffer(), + (size_t)subObject->m_data.getCount())); + + // Compute specialization args for the structured buffer object. + // If we haven't filled anything to `m_structuredBufferSpecializationArgs` yet, + // use `specializationArgs` directly. + if (m_structuredBufferSpecializationArgs.getCount() == 0) + { + m_structuredBufferSpecializationArgs = Slang::_Move(specializationArgs); + } + else + { + // If `m_structuredBufferSpecializationArgs` already contains some arguments, we + // need to check if they are the same as `specializationArgs`, and replace + // anything that is different with `__Dynamic` because we cannot specialize the + // buffer type if the element types are not the same. + SLANG_ASSERT( + m_structuredBufferSpecializationArgs.getCount() == + specializationArgs.getCount()); + auto device = getRenderer(); + for (Slang::Index i = 0; i < m_structuredBufferSpecializationArgs.getCount(); i++) + { + if (m_structuredBufferSpecializationArgs[i].componentID != + specializationArgs[i].componentID) + { + auto dynamicType = device->slangContext.session->getDynamicType(); + m_structuredBufferSpecializationArgs.componentIDs[i] = + device->shaderCache.getComponentId(dynamicType); + m_structuredBufferSpecializationArgs.components[i] = + slang::SpecializationArg::fromType(dynamicType); + } + } + } + return SLANG_OK; + } + + // Case 2 & 3, setting object as an StructuredBuffer, ConstantBuffer, ParameterBlock or + // existential value. + + if (offset.bindingRangeIndex < 0) + return SLANG_E_INVALID_ARG; + if (offset.bindingRangeIndex >= layout->getBindingRangeCount()) + return SLANG_E_INVALID_ARG; + + auto bindingRangeIndex = offset.bindingRangeIndex; + auto bindingRange = layout->getBindingRange(bindingRangeIndex); + + m_objects[bindingRange.subObjectIndex + offset.bindingArrayIndex] = subObject; + + switch (bindingRange.bindingType) + { + case slang::BindingType::ExistentialValue: + { + // If the range being assigned into represents an interface/existential-type + // leaf field, then we need to consider how the `object` being assigned here + // affects specialization. We may also need to assign some data from the + // sub-object into the ordinary data buffer for the parent object. + // + // A leaf field of interface type is laid out inside of the parent object + // as a tuple of `(RTTI, WitnessTable, Payload)`. The layout of these fields + // is a contract between the compiler and any runtime system, so we will + // need to rely on details of the binary layout. + + // We start by querying the layout/type of the concrete value that the + // application is trying to store into the field, and also the layout/type of + // the leaf existential-type field itself. + // + auto concreteTypeLayout = subObject->getElementTypeLayout(); + auto concreteType = concreteTypeLayout->getType(); + // + auto existentialTypeLayout = + layout->getElementTypeLayout()->getBindingRangeLeafTypeLayout( + bindingRangeIndex); + auto existentialType = existentialTypeLayout->getType(); + + // Fills in the first and second field of the tuple that specify RTTI type ID + // and witness table ID. + SLANG_RETURN_ON_FAIL(setExistentialHeader(existentialType, concreteType, offset)); + + // The third field of the tuple (offset 16) is the "payload" that is supposed to + // hold the data for a value of the given concrete type. + // + auto payloadOffset = offset; + payloadOffset.uniformOffset += 16; + + // There are two cases we need to consider here for how the payload might be + // used: + // + // * If the concrete type of the value being bound is one that can "fit" into + // the + // available payload space, then it should be stored in the payload. + // + // * If the concrete type of the value cannot fit in the payload space, then it + // will need to be stored somewhere else. + // + if (_doesValueFitInExistentialPayload(concreteTypeLayout, existentialTypeLayout)) + { + // If the value can fit in the payload area, then we will go ahead and copy + // its bytes into that area. + // + setData( + payloadOffset, subObject->m_data.getBuffer(), subObject->m_data.getCount()); + } + else + { + // If the value does *not *fit in the payload area, then there is nothing + // we can do at this point (beyond saving a reference to the sub-object, + // which was handled above). + // + // Once all the sub-objects have been set into the parent object, we can + // compute a specialized layout for it, and that specialized layout can tell + // us where the data for these sub-objects has been laid out. + return SLANG_E_NOT_IMPLEMENTED; + } + } + break; + case slang::BindingType::MutableRawBuffer: + case slang::BindingType::RawBuffer: + { + // If we are setting into a `StructuredBuffer` field, make sure we create and set + // the StructuredBuffer resource as well. + setResource( + offset, + subObject->m_data.getResourceView( + getRenderer(), + subObject->getElementTypeLayout(), + bindingRange.bindingType)); + } + break; + } + return SLANG_OK; + } + + // Appends all types that are used to specialize the element type of this shader object in + // `args` list. + virtual Result collectSpecializationArgs(ExtendedShaderObjectTypeList& args) override + { + auto device = getRenderer(); + auto& subObjectRanges = getLayout()->getSubObjectRanges(); + // The following logic is built on the assumption that all fields that involve + // existential types (and therefore require specialization) will results in a sub-object + // range in the type layout. This allows us to simply scan the sub-object ranges to find + // out all specialization arguments. + Slang::Index subObjectRangeCount = subObjectRanges.getCount(); + + for (Slang::Index subObjectRangeIndex = 0; subObjectRangeIndex < subObjectRangeCount; + subObjectRangeIndex++) + { + auto const& subObjectRange = subObjectRanges[subObjectRangeIndex]; + auto const& bindingRange = + getLayout()->getBindingRange(subObjectRange.bindingRangeIndex); + + Slang::Index oldArgsCount = args.getCount(); + + Slang::Index count = bindingRange.count; + + for (Slang::Index subObjectIndexInRange = 0; subObjectIndexInRange < count; + subObjectIndexInRange++) + { + ExtendedShaderObjectTypeList typeArgs; + + auto subObject = m_objects[bindingRange.subObjectIndex + subObjectIndexInRange]; + + if (!subObject) + continue; + + switch (bindingRange.bindingType) + { + case slang::BindingType::ExistentialValue: + { + // A binding type of `ExistentialValue` means the sub-object represents a + // interface-typed field. In this case the specialization argument for this + // field is the actual specialized type of the bound shader object. If the + // shader object's type is an ordinary type without existential fields, then + // the type argument will simply be the ordinary type. But if the sub + // object's type is itself a specialized type, we need to make sure to use + // that type as the specialization argument. + + ExtendedShaderObjectType specializedSubObjType; + SLANG_RETURN_ON_FAIL( + subObject->getSpecializedShaderObjectType(&specializedSubObjType)); + typeArgs.add(specializedSubObjType); + break; + } + case slang::BindingType::ParameterBlock: + case slang::BindingType::ConstantBuffer: + // Currently we only handle the case where the field's type is + // `ParameterBlock<SomeStruct>` or `ConstantBuffer<SomeStruct>`, where + // `SomeStruct` is a struct type (not directly an interface type). In this case, + // we just recursively collect the specialization arguments from the bound sub + // object. + SLANG_RETURN_ON_FAIL(subObject->collectSpecializationArgs(typeArgs)); + // TODO: we need to handle the case where the field is of the form + // `ParameterBlock<IFoo>`. We should treat this case the same way as the + // `ExistentialValue` case here, but currently we lack a mechanism to + // distinguish the two scenarios. + break; + case slang::BindingType::RawBuffer: + case slang::BindingType::MutableRawBuffer: + typeArgs.addRange(subObject->m_structuredBufferSpecializationArgs); + break; + } + + auto addedTypeArgCountForCurrentRange = args.getCount() - oldArgsCount; + if (addedTypeArgCountForCurrentRange == 0) + { + args.addRange(typeArgs); + } + else + { + // If type arguments for each elements in the array is different, use + // `__Dynamic` type for the differing argument to disable specialization. + SLANG_ASSERT(addedTypeArgCountForCurrentRange == typeArgs.getCount()); + for (Slang::Index i = 0; i < addedTypeArgCountForCurrentRange; i++) + { + if (args[i + oldArgsCount].componentID != typeArgs[i].componentID) + { + auto dynamicType = device->slangContext.session->getDynamicType(); + args.componentIDs[i + oldArgsCount] = + device->shaderCache.getComponentId(dynamicType); + args.components[i + oldArgsCount] = + slang::SpecializationArg::fromType(dynamicType); + } + } + } + } + } + return SLANG_OK; + } }; class ShaderProgramBase : public IShaderProgram, public Slang::ComObject @@ -571,10 +968,14 @@ public: virtual SLANG_NO_THROW Result SLANG_MCALL getSlangSession(slang::ISession** outSlangSession) SLANG_OVERRIDE; IDevice* getInterface(const Slang::Guid& guid); - virtual SLANG_NO_THROW Result SLANG_MCALL createShaderObject(slang::TypeReflection* type, IShaderObject** outObject) SLANG_OVERRIDE; + virtual SLANG_NO_THROW Result SLANG_MCALL createShaderObject( + slang::TypeReflection* type, + ShaderObjectContainerType containerType, + IShaderObject** outObject) SLANG_OVERRIDE; Result getShaderObjectLayout( slang::TypeReflection* type, + ShaderObjectContainerType container, ShaderObjectLayoutBase** outLayout); public: diff --git a/tools/gfx/vulkan/render-vk.cpp b/tools/gfx/vulkan/render-vk.cpp index f330d95a8..643c41394 100644 --- a/tools/gfx/vulkan/render-vk.cpp +++ b/tools/gfx/vulkan/render-vk.cpp @@ -35,6 +35,9 @@ #ifdef Always #undef Always #endif +#ifdef None +#undef None +#endif namespace gfx { using namespace Slang; @@ -224,17 +227,9 @@ public: } }; - class ResourceViewImpl : public IResourceView, public ComObject + class ResourceViewImpl : public ResourceViewBase { public: - SLANG_COM_OBJECT_IUNKNOWN_ALL - IResourceView* getInterface(const Guid& guid) - { - if (guid == GfxGUID::IID_ISlangUnknown || guid == GfxGUID::IID_IResourceView) - return static_cast<IResourceView*>(this); - return nullptr; - } - public: enum class ViewType { Texture, @@ -810,6 +805,10 @@ public: Index count; Index baseIndex; + /// An index into the sub-object array if this binding range is treated + /// as a sub-object. + Index subObjectIndex; + /// The `binding` offset to apply for this range uint32_t bindingOffset; @@ -903,6 +902,11 @@ public: VKDevice* m_renderer; slang::TypeLayoutReflection* m_elementTypeLayout; + /// The container type of this shader object. When `m_containerType` is + /// `StructuredBuffer` or `UnsizedArray`, this shader object represents a collection + /// instead of a single object. + ShaderObjectContainerType m_containerType = ShaderObjectContainerType::None; + List<BindingRangeInfo> m_bindingRanges; List<SubObjectRangeInfo> m_subObjectRanges; @@ -1240,15 +1244,28 @@ public: typeLayout->getBindingRangeLeafTypeLayout(r); Index baseIndex = 0; + Index subObjectIndex = 0; switch (slangBindingType) { case slang::BindingType::ConstantBuffer: case slang::BindingType::ParameterBlock: case slang::BindingType::ExistentialValue: baseIndex = m_subObjectCount; + subObjectIndex = baseIndex; m_subObjectCount += count; break; - + case slang::BindingType::RawBuffer: + case slang::BindingType::MutableRawBuffer: + if (slangLeafTypeLayout->getType()->getElementType() != nullptr) + { + // A structured buffer occupies both a resource slot and + // a sub-object slot. + subObjectIndex = m_subObjectCount; + m_subObjectCount += count; + } + baseIndex = m_resourceViewCount; + m_resourceViewCount += count; + break; case slang::BindingType::Sampler: baseIndex = m_samplerCount; m_samplerCount += count; @@ -1281,6 +1298,7 @@ public: bindingRangeInfo.bindingType = slangBindingType; bindingRangeInfo.count = count; bindingRangeInfo.baseIndex = baseIndex; + bindingRangeInfo.subObjectIndex = subObjectIndex; // We'd like to extract the information on the GLSL/SPIR-V // `binding` that this range should bind into (or whatever @@ -1408,7 +1426,7 @@ public: Result setElementTypeLayout( slang::TypeLayoutReflection* typeLayout) { - typeLayout = _unwrapParameterGroups(typeLayout); + typeLayout = _unwrapParameterGroups(typeLayout, m_containerType); m_elementTypeLayout = typeLayout; m_totalOrdinaryDataSize = (uint32_t) typeLayout->getSize(); @@ -1556,8 +1574,6 @@ public: BindingRangeInfo const& getBindingRange(Index index) { return m_bindingRanges[index]; } - slang::TypeLayoutReflection* getElementTypeLayout() { return m_elementTypeLayout; } - Index getResourceViewCount() { return m_resourceViewCount; } Index getSamplerCount() { return m_samplerCount; } Index getCombinedTextureSamplerCount() { return m_combinedTextureSamplerCount; } @@ -1593,6 +1609,8 @@ public: m_subObjectRanges = builder->m_subObjectRanges; m_totalOrdinaryDataSize = builder->m_totalOrdinaryDataSize; + m_containerType = builder->m_containerType; + // Create VkDescriptorSetLayout for all descriptor sets. for (auto& descriptorSetInfo : m_descriptorSetInfos) { @@ -2194,7 +2212,7 @@ public: ConstArrayView<VkPushConstantRange> pushConstantRanges; }; - class ShaderObjectImpl : public ShaderObjectBase + class ShaderObjectImpl : public ShaderObjectBaseImpl<ShaderObjectImpl, ShaderObjectLayoutImpl, SimpleShaderObjectData> { public: static Result create( @@ -2220,25 +2238,14 @@ public: return SLANG_OK; } - ShaderObjectLayoutImpl* getLayout() - { - return static_cast<ShaderObjectLayoutImpl*>(m_layout.Ptr()); - } - - SLANG_NO_THROW slang::TypeLayoutReflection* SLANG_MCALL getElementTypeLayout() - SLANG_OVERRIDE - { - return m_layout->getElementTypeLayout(); - } - SLANG_NO_THROW Result SLANG_MCALL setData(ShaderOffset const& inOffset, void const* data, size_t inSize) SLANG_OVERRIDE { Index offset = inOffset.uniformOffset; Index size = inSize; - char* dest = m_ordinaryData.getBuffer(); - Index availableSize = m_ordinaryData.getCount(); + char* dest = m_data.getBuffer(); + Index availableSize = m_data.getCount(); // TODO: We really should bounds-check access rather than silently ignoring sets // that are too large, but we have several test cases that set more data than @@ -2259,148 +2266,6 @@ public: return SLANG_OK; } - virtual SLANG_NO_THROW Result SLANG_MCALL - setObject(ShaderOffset const& offset, IShaderObject* object) SLANG_OVERRIDE - { - if (offset.bindingRangeIndex < 0) - return SLANG_E_INVALID_ARG; - auto layout = getLayout(); - if (offset.bindingRangeIndex >= layout->getBindingRangeCount()) - return SLANG_E_INVALID_ARG; - - auto subObject = static_cast<ShaderObjectImpl*>(object); - - auto bindingRangeIndex = offset.bindingRangeIndex; - auto& bindingRange = layout->getBindingRange(bindingRangeIndex); - - m_objects[bindingRange.baseIndex + offset.bindingArrayIndex] = subObject; - - // If the range being assigned into represents an interface/existential-type leaf field, - // then we need to consider how the `object` being assigned here affects specialization. - // We may also need to assign some data from the sub-object into the ordinary data - // buffer for the parent object. - // - if (bindingRange.bindingType == slang::BindingType::ExistentialValue) - { - // A leaf field of interface type is laid out inside of the parent object - // as a tuple of `(RTTI, WitnessTable, Payload)`. The layout of these fields - // is a contract between the compiler and any runtime system, so we will - // need to rely on details of the binary layout. - - // We start by querying the layout/type of the concrete value that the application - // is trying to store into the field, and also the layout/type of the leaf - // existential-type field itself. - // - auto concreteTypeLayout = subObject->getElementTypeLayout(); - auto concreteType = concreteTypeLayout->getType(); - // - auto existentialTypeLayout = - layout->getElementTypeLayout()->getBindingRangeLeafTypeLayout( - bindingRangeIndex); - auto existentialType = existentialTypeLayout->getType(); - - // The first field of the tuple (offset zero) is the run-time type information - // (RTTI) ID for the concrete type being stored into the field. - // - // TODO: We need to be able to gather the RTTI type ID from `object` and then - // use `setData(offset, &TypeID, sizeof(TypeID))`. - - // The second field of the tuple (offset 8) is the ID of the "witness" for the - // conformance of the concrete type to the interface used by this field. - // - auto witnessTableOffset = offset; - witnessTableOffset.uniformOffset += 8; - // - // Conformances of a type to an interface are computed and then stored by the - // Slang runtime, so we can look up the ID for this particular conformance (which - // will create it on demand). - // - ComPtr<slang::ISession> slangSession; - SLANG_RETURN_ON_FAIL(getRenderer()->getSlangSession(slangSession.writeRef())); - // - // Note: If the type doesn't actually conform to the required interface for - // this sub-object range, then this is the point where we will detect that - // fact and error out. - // - uint32_t conformanceID = 0xFFFFFFFF; - SLANG_RETURN_ON_FAIL(slangSession->getTypeConformanceWitnessSequentialID( - concreteType, existentialType, &conformanceID)); - // - // Once we have the conformance ID, then we can write it into the object - // at the required offset. - // - SLANG_RETURN_ON_FAIL( - setData(witnessTableOffset, &conformanceID, sizeof(conformanceID))); - - // The third field of the tuple (offset 16) is the "payload" that is supposed to - // hold the data for a value of the given concrete type. - // - auto payloadOffset = offset; - payloadOffset.uniformOffset += 16; - - // There are two cases we need to consider here for how the payload might be used: - // - // * If the concrete type of the value being bound is one that can "fit" into the - // available payload space, then it should be stored in the payload. - // - // * If the concrete type of the value cannot fit in the payload space, then it - // will need to be stored somewhere else. - // - if (_doesValueFitInExistentialPayload(concreteTypeLayout, existentialTypeLayout)) - { - // If the value can fit in the payload area, then we will go ahead and copy - // its bytes into that area. - // - setData( - payloadOffset, - subObject->m_ordinaryData.getBuffer(), - subObject->m_ordinaryData.getCount()); - } - else - { - // If the value does *not *fit in the payload area, then there is nothing - // we can do at this point (beyond saving a reference to the sub-object, which - // was handled above). - // - // Once all the sub-objects have been set into the parent object, we can - // compute a specialized layout for it, and that specialized layout can tell - // us where the data for these sub-objects has been laid out. - } - } - - return SLANG_OK; - } - - virtual SLANG_NO_THROW Result SLANG_MCALL - getObject(ShaderOffset const& offset, IShaderObject** outObject) SLANG_OVERRIDE - { - SLANG_ASSERT(outObject); - if (offset.bindingRangeIndex < 0) - return SLANG_E_INVALID_ARG; - auto layout = getLayout(); - if (offset.bindingRangeIndex >= layout->getBindingRangeCount()) - return SLANG_E_INVALID_ARG; - auto& bindingRange = layout->getBindingRange(offset.bindingRangeIndex); - - auto object = m_objects[bindingRange.baseIndex + offset.bindingArrayIndex].Ptr(); - returnComPtr(outObject, object); - - // auto& subObjectRange = - // m_layout->getSubObjectRange(bindingRange.subObjectRangeIndex); *outObject = - // m_objects[subObjectRange.baseIndex + offset.bindingArrayIndex]; - - return SLANG_OK; - -#if 0 - SLANG_ASSERT(bindingRange.descriptorSetIndex >= 0); - SLANG_ASSERT(bindingRange.descriptorSetIndex < m_descriptorSets.getCount()); - auto& descriptorSet = m_descriptorSets[bindingRange.descriptorSetIndex]; - - descriptorSet->setConstantBuffer(bindingRange.rangeIndexInDescriptorSet, offset.bindingArrayIndex, buffer); - return SLANG_OK; -#endif - } - SLANG_NO_THROW Result SLANG_MCALL setResource(ShaderOffset const& offset, IResourceView* resourceView) SLANG_OVERRIDE { @@ -2450,68 +2315,6 @@ public: return SLANG_OK; } - public: - // Appends all types that are used to specialize the element type of this shader object in - // `args` list. - virtual Result collectSpecializationArgs(ExtendedShaderObjectTypeList& args) override - { - auto& subObjectRanges = getLayout()->getSubObjectRanges(); - // The following logic is built on the assumption that all fields that involve - // existential types (and therefore require specialization) will results in a sub-object - // range in the type layout. This allows us to simply scan the sub-object ranges to find - // out all specialization arguments. - Index subObjectRangeCount = subObjectRanges.getCount(); - for (Index subObjectRangeIndex = 0; subObjectRangeIndex < subObjectRangeCount; - subObjectRangeIndex++) - { - auto const& subObjectRange = subObjectRanges[subObjectRangeIndex]; - auto const& bindingRange = - getLayout()->getBindingRange(subObjectRange.bindingRangeIndex); - - Index count = bindingRange.count; - SLANG_ASSERT(count == 1); - - Index subObjectIndexInRange = 0; - auto subObject = m_objects[bindingRange.baseIndex + subObjectIndexInRange]; - - switch (bindingRange.bindingType) - { - case slang::BindingType::ExistentialValue: - { - // A binding type of `ExistentialValue` means the sub-object represents a - // interface-typed field. In this case the specialization argument for this - // field is the actual specialized type of the bound shader object. If the - // shader object's type is an ordinary type without existential fields, then - // the type argument will simply be the ordinary type. But if the sub - // object's type is itself a specialized type, we need to make sure to use - // that type as the specialization argument. - - ExtendedShaderObjectType specializedSubObjType; - SLANG_RETURN_ON_FAIL( - subObject->getSpecializedShaderObjectType(&specializedSubObjType)); - args.add(specializedSubObjType); - break; - } - case slang::BindingType::ParameterBlock: - case slang::BindingType::ConstantBuffer: - // Currently we only handle the case where the field's type is - // `ParameterBlock<SomeStruct>` or `ConstantBuffer<SomeStruct>`, where - // `SomeStruct` is a struct type (not directly an interface type). In this case, - // we just recursively collect the specialization arguments from the bound sub - // object. - SLANG_RETURN_ON_FAIL(subObject->collectSpecializationArgs(args)); - // TODO: we need to handle the case where the field is of the form - // `ParameterBlock<IFoo>`. We should treat this case the same way as the - // `ExistentialValue` case here, but currently we lack a mechanism to - // distinguish the two scenarios. - break; - } - // TODO: need to handle another case where specialization happens on resources - // fields e.g. `StructuredBuffer<IFoo>`. - } - return SLANG_OK; - } - protected: friend class RootShaderObjectLayout; @@ -2533,8 +2336,8 @@ public: size_t uniformSize = layout->getElementTypeLayout()->getSize(); if (uniformSize) { - m_ordinaryData.setCount(uniformSize); - memset(m_ordinaryData.getBuffer(), 0, uniformSize); + m_data.setCount(uniformSize); + memset(m_data.getBuffer(), 0, uniformSize); } #if 0 @@ -2583,7 +2386,7 @@ public: RefPtr<ShaderObjectImpl> subObject; SLANG_RETURN_ON_FAIL( ShaderObjectImpl::create(device, subObjectLayout, subObject.writeRef())); - m_objects[bindingRangeInfo.baseIndex + i] = subObject; + m_objects[bindingRangeInfo.subObjectIndex + i] = subObject; } } @@ -2599,8 +2402,8 @@ public: size_t destSize, ShaderObjectLayoutImpl* specializedLayout) { - auto src = m_ordinaryData.getBuffer(); - auto srcSize = size_t(m_ordinaryData.getCount()); + auto src = m_data.getBuffer(); + auto srcSize = size_t(m_data.getCount()); SLANG_ASSERT(srcSize <= destSize); @@ -2675,7 +2478,7 @@ public: for (Slang::Index i = 0; i < count; ++i) { - auto subObject = m_objects[bindingRangeInfo.baseIndex + i]; + auto subObject = m_objects[bindingRangeInfo.subObjectIndex + i]; RefPtr<ShaderObjectLayoutImpl> subObjectLayout; SLANG_RETURN_ON_FAIL( @@ -3083,7 +2886,7 @@ public: { auto const& bindingRangeInfo = specializedLayout->getBindingRange(subObjectRange.bindingRangeIndex); auto count = bindingRangeInfo.count; - auto baseIndex = bindingRangeInfo.baseIndex; + auto subObjectIndex = bindingRangeInfo.subObjectIndex; auto subObjectLayout = subObjectRange.layout; @@ -3109,7 +2912,7 @@ public: // the ordinary data buffer (if needed) and any other // bindings it recursively contains. // - ShaderObjectImpl* subObject = m_objects[baseIndex + i]; + ShaderObjectImpl* subObject = m_objects[subObjectIndex + i]; subObject->bindAsConstantBuffer(encoder, context, objOffset, subObjectLayout); // When dealing with arrays of sub-objects, we need to make @@ -3129,7 +2932,7 @@ public: // from `ConstantBuffer<X>`, except that we call `bindAsParameterBlock` // instead (understandably). // - ShaderObjectImpl* subObject = m_objects[baseIndex + i]; + ShaderObjectImpl* subObject = m_objects[subObjectIndex + i]; subObject->bindAsParameterBlock(encoder, context, objOffset, subObjectLayout); objOffset += rangeStride; @@ -3162,13 +2965,16 @@ public: // have been handled as part of the buffer for a parent object // already. // - ShaderObjectImpl* subObject = m_objects[baseIndex + i]; + ShaderObjectImpl* subObject = m_objects[subObjectIndex + i]; subObject->bindAsValue(encoder, context, BindingOffset(objOffset), subObjectLayout); objOffset += objStride; } } break; - + case slang::BindingType::RawBuffer: + case slang::BindingType::MutableRawBuffer: + // No action needed for sub-objects bound though a `StructuredBuffer`. + break; default: SLANG_ASSERT(!"unsupported sub-object type"); return SLANG_FAIL; @@ -3297,17 +3103,12 @@ public: return SLANG_OK; } - /// Any "ordinary" / uniform data for this object - List<char> m_ordinaryData; - List<RefPtr<ResourceViewImpl>> m_resourceViews; List<RefPtr<SamplerStateImpl>> m_samplers; List<CombinedTextureSamplerSlot> m_combinedTextureSamplers; - List<RefPtr<ShaderObjectImpl>> m_objects; - // The version number of the transient resource heap that contains up-to-date // constant buffer content for this shader object. uint64_t m_upToDateConstantBufferHeapVersion; @@ -3345,7 +3146,9 @@ public: auto device = getDevice(); RefPtr<ShaderObjectLayoutImpl> layout; SLANG_RETURN_ON_FAIL(device->getShaderObjectLayout( - extendedType.slangType, (ShaderObjectLayoutBase**)layout.writeRef())); + extendedType.slangType, + m_layout->getContainerType(), + (ShaderObjectLayoutBase**)layout.writeRef())); returnRefPtrMove(outLayout, layout); return SLANG_OK; @@ -3389,7 +3192,7 @@ public: // // TODO: Can/should this function be renamed as just `bindAsPushConstantBuffer`? // - if (m_ordinaryData.getCount()) + if (m_data.getCount()) { // The index of the push constant range to bind should be // passed down as part of the `offset`, and we will increment @@ -3410,9 +3213,9 @@ public: // TODO: This would not be the case if specialization for interface-type // parameters led to the entry point having "pending" ordinary data. // - SLANG_ASSERT(pushConstantRange.size == (uint32_t) m_ordinaryData.getCount()); + SLANG_ASSERT(pushConstantRange.size == (uint32_t)m_data.getCount()); - auto pushConstantData = m_ordinaryData.getBuffer(); + auto pushConstantData = m_data.getBuffer(); encoder->m_api->vkCmdPushConstants( encoder->m_commandBuffer->m_commandBuffer, @@ -6292,9 +6095,10 @@ Result VKDevice::createBufferView(IBufferResource* buffer, IResourceView::Desc c return SLANG_FAIL; case IResourceView::Type::UnorderedAccess: + case IResourceView::Type::ShaderResource: // Is this a formatted view? // - if(desc.format == Format::Unknown) + if (desc.format == Format::Unknown) { // Buffer usage that doesn't involve formatting doesn't // require a view in Vulkan. @@ -6310,7 +6114,6 @@ Result VKDevice::createBufferView(IBufferResource* buffer, IResourceView::Desc c // it just like we would for a "sampled" buffer: // // FALLTHROUGH - case IResourceView::Type::ShaderResource: { VkBufferViewCreateInfo info = { VK_STRUCTURE_TYPE_BUFFER_VIEW_CREATE_INFO }; diff --git a/tools/render-test/render-test-main.cpp b/tools/render-test/render-test-main.cpp index c6f775312..5feeeaf21 100644 --- a/tools/render-test/render-test-main.cpp +++ b/tools/render-test/render-test-main.cpp @@ -232,13 +232,20 @@ struct AssignValsFromLayoutContext SlangResult assignTexture(ShaderCursor const& dstCursor, ShaderInputLayout::TextureVal* srcVal) { ComPtr<ITextureResource> texture; - SLANG_RETURN_ON_FAIL(ShaderRendererUtil::generateTextureResource( - srcVal->textureDesc, ResourceState::ShaderResource, device, texture)); + ResourceState defaultState = ResourceState::ShaderResource; + IResourceView::Type viewType = IResourceView::Type::ShaderResource; + + if (srcVal->textureDesc.isRWTexture) + { + defaultState = ResourceState::UnorderedAccess; + viewType = IResourceView::Type::UnorderedAccess; + } - // TODO: support UAV textures... + SLANG_RETURN_ON_FAIL(ShaderRendererUtil::generateTextureResource( + srcVal->textureDesc, defaultState, device, texture)); IResourceView::Desc viewDesc; - viewDesc.type = IResourceView::Type::ShaderResource; + viewDesc.type = viewType; viewDesc.format = texture->getDesc()->format; auto textureView = device->createTextureView( texture, @@ -1078,7 +1085,14 @@ static SlangResult _innerMain(Slang::StdWriters* stdWriters, SlangSession* sessi desc.requiredFeatures = requiredFeatureList.getBuffer(); desc.requiredFeatureCount = (int)requiredFeatureList.getCount(); - + for (int i = 0; i < options.slangArgCount; i++) + { + if (UnownedStringSlice(options.slangArgs[i]) == "-matrix-layout-column-major") + { + desc.slang.defaultMatrixLayoutMode = SLANG_MATRIX_LAYOUT_COLUMN_MAJOR; + } + } + desc.nvapiExtnSlot = int(nvapiExtnSlot); desc.slang.slangGlobalSession = session; diff --git a/tools/render-test/shader-input-layout.cpp b/tools/render-test/shader-input-layout.cpp index 43dfee804..bece936dd 100644 --- a/tools/render-test/shader-input-layout.cpp +++ b/tools/render-test/shader-input-layout.cpp @@ -416,7 +416,17 @@ namespace renderer_test String parseTypeName(TokenReader& parser) { - return parser.ReadWord(); + String typeName = parser.ReadWord(); + if (parser.AdvanceIf("<")) + { + StringBuilder sb; + sb << typeName << "<"; + sb << parseTypeName(parser); + sb << ">"; + parser.Read(">"); + return sb.ProduceString(); + } + return typeName; } RefPtr<ShaderInputLayout::Val> parseValExpr(TokenReader& parser) |
