From 7826afcaad78cc33c976bb3db3cdc9eada4c77e8 Mon Sep 17 00:00:00 2001 From: Ellie Hermaszewska Date: Wed, 18 Oct 2023 06:26:00 +0800 Subject: Type layouts for structured buffers with counters (#3269) * More tests for append structured buffer * Append and Consume structured buffer tests for DX12 * neaten * test wobble * Add counter layout information to append/consume structured buffers * add getRWStructuredBufferType * Correct definition of get size for append/consume structured buffers * tweak append structured buffer test * Allow initializing counter buffer in render test * vulkan test for consume structured buffer * Handle null counterVarLayout in getExplicitCounterBindingRangeOffset * remove dead code * Implement atomic counter increment/decrement for spirv * explicit spirv test * Add missing check on result * Hold on to counter resources --------- Co-authored-by: Yong He --- slang.h | 14 +++ source/slang/slang-ast-builder.cpp | 5 + source/slang/slang-ast-builder.h | 2 + source/slang/slang-emit-spirv-ops.h | 52 ++++++++++ source/slang/slang-emit-spirv.cpp | 14 +++ ...g-ir-lower-append-consume-structured-buffer.cpp | 17 ++-- source/slang/slang-lower-to-ir.cpp | 31 +++--- source/slang/slang-reflection-api.cpp | 105 ++++++++++++++++++--- source/slang/slang-type-layout.cpp | 83 ++++++++++++++-- source/slang/slang-type-layout.h | 22 +++++ tests/hlsl/append-structured-buffer.slang | 71 +++++++------- tests/hlsl/consume-structured-buffer.slang | 50 +++++----- tools/gfx-util/shader-cursor.cpp | 52 ++++++++++ tools/gfx-util/shader-cursor.h | 7 ++ tools/gfx/d3d12/d3d12-device.cpp | 3 +- tools/gfx/d3d12/d3d12-resource-views.h | 2 + tools/gfx/d3d12/d3d12-shader-object.cpp | 9 +- tools/gfx/d3d12/d3d12-shader-object.h | 1 + tools/render-test/render-test-main.cpp | 42 ++++++++- tools/render-test/shader-input-layout.cpp | 5 + tools/render-test/shader-input-layout.h | 4 + 21 files changed, 483 insertions(+), 108 deletions(-) diff --git a/slang.h b/slang.h index 78b56c546..fab62cf81 100644 --- a/slang.h +++ b/slang.h @@ -2227,6 +2227,8 @@ extern "C" SLANG_API SlangInt spReflectionTypeLayout_findFieldIndexByName(SlangReflectionTypeLayout* typeLayout, const char* nameBegin, const char* nameEnd); + SLANG_API SlangReflectionVariableLayout* spReflectionTypeLayout_GetExplicitCounter(SlangReflectionTypeLayout* typeLayout); + SLANG_API size_t spReflectionTypeLayout_GetElementStride(SlangReflectionTypeLayout* type, SlangParameterCategory category); SLANG_API SlangReflectionTypeLayout* spReflectionTypeLayout_GetElementTypeLayout(SlangReflectionTypeLayout* type); SLANG_API SlangReflectionVariableLayout* spReflectionTypeLayout_GetElementVarLayout(SlangReflectionTypeLayout* type); @@ -2253,6 +2255,7 @@ extern "C" SLANG_API SlangReflectionTypeLayout* spReflectionTypeLayout_getBindingRangeLeafTypeLayout(SlangReflectionTypeLayout* typeLayout, SlangInt index); SLANG_API SlangReflectionVariable* spReflectionTypeLayout_getBindingRangeLeafVariable(SlangReflectionTypeLayout* typeLayout, SlangInt index); SLANG_API SlangInt spReflectionTypeLayout_getFieldBindingRangeOffset(SlangReflectionTypeLayout* typeLayout, SlangInt fieldIndex); + SLANG_API SlangInt spReflectionTypeLayout_getExplicitCounterBindingRangeOffset(SlangReflectionTypeLayout* inTypeLayout); SLANG_API SlangInt spReflectionTypeLayout_getBindingRangeDescriptorSetIndex(SlangReflectionTypeLayout* typeLayout, SlangInt index); SLANG_API SlangInt spReflectionTypeLayout_getBindingRangeFirstDescriptorRangeIndex(SlangReflectionTypeLayout* typeLayout, SlangInt index); @@ -2711,6 +2714,11 @@ namespace slang return spReflectionTypeLayout_findFieldIndexByName((SlangReflectionTypeLayout*) this, nameBegin, nameEnd); } + VariableLayoutReflection* getExplicitCounter() + { + return (VariableLayoutReflection*) spReflectionTypeLayout_GetExplicitCounter((SlangReflectionTypeLayout*) this); + } + bool isArray() { return getType()->isArray(); } TypeLayoutReflection* unwrapArray() @@ -2871,6 +2879,12 @@ namespace slang fieldIndex); } + SlangInt getExplicitCounterBindingRangeOffset() + { + return spReflectionTypeLayout_getExplicitCounterBindingRangeOffset( + (SlangReflectionTypeLayout*) this); + } + TypeLayoutReflection* getBindingRangeLeafTypeLayout(SlangInt index) { return (TypeLayoutReflection*) spReflectionTypeLayout_getBindingRangeLeafTypeLayout( diff --git a/source/slang/slang-ast-builder.cpp b/source/slang/slang-ast-builder.cpp index 6a9aad257..ce13ab650 100644 --- a/source/slang/slang-ast-builder.cpp +++ b/source/slang/slang-ast-builder.cpp @@ -358,6 +358,11 @@ HLSLStructuredBufferType* ASTBuilder::getStructuredBufferType(Type* elementType) return as(getSpecializedBuiltinType(elementType, "HLSLStructuredBufferType")); } +HLSLRWStructuredBufferType* ASTBuilder::getRWStructuredBufferType(Type* elementType) +{ + return as(getSpecializedBuiltinType(elementType, "HLSLRWStructuredBufferType")); +} + SamplerStateType* ASTBuilder::getSamplerStateType() { return as(getSpecializedBuiltinType(nullptr, "HLSLStructuredBufferType")); diff --git a/source/slang/slang-ast-builder.h b/source/slang/slang-ast-builder.h index a5e1cd40c..aff3088ab 100644 --- a/source/slang/slang-ast-builder.h +++ b/source/slang/slang-ast-builder.h @@ -473,6 +473,8 @@ public: HLSLStructuredBufferType* getStructuredBufferType(Type* elementType); + HLSLRWStructuredBufferType* getRWStructuredBufferType(Type* elementType); + SamplerStateType* getSamplerStateType(); DifferentialPairType* getDifferentialPairType( diff --git a/source/slang/slang-emit-spirv-ops.h b/source/slang/slang-emit-spirv-ops.h index f64fc7f9a..f1a9ff3e4 100644 --- a/source/slang/slang-emit-spirv-ops.h +++ b/source/slang/slang-emit-spirv-ops.h @@ -2313,4 +2313,56 @@ SpvInst* emitOpExecutionModeIdLocalSizeId( zSize ); } + +template +SpvInst* emitOpAtomicIIncrement( + SpvInstParent* parent, + IRInst* inst, + const T1& idResultType, + const T2& pointer, + const T3& memory, + const T4& semantics +) +{ + static_assert(isSingular); + static_assert(isSingular); + static_assert(isSingular); + static_assert(isSingular); + return emitInst( + parent, + inst, + SpvOpAtomicIIncrement, + idResultType, + kResultID, + pointer, + memory, + semantics + ); +} + +template +SpvInst* emitOpAtomicIDecrement( + SpvInstParent* parent, + IRInst* inst, + const T1& idResultType, + const T2& pointer, + const T3& memory, + const T4& semantics +) +{ + static_assert(isSingular); + static_assert(isSingular); + static_assert(isSingular); + static_assert(isSingular); + return emitInst( + parent, + inst, + SpvOpAtomicIDecrement, + idResultType, + kResultID, + pointer, + memory, + semantics + ); +} #endif // SLANG_IN_SPIRV_EMIT_CONTEXT diff --git a/source/slang/slang-emit-spirv.cpp b/source/slang/slang-emit-spirv.cpp index 16a88b8af..aa21127e9 100644 --- a/source/slang/slang-emit-spirv.cpp +++ b/source/slang/slang-emit-spirv.cpp @@ -2390,6 +2390,20 @@ struct SPIRVEmitContext return emitImageStore(parent, as(inst)); case kIROp_ImageSubscript: return emitImageSubscript(parent, as(inst)); + case kIROp_AtomicCounterIncrement: + { + IRBuilder builder{inst}; + const auto memoryScope = emitIntConstant(IRIntegerValue{SpvScopeDevice}, builder.getUIntType()); + const auto memorySemantics = emitIntConstant(IRIntegerValue{SpvMemorySemanticsMaskNone}, builder.getUIntType()); + return emitOpAtomicIIncrement(parent, inst, inst->getFullType(), inst->getOperand(0), memoryScope, memorySemantics); + } + case kIROp_AtomicCounterDecrement: + { + IRBuilder builder{inst}; + const auto memoryScope = emitIntConstant(IRIntegerValue{SpvScopeDevice}, builder.getUIntType()); + const auto memorySemantics = emitIntConstant(IRIntegerValue{SpvMemorySemanticsMaskNone}, builder.getUIntType()); + return emitOpAtomicIDecrement(parent, inst, inst->getFullType(), inst->getOperand(0), memoryScope, memorySemantics); + } } } diff --git a/source/slang/slang-ir-lower-append-consume-structured-buffer.cpp b/source/slang/slang-ir-lower-append-consume-structured-buffer.cpp index 9df5f4e7e..ed04541ef 100644 --- a/source/slang/slang-ir-lower-append-consume-structured-buffer.cpp +++ b/source/slang/slang-ir-lower-append-consume-structured-buffer.cpp @@ -154,14 +154,15 @@ namespace Slang builder.setInsertInto(getDimensionsFunc); builder.emitBlock(); auto bufferParam = builder.emitParam(structType); - auto counterBuffer = builder.emitFieldExtract(counterBufferType, bufferParam, counterBufferKey); - IRInst* getCounterPtrArgs[] = { counterBuffer, builder.getIntValue(builder.getIntType(), 0) }; - auto counterBufferPtr = builder.emitIntrinsicInst(builder.getPtrType(builder.getIntType()), kIROp_RWStructuredBufferGetElementPtr, 2, getCounterPtrArgs); - auto counter = builder.emitLoad(counterBufferPtr); - counter = builder.emitCast(builder.getUIntType(), counter); - auto stride = builder.getIntValue(builder.getUIntType(), elementSize.getStride()); - IRInst* vecArgs[] = { counter, stride }; - builder.emitReturn(builder.emitMakeVector(uint2Type, 2, vecArgs)); + auto elementBuffer = builder.emitFieldExtract(elementBufferType, bufferParam, elementBufferKey); + + const auto dim = builder.emitIntrinsicInst( + uint2Type, + kIROp_StructuredBufferGetDimensions, + 1, + &elementBuffer + ); + builder.emitReturn(dim); } // Replace all insts with synthesized functions. diff --git a/source/slang/slang-lower-to-ir.cpp b/source/slang/slang-lower-to-ir.cpp index c90230c1f..2813918b6 100644 --- a/source/slang/slang-lower-to-ir.cpp +++ b/source/slang/slang-lower-to-ir.cpp @@ -8633,6 +8633,23 @@ struct DeclLoweringVisitor : DeclVisitor return v; } + void addSpecializedForTargetDecorations(IRInst* inst, Decl* decl) + { + // If this declaration was marked as being an intrinsic for a particular + // target, then we should reflect that here. + for (auto targetMod : decl->getModifiersOfType()) + { + // `targetMod` indicates that this particular declaration represents + // a specialized definition of the particular function for the given + // target, and we need to reflect that at the IR level. + + auto targetName = targetMod->targetToken.getContent(); + auto targetCap = findCapabilityAtom(targetName); + + getBuilder()->addTargetDecoration(inst, CapabilitySet(targetCap)); + } + } + // Attach target-intrinsic decorations to an instruction, // based on modifiers on an AST declaration. void addTargetIntrinsicDecorations( @@ -9170,19 +9187,7 @@ struct DeclLoweringVisitor : DeclVisitor getBuilder()->addHighLevelDeclDecoration(irFunc, decl); - // If this declaration was marked as being an intrinsic for a particular - // target, then we should reflect that here. - for (auto targetMod : decl->getModifiersOfType()) - { - // `targetMod` indicates that this particular declaration represents - // a specialized definition of the particular function for the given - // target, and we need to reflect that at the IR level. - - auto targetName = targetMod->targetToken.getContent(); - auto targetCap = findCapabilityAtom(targetName); - - getBuilder()->addTargetDecoration(irFunc, CapabilitySet(targetCap)); - } + addSpecializedForTargetDecorations(irFunc, decl); // If this declaration was marked as having a target-specific lowering // for a particular target, then handle that here. diff --git a/source/slang/slang-reflection-api.cpp b/source/slang/slang-reflection-api.cpp index a52b7b886..9d687245f 100644 --- a/source/slang/slang-reflection-api.cpp +++ b/source/slang/slang-reflection-api.cpp @@ -935,6 +935,14 @@ SLANG_API SlangInt spReflectionTypeLayout_findFieldIndexByName(SlangReflectionTy return -1; } +SLANG_API SlangReflectionVariableLayout* spReflectionTypeLayout_GetExplicitCounter(SlangReflectionTypeLayout* inTypeLayout) +{ + const auto typeLayout = convert(inTypeLayout); + if(const auto structuredBufferTypeLayout = as(typeLayout)) + return (SlangReflectionVariableLayout*) structuredBufferTypeLayout->counterVarLayout.Ptr(); + return nullptr; +} + SLANG_API size_t spReflectionTypeLayout_GetElementStride(SlangReflectionTypeLayout* inTypeLayout, SlangParameterCategory category) { auto typeLayout = convert(inTypeLayout); @@ -1784,6 +1792,75 @@ namespace Slang m_extendedInfo->m_bindingRanges.add(bindingRange); m_extendedInfo->m_subObjectRanges.add(subObjectRange); } + else if(const auto structuredBufferTypeLayout = as(typeLayout)) + { + // For structured buffers we expect them to consume a single + // resource descriptor slot (not counting the possible counter + // buffer) + SLANG_ASSERT(typeLayout->resourceInfos.getCount() == 1); + const auto& resInfo = typeLayout->resourceInfos[0]; + + const auto bindingType = as(typeLayout->getType()) + ? SLANG_BINDING_TYPE_RAW_BUFFER + : SLANG_BINDING_TYPE_MUTABLE_RAW_BUFFER; + + // We now allocate a descriptor range for this buffer + TypeLayout::ExtendedInfo::DescriptorRangeInfo descriptorRange; + descriptorRange.kind = resInfo.kind; + descriptorRange.bindingType = bindingType; + // Note that we don't use resInfo.count here, as each + // structuredBufferType is essentially a struct of 2 fields + // (elements, counter) and not an array of length 2. + SLANG_ASSERT(resInfo.count != 2 || structuredBufferTypeLayout->counterVarLayout); + SLANG_ASSERT(resInfo.count != 1 || !structuredBufferTypeLayout->counterVarLayout); + descriptorRange.count = multiplier; + descriptorRange.indexOffset = _calcIndexOffset(path.primary, resInfo.kind); + + Int descriptorSetIndex = _findOrAddDescriptorSet(_calcSpaceOffset(path.primary, resInfo.kind)); + const RefPtr descriptorSet + = m_extendedInfo->m_descriptorSets[descriptorSetIndex]; + auto descriptorRangeIndex = descriptorSet->descriptorRanges.getCount(); + descriptorSet->descriptorRanges.add(descriptorRange); + + // We will map the elements buffer to a single binding range + TypeLayout::ExtendedInfo::BindingRangeInfo bindingRange; + bindingRange.leafTypeLayout = typeLayout; + bindingRange.leafVariable = path.primary ? path.primary->var->getVariable() : nullptr; + bindingRange.bindingType = bindingType; + bindingRange.count = multiplier; + bindingRange.descriptorSetIndex = descriptorSetIndex; + bindingRange.firstDescriptorRangeIndex = descriptorRangeIndex; + bindingRange.descriptorRangeCount = 1; + + auto bindingRangeIndex = m_extendedInfo->m_bindingRanges.getCount(); + m_extendedInfo->m_bindingRanges.add(bindingRange); + + // We also make sure to report it as a sub-object range. + TypeLayout::ExtendedInfo::SubObjectRangeInfo subObjectRange; + subObjectRange.bindingRangeIndex = bindingRangeIndex; + subObjectRange.offsetVarLayout = createOffsetVarLayout(typeLayout, path); + subObjectRange.spaceOffset = 0; + m_extendedInfo->m_subObjectRanges.add(subObjectRange); + + // If we have an associated counter for this structured buffer, + // add its ranges + if(structuredBufferTypeLayout->counterVarLayout) + { + ExtendedBindingRangePath counterPath( + path, + structuredBufferTypeLayout->counterVarLayout + ); + // This should always be 1, because it comes after the + // single binding range we just added + structuredBufferTypeLayout->counterVarLayout->bindingRangeOffset = + m_extendedInfo->m_bindingRanges.getCount() - bindingRangeIndex; + addRangesRec( + structuredBufferTypeLayout->counterVarLayout->typeLayout, + counterPath, + multiplier + ); + } + } else { // Here we have the catch-all case that handles "leaf" fields @@ -1940,19 +2017,7 @@ 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 (const auto structuredBufferTypeLayout = as(typeLayout)) - { - TypeLayout::ExtendedInfo::SubObjectRangeInfo subObjectRange; - subObjectRange.bindingRangeIndex = bindingRangeIndex; - subObjectRange.offsetVarLayout = createOffsetVarLayout(typeLayout, path); - subObjectRange.spaceOffset = 0; - m_extendedInfo->m_subObjectRanges.add(subObjectRange); - } } } }; @@ -2298,6 +2363,22 @@ SLANG_API SlangInt spReflectionTypeLayout_getFieldBindingRangeOffset(SlangReflec return 0; } +SLANG_API SlangInt spReflectionTypeLayout_getExplicitCounterBindingRangeOffset(SlangReflectionTypeLayout* inTypeLayout) +{ + auto typeLayout = convert(inTypeLayout); + if(!typeLayout) return 0; + + if(const auto structuredBufferTypeLayout = as(typeLayout)) + { + getExtendedTypeLayout(structuredBufferTypeLayout); + return structuredBufferTypeLayout->counterVarLayout + ? structuredBufferTypeLayout->counterVarLayout->bindingRangeOffset + : 0; + } + + return 0; +} + #if 0 SLANG_API SlangInt spReflectionTypeLayout_getSubObjectRangeCount(SlangReflectionTypeLayout* inTypeLayout) { diff --git a/source/slang/slang-type-layout.cpp b/source/slang/slang-type-layout.cpp index 978fa6fbb..1534b6cc5 100644 --- a/source/slang/slang-type-layout.cpp +++ b/source/slang/slang-type-layout.cpp @@ -228,6 +228,11 @@ struct DefaultLayoutRulesImpl : SimpleLayoutRulesImpl // and instead it will be injected in the concrete implementations // that require it. } + + bool DoStructuredBuffersNeedSeparateCounterBuffer() override + { + return true; + } }; /// Common behavior for GLSL-family layout. @@ -600,6 +605,14 @@ struct HLSLStructuredBufferLayoutRulesImpl : DefaultLayoutRulesImpl // but retain the rules around not adjusting the size of an array or // structure to its alignment. In this way they should match our // default layout rules. + // + // DirectX does however allow transparently managing the counter buffer + // resource for StructuredBuffers. + + bool DoStructuredBuffersNeedSeparateCounterBuffer() override + { + return false; + } }; struct DefaultVaryingLayoutRulesImpl : DefaultLayoutRulesImpl @@ -733,8 +746,7 @@ struct GLSLObjectLayoutRulesImpl : ObjectLayoutRulesImpl // In Vulkan GLSL, pretty much every object is just a descriptor-table slot. // Except for AppendConsumeStructuredBuffer, which takes two slots. - if (kind == ShaderParameterKind::AppendConsumeStructuredBuffer) - slotCount = 2; + // This, however, is added in 'createStructuredBufferWithCounterTypeLayout' if (options.hlslToVulkanKindFlags) { @@ -2785,6 +2797,49 @@ RefPtr createParameterGroupTypeLayout( elementTypeRules); } +// Create a type layout for a structured buffer type with an associated counter +RefPtr +createStructuredBufferWithCounterTypeLayout( + TypeLayoutContext const& context, + ShaderParameterKind kind, + Type* structuredBufferType, + RefPtr elementTypeLayout) +{ + auto typeLayout = createStructuredBufferTypeLayout(context, kind, structuredBufferType, elementTypeLayout); + + const auto structuredBufferLayoutRules = context.getRulesFamily()->getStructuredBufferRules(context.targetReq); + + const auto counterType = context.astBuilder->getIntType(); + const auto counterBufferType = context.astBuilder->getRWStructuredBufferType(counterType); + const auto counterTypeLayout = createTypeLayoutWith(context, structuredBufferLayoutRules, counterBufferType); + + const auto counterVarDecl = context.astBuilder->create(); + counterVarDecl->type.type = counterBufferType; + counterVarDecl->nameAndLoc.name = + context.astBuilder->getSharedASTBuilder()->getNamePool()->getName("counter"); + + RefPtr counterVarLayout = new VarLayout(); + counterVarLayout->varDecl = makeDeclRef(counterVarDecl); + counterVarLayout->typeLayout = counterTypeLayout; + + for(auto& typeResourceInfo : typeLayout->resourceInfos) + { + const auto counterResourceInfo + = counterVarLayout->findOrAddResourceInfo(typeResourceInfo.kind); + const auto counterTypeResourceInfo + = counterVarLayout->getTypeLayout()->FindResourceInfo(typeResourceInfo.kind); + // We expect this index to be 1 + counterResourceInfo->index = typeResourceInfo.count.getFiniteValue(); + // likewise + typeResourceInfo.count += counterTypeResourceInfo->count; + } + + typeLayout->counterVarLayout = counterVarLayout; + typeLayout->addResourceUsageFrom(counterTypeLayout); + + return typeLayout; +} + // Create a type layout for a structured buffer type. RefPtr createStructuredBufferTypeLayout( @@ -2845,12 +2900,24 @@ createStructuredBufferTypeLayout( structuredBufferLayoutRules, elementType); - return createStructuredBufferTypeLayout( - context, - kind, - structuredBufferType, - elementTypeLayout); - + if(kind == ShaderParameterKind::AppendConsumeStructuredBuffer + && structuredBufferLayoutRules->DoStructuredBuffersNeedSeparateCounterBuffer()) + { + return createStructuredBufferWithCounterTypeLayout( + context, + kind, + structuredBufferType, + elementTypeLayout + ); + } + else + { + return createStructuredBufferTypeLayout( + context, + kind, + structuredBufferType, + elementTypeLayout); + } } /// Create layout information for the given `type`. diff --git a/source/slang/slang-type-layout.h b/source/slang/slang-type-layout.h index e3dd719d6..1ebd26098 100644 --- a/source/slang/slang-type-layout.h +++ b/source/slang/slang-type-layout.h @@ -628,6 +628,10 @@ class StructuredBufferTypeLayout : public TypeLayout { public: RefPtr elementTypeLayout; + // If required, this is the VarLayout for a buffer which contains the + // counter associated with the buffer, most often used for + // AppendStructuredBuffer or ConsumeStructuredBuffer + RefPtr counterVarLayout; }; /// Type layout for a logical sequence type @@ -962,6 +966,10 @@ struct SimpleLayoutRulesImpl // End layout for a struct, and finalize its size/alignment. virtual void EndStructLayout(UniformLayoutInfo* ioStructInfo) = 0; + + // Do structured buffers need a separate binding for the counter buffer? + // (DirectX is the exception in managing these together) + virtual bool DoStructuredBuffersNeedSeparateCounterBuffer() = 0; }; struct ObjectLayoutRulesImpl @@ -1021,6 +1029,13 @@ struct LayoutRulesImpl return simpleRules->EndStructLayout(ioStructInfo); } + // Do structured buffers need a separate binding for the counter buffer? + // (DirectX is the exception in managing these together) + bool DoStructuredBuffersNeedSeparateCounterBuffer() + { + return simpleRules->DoStructuredBuffersNeedSeparateCounterBuffer(); + } + // Forward `ObjectLayoutRulesImpl` interface SimpleLayoutInfo GetObjectLayout(ShaderParameterKind kind, const ObjectLayoutRulesImpl::Options& options) @@ -1306,6 +1321,13 @@ createStructuredBufferTypeLayout( Type* structuredBufferType, Type* elementType); +RefPtr +createStructuredBufferTypeLayout( + TypeLayoutContext const& context, + ShaderParameterKind kind, + Type* structuredBufferType, + RefPtr elementTypeLayout); + /// Create a type layout for an unspecialized `globalGenericParamDecl`. RefPtr createTypeLayoutForGlobalGenericTypeParam( TypeLayoutContext const& context, diff --git a/tests/hlsl/append-structured-buffer.slang b/tests/hlsl/append-structured-buffer.slang index 52c657b86..5ec7f844a 100644 --- a/tests/hlsl/append-structured-buffer.slang +++ b/tests/hlsl/append-structured-buffer.slang @@ -1,43 +1,42 @@ +//TEST(compute):COMPARE_COMPUTE_EX(filecheck-buffer=BUF):-dx12 -use-dxil -compute -output-using-type +//TEST(compute, vulkan):COMPARE_COMPUTE_EX(filecheck-buffer=BUF):-vk -compute -output-using-type +//TEST(compute, vulkan):COMPARE_COMPUTE_EX(filecheck-buffer=BUF):-vk -compute -output-using-type -emit-spirv-directly -//TEST:SIMPLE(filecheck=GLSL):-target glsl -profile glsl_450 -stage compute -entry computeMain -fvk-u-shift 10 0 -//TEST:SIMPLE(filecheck=SPIRV):-target spirv -profile glsl_450 -stage compute -entry computeMain +// To check that our counter-initialization works correctly, set the initial +// counter to 1 instead of 0 +//TEST_INPUT:ubuffer(data=[0 0 0 0 0 0 0 0], stride=4, counter=1):out,name=outputBuffer +AppendStructuredBuffer outputBuffer; -//DISABLED_TEST(compute, vulkan):COMPARE_COMPUTE_EX:-vk -compute -shaderobj -output-using-type -xslang -fvk-use-gl-layout +//TEST_INPUT:set inBuffer = ubuffer(data=[1 2 3 4], stride=4) +RWStructuredBuffer inBuffer; -//TEST_INPUT:ubuffer(data=[0], stride=4):out,name=outputBuffer -RWStructuredBuffer outputBuffer; - -AppendStructuredBuffer appendBuffer; - -// GLSL: layout(std430, binding = 11) buffer StructuredBuffer_float2_t -// GLSL: vec2 _data[]; -// GLSL: } appendBuffer_elements_0 - -// GLSL: layout(std430, binding = 12) buffer StructuredBuffer_int_t -// GLSL: int _data[]; -// GLSL: } appendBuffer_counter - -// GLSL: void AppendStructuredBuffer_Append_0(vec2 [[PARAM:[A-Za-z0-9_]+]]) -// GLSL: int [[COUNTER:[A-Za-z0-9_]+]] = atomicAdd(appendBuffer_counter_0._data[0], 1); -// GLSL: appendBuffer_elements_0._data{{\[}}[[COUNTER]]{{\]}} = [[PARAM]]; - -// GLSL: uvec2 StructuredBuffer_GetDimensions_0() -// GLSL: { -// GLSL: return uvec2(uint(appendBuffer_counter_0._data[0]), 8U); -// GLSL: } - -// SPIRV: OpEntryPoint - -//TEST_INPUT:set inBuffer = ubuffer(data=[1.0 2.0 3.0 4.0 5.0 6.0 7.0 8.0], stride=4) -RWByteAddressBuffer inBuffer; - -[numthreads(1, 1, 1)] -void computeMain(uint3 dispatchThreadID : SV_DispatchThreadID) +[numthreads(4, 1, 1)] +void computeMain(uint i : SV_GroupIndex) { - var g = inBuffer.Load(4); - appendBuffer.Append(g.xy); + int g = inBuffer[i]; + outputBuffer.Append(g); + + GroupMemoryBarrier(); uint numStructs, stride; - appendBuffer.GetDimensions(numStructs, stride); - outputBuffer[dispatchThreadID.x] = numStructs; // expect 1.0 + outputBuffer.GetDimensions(numStructs, stride); + if(i == 0) + outputBuffer.Append(int(numStructs)); + + // BUF: type: int32_t + // Never assigned, as we set the initial counter to 1 + // BUF: 0 + + // The values from inBuffer in any order + // BUF-DAG: 1 + // BUF-DAG: 3 + // BUF-DAG: 2 + // BUF-DAG: 4 + + // The total size of the AppendStructuredBuffer (from GetDimensions) + // BUF: 8 + + // Never assigned + // BUF: 0 + // BUF: 0 } diff --git a/tests/hlsl/consume-structured-buffer.slang b/tests/hlsl/consume-structured-buffer.slang index 3027b4184..352fd6dac 100644 --- a/tests/hlsl/consume-structured-buffer.slang +++ b/tests/hlsl/consume-structured-buffer.slang @@ -1,35 +1,33 @@ +//TEST(compute):COMPARE_COMPUTE_EX(filecheck-buffer=BUF):-dx12 -use-dxil -compute -output-using-type +//TEST(compute, vulkan):COMPARE_COMPUTE_EX(filecheck-buffer=BUF):-vk -compute -output-using-type +//TEST(compute, vulkan):COMPARE_COMPUTE_EX(filecheck-buffer=BUF):-vk -compute -output-using-type -emit-spirv-directly -//TEST:SIMPLE(filecheck=GLSL):-target glsl -profile glsl_450 -stage compute -entry computeMain -//TEST:SIMPLE(filecheck=SPIRV):-target spirv -profile glsl_450 -stage compute -entry computeMain - -//DISABLED_TEST(compute, vulkan):COMPARE_COMPUTE_EX:-vk -compute -shaderobj -output-using-type -xslang -fvk-use-gl-layout - -//TEST_INPUT:ubuffer(data=[0], stride=4):out,name=outputBuffer +//TEST_INPUT:ubuffer(data=[0 0 0 0 0], stride=4):out,name=outputBuffer RWStructuredBuffer outputBuffer; -ConsumeStructuredBuffer consumeBuffer; +// To check that our counter-initialization works correctly, set the initial +// counter to 6 instead of 8, so we'll start reading from 6.0 downwards +//TEST_INPUT:set consumeBuffer = ubuffer(data=[1.0 2.0 3.0 4.0 5.0 6.0 7.0 8.0], stride=4, counter=6) +ConsumeStructuredBuffer consumeBuffer; -// GLSL: layout(std430, binding = 1) buffer StructuredBuffer_float2_t -// GLSL: vec2 _data[]; -// GLSL: } consumeBuffer_elements_0 +[numthreads(4, 1, 1)] +void computeMain(uint i : SV_GroupIndex) +{ + uint size, stride; + consumeBuffer.GetDimensions(size, stride); -// GLSL: layout(std430, binding = 2) buffer StructuredBuffer_int_t -// GLSL: int _data[]; -// GLSL: } consumeBuffer_counter + if(i == 0) + outputBuffer[0] = size; -// GLSL: vec2 ConsumeStructuredBuffer_Consume_0() -// GLSL: int [[COUNTER:[A-Za-z0-9_]+]] = atomicAdd(consumeBuffer_counter_0._data[0], -1); -// GLSL: int [[COUNTER1:[A-Za-z0-9_]+]] = [[COUNTER]] - 1; -// GLSL: if{{\s?}}([[COUNTER1]] >= 0) -// GLSL: return consumeBuffer_elements_0._data{{\[}}[[COUNTER1]]{{\]}}; -// GLSL: else -// GLSL: return vec2(0.0); + outputBuffer[i+1] = consumeBuffer.Consume(); -// SPIRV: OpEntryPoint + // BUF: type: float + // The total size of the ConsumeStructuredBuffer (from GetDimensions) + // BUF: 8.0 -[numthreads(1, 1, 1)] -void computeMain(uint3 dispatchThreadID : SV_DispatchThreadID) -{ - var v = consumeBuffer.Consume(); - outputBuffer[dispatchThreadID.x] = v.x; // expect 1.0 + // The values from consumeBuffer in any order + // BUF-DAG: 6.0 + // BUF-DAG: 4.0 + // BUF-DAG: 5.0 + // BUF-DAG: 3.0 } diff --git a/tools/gfx-util/shader-cursor.cpp b/tools/gfx-util/shader-cursor.cpp index 5a2cdfd33..c849673bf 100644 --- a/tools/gfx-util/shader-cursor.cpp +++ b/tools/gfx-util/shader-cursor.cpp @@ -20,6 +20,58 @@ Result gfx::ShaderCursor::getDereferenced(ShaderCursor& outCursor) const } } +ShaderCursor ShaderCursor::getExplicitCounter() const +{ + // Similar to getField below + + // The alternative to handling this here would be to augment IResourceView + // with a `getCounterResourceView()`, and set that also in `setResource` + if(const auto counterVarLayout = m_typeLayout->getExplicitCounter()) + { + ShaderCursor counterCursor; + + // The counter cursor will point into the same parent object. + counterCursor.m_baseObject = m_baseObject; + + // The type being pointed to is the type of the field. + counterCursor.m_typeLayout = counterVarLayout->getTypeLayout(); + + // The byte offset is the current offset plus the relative offset of the counter. + // The offset in binding ranges is computed similarly. + counterCursor.m_offset.uniformOffset + = m_offset.uniformOffset + SlangInt(counterVarLayout->getOffset()); + counterCursor.m_offset.bindingRangeIndex + = m_offset.bindingRangeIndex + GfxIndex(m_typeLayout->getExplicitCounterBindingRangeOffset()); + + // The index of the counter within any binding ranges will be the same + // as the index computed for the parent structure. + // + // Note: this case would arise for an array of structured buffers + // + // AppendStructuredBuffer g[4]; + // + // In this scenario, `g` holds two binding ranges: + // + // * Range #0 comprises 4 element buffers, representing `g[...].elements` + // * Range #1 comprises 4 counter buffers, representing `g[...].counter` + // + // A cursor for `g[2]` would have a `bindingRangeIndex` of zero but + // a `bindingArrayIndex` of 2, indicating that we could end up + // referencing either range, but no matter what we know the index + // is 2. Thus when we form a cursor for `g[2].counter` we want to + // apply the binding range offset to get a `bindingRangeIndex` of + // 1, while the `bindingArrayIndex` is unmodified. + // + // The result is that `g[2].counter` is stored in range #1 at array index 2. + // + counterCursor.m_offset.bindingArrayIndex = m_offset.bindingArrayIndex; + + return counterCursor; + } + // Otherwise, return an invalid cursor + return ShaderCursor{}; +} + Result ShaderCursor::getField(const char* name, const char* nameEnd, ShaderCursor& outCursor) const { // If this cursor is invalid, then can't possible fetch a field. diff --git a/tools/gfx-util/shader-cursor.h b/tools/gfx-util/shader-cursor.h index 5dfc57f6d..3d35bb2ed 100644 --- a/tools/gfx-util/shader-cursor.h +++ b/tools/gfx-util/shader-cursor.h @@ -63,6 +63,13 @@ struct ShaderCursor return cursor; } + /// Some resources such as RWStructuredBuffer, AppendStructuredBuffer and + /// ConsumeStructuredBuffer need to have their counter explicitly bound on + /// APIs other than DirectX, this will return a valid ShaderCursor pointing + /// to that resource if that is the case. + /// Otherwise, this returns an invalid cursor. + ShaderCursor getExplicitCounter() const; + ShaderCursor getElement(GfxIndex index) const; static Result followPath(const char* path, ShaderCursor& ioCursor); diff --git a/tools/gfx/d3d12/d3d12-device.cpp b/tools/gfx/d3d12/d3d12-device.cpp index 983f93bce..eb9e597bf 100644 --- a/tools/gfx/d3d12/d3d12-device.cpp +++ b/tools/gfx/d3d12/d3d12-device.cpp @@ -1665,9 +1665,11 @@ Result DeviceImpl::createBufferView( { auto resourceImpl = (BufferResourceImpl*)buffer; auto resourceDesc = *resourceImpl->getDesc(); + const auto counterResourceImpl = static_cast(counterBuffer); RefPtr viewImpl = new ResourceViewImpl(); viewImpl->m_resource = resourceImpl; + viewImpl->m_counterResource = counterResourceImpl; viewImpl->m_desc = desc; switch (desc.type) @@ -1722,7 +1724,6 @@ Result DeviceImpl::createBufferView( } else { - auto counterResourceImpl = static_cast(counterBuffer); SLANG_RETURN_ON_FAIL(m_cpuViewHeap->allocate(&viewImpl->m_descriptor)); viewImpl->m_allocator = m_cpuViewHeap; m_device->CreateUnorderedAccessView( diff --git a/tools/gfx/d3d12/d3d12-resource-views.h b/tools/gfx/d3d12/d3d12-resource-views.h index 12e3d0714..fd3f44116 100644 --- a/tools/gfx/d3d12/d3d12-resource-views.h +++ b/tools/gfx/d3d12/d3d12-resource-views.h @@ -26,6 +26,8 @@ class ResourceViewImpl { public: Slang::RefPtr m_resource; + // null, unless this is a structuredbuffer with a separate counter buffer + Slang::RefPtr m_counterResource; virtual SLANG_NO_THROW Result SLANG_MCALL getNativeHandle(InteropHandle* outHandle) override; }; diff --git a/tools/gfx/d3d12/d3d12-shader-object.cpp b/tools/gfx/d3d12/d3d12-shader-object.cpp index c5389f6ea..74760eabd 100644 --- a/tools/gfx/d3d12/d3d12-shader-object.cpp +++ b/tools/gfx/d3d12/d3d12-shader-object.cpp @@ -181,7 +181,11 @@ Result ShaderObjectImpl::init( // referenced by descriptors in this object does not get // freed while the object is still live. // + // The doubling here is because any buffer resource could + // have a counter buffer associated with it, which we + // also need to ensure isn't destroyed prematurely. m_boundResources.setCount(resourceCount); + m_boundCounterResources.setCount(resourceCount); } if (auto samplerCount = layout->getSamplerSlotCount()) { @@ -950,8 +954,9 @@ Result ShaderObjectImpl::setResource(ShaderOffset const& offset, IResourceView* { auto resourceViewImpl = static_cast(resourceView); // Hold a reference to the resource to prevent its destruction. - m_boundResources[bindingRange.baseIndex + offset.bindingArrayIndex] = - resourceViewImpl->m_resource; + const auto resourceOffset = bindingRange.baseIndex + offset.bindingArrayIndex; + m_boundResources[resourceOffset] = resourceViewImpl->m_resource; + m_boundCounterResources[resourceOffset] = resourceViewImpl->m_counterResource; internalResourceView = resourceViewImpl; } break; diff --git a/tools/gfx/d3d12/d3d12-shader-object.h b/tools/gfx/d3d12/d3d12-shader-object.h index d6560628c..84d305ae7 100644 --- a/tools/gfx/d3d12/d3d12-shader-object.h +++ b/tools/gfx/d3d12/d3d12-shader-object.h @@ -197,6 +197,7 @@ public: DescriptorSet m_cachedGPUDescriptorSet; ShortList, 8> m_boundResources; + ShortList, 8> m_boundCounterResources; List m_rootArguments; /// A constant buffer used to stored ordinary data for this object /// and existential-type sub-objects. diff --git a/tools/render-test/render-test-main.cpp b/tools/render-test/render-test-main.cpp index 3bdcdba07..1f2b3a5ae 100644 --- a/tools/render-test/render-test-main.cpp +++ b/tools/render-test/render-test-main.cpp @@ -209,11 +209,49 @@ struct AssignValsFromLayoutContext ComPtr bufferResource; SLANG_RETURN_ON_FAIL(ShaderRendererUtil::createBufferResource(srcBuffer, /*entry.isOutput,*/ bufferSize, bufferData.getBuffer(), device, bufferResource)); + ComPtr counterResource; + const auto explicitCounterCursor = dstCursor.getExplicitCounter(); + if(srcBuffer.counter != ~0u) + { + if(explicitCounterCursor.isValid()) + { + // If this cursor has a full buffer object associated with the + // resource, then assign to that. + ShaderInputLayout::BufferVal counterVal; + counterVal.bufferData.add(srcBuffer.counter); + assignBuffer(explicitCounterCursor, &counterVal); + } + else + { + // Otherwise, this API (D3D) must be handling the buffer object + // specially, in which case create the buffer resource to pass + // into `createBufferView` + const InputBufferDesc& counterBufferDesc{ + InputBufferType::StorageBuffer, + sizeof(uint32_t), + Format::Unknown, + }; + SLANG_RETURN_ON_FAIL(ShaderRendererUtil::createBufferResource( + counterBufferDesc, + sizeof(srcBuffer.counter), + &srcBuffer.counter, + device, + counterResource + )); + } + } + else if(explicitCounterCursor.isValid()) + { + // If we know we require a counter for this resource but haven't + // been given one, error + return SLANG_E_INVALID_ARG; + } + IResourceView::Desc viewDesc = {}; viewDesc.type = IResourceView::Type::UnorderedAccess; viewDesc.format = srcBuffer.format; viewDesc.bufferElementSize = srcVal->bufferDesc.stride; - auto bufferView = device->createBufferView(bufferResource, nullptr, viewDesc); + auto bufferView = device->createBufferView(bufferResource, counterResource, viewDesc); dstCursor.setResource(bufferView); maybeAddOutput(dstCursor, srcVal, bufferResource); @@ -977,7 +1015,7 @@ Result RenderTestApp::writeBindingOutput(const String& fileName) m_transientHeap->finish(); m_transientHeap->synchronizeAndReset(); - m_device->readBufferResource(stagingBuffer, 0, bufferSize, blob.writeRef()); + SLANG_RETURN_ON_FAIL(m_device->readBufferResource(stagingBuffer, 0, bufferSize, blob.writeRef())); } if (!blob) diff --git a/tools/render-test/shader-input-layout.cpp b/tools/render-test/shader-input-layout.cpp index b8c505607..80ce4e316 100644 --- a/tools/render-test/shader-input-layout.cpp +++ b/tools/render-test/shader-input-layout.cpp @@ -203,6 +203,11 @@ namespace renderer_test parser.Read("="); val->bufferDesc.stride = parser.ReadInt(); } + else if (word == "counter") + { + parser.Read("="); + val->bufferDesc.counter = parser.ReadInt(); + } else if (word == "random") { parser.Read("("); diff --git a/tools/render-test/shader-input-layout.h b/tools/render-test/shader-input-layout.h index 78d545114..2803d1915 100644 --- a/tools/render-test/shader-input-layout.h +++ b/tools/render-test/shader-input-layout.h @@ -58,6 +58,10 @@ struct InputBufferDesc InputBufferType type = InputBufferType::StorageBuffer; int stride = 0; // stride == 0 indicates an unstructured buffer. Format format = Format::Unknown; + // For RWStructuredBuffer, AppendStructuredBuffer, ConsumeStructuredBuffer + // the default value of 0xffffffff indicates that a counter buffer should + // not be assigned + uint32_t counter = ~0u; }; struct InputSamplerDesc -- cgit v1.2.3