From 7db141c77b3f1213b5543eabb33246e59ed507fd Mon Sep 17 00:00:00 2001 From: baldurk Date: Mon, 22 Jul 2024 14:47:18 +0100 Subject: [PATCH] Implement bindless feedback for samplers on D3D12. Closes #3387 --- .../driver/d3d12/d3d12_shader_feedback.cpp | 105 ++++++++++++++++-- .../demos/d3d12/d3d12_descriptor_indexing.cpp | 20 ++-- util/test/demos/d3d12/d3d12_test.cpp | 2 +- .../tests/D3D12/D3D12_Descriptor_Indexing.py | 30 +++-- 4 files changed, 125 insertions(+), 32 deletions(-) diff --git a/renderdoc/driver/d3d12/d3d12_shader_feedback.cpp b/renderdoc/driver/d3d12/d3d12_shader_feedback.cpp index 0dbed640b..4fb7c5e62 100644 --- a/renderdoc/driver/d3d12/d3d12_shader_feedback.cpp +++ b/renderdoc/driver/d3d12/d3d12_shader_feedback.cpp @@ -112,7 +112,8 @@ static bool AnnotateDXBCShader(const DXBC::DXBCContainer *dxbc, uint32_t space, for(const Operand &operand : op.operands) { - if(operand.type != TYPE_RESOURCE && operand.type != TYPE_UNORDERED_ACCESS_VIEW) + if(operand.type != TYPE_RESOURCE && operand.type != TYPE_UNORDERED_ACCESS_VIEW && + operand.type != TYPE_SAMPLER) continue; const Declaration *decl = @@ -187,9 +188,6 @@ static bool AnnotateDXBCShader(const DXBC::DXBCContainer *dxbc, uint32_t space, // atomic or the slot editor.InsertOperation( i++, oper(OPCODE_ATOMIC_OR, {uav(u), temp(t).swizzle(0), imm(magicFeedbackValue)})); - - // only one resource operand per instruction - break; } } @@ -249,10 +247,10 @@ static bool AnnotateDXILShader(const DXBC::DXBCContainer *dxbc, uint32_t space, Attribute::NoUnwind | Attribute::ReadNone); // while we're iterating through the metadata to add our UAV, we'll also note the shader-local - // register IDs of each SRV/UAV with slots, and record the base slot in this array for easy access - // later when annotating dx.op.createHandle calls. We also need to know the base register because - // the index dxc provides is register-relative - rdcarray> srvBaseSlots, uavBaseSlots; + // register IDs of each SRV/UAV/Sampler with slots, and record the base slot in this array for + // easy access later when annotating dx.op.createHandle calls. We also need to know the base + // register because the index dxc provides is register-relative + rdcarray> srvBaseSlots, uavBaseSlots, sampBaseSlots; // declare the resource, this happens purely in metadata but we need to store the slot uint32_t regSlot = 0; @@ -272,6 +270,7 @@ static bool AnnotateDXILShader(const DXBC::DXBCContainer *dxbc, uint32_t space, Metadata *srvs = reslist->children[0]; Metadata *uavs = reslist->children[1]; + Metadata *samps = reslist->children[3]; // if there isn't a UAV list, create an empty one so we can add our own if(!uavs) uavs = reslist->children[1] = editor.CreateMetadata(); @@ -329,6 +328,56 @@ static bool AnnotateDXILShader(const DXBC::DXBCContainer *dxbc, uint32_t space, srvBaseSlots[id] = {feedbackSlot, key.bind}; } + key.type = DXBCBytecode::TYPE_SAMPLER; + + for(size_t i = 0; samps && i < samps->children.size(); i++) + { + // each Sampler child should have a fixed format + const Metadata *samp = samps->children[i]; + const Constant *slot = cast(samp->children[(size_t)ResField::ID]->value); + const Constant *sampSpace = cast(samp->children[(size_t)ResField::Space]->value); + const Constant *reg = cast(samp->children[(size_t)ResField::RegBase]->value); + + if(!slot) + { + RDCWARN("Unexpected non-constant slot ID in sampler"); + continue; + } + + if(!sampSpace) + { + RDCWARN("Unexpected non-constant register space in sampler"); + continue; + } + + if(!reg) + { + RDCWARN("Unexpected non-constant register base in sampler"); + continue; + } + + uint32_t id = slot->getU32(); + key.space = sampSpace->getU32(); + key.bind = reg->getU32(); + + // ensure every valid ID has an index, even if it's 0 + sampBaseSlots.resize_for_index(id); + + auto it = slots.find(key); + + // not annotated + if(it == slots.end()) + continue; + + uint32_t feedbackSlot = it->second.slot; + + // we assume all feedback slots are non-zero, so that a 0 base slot can be used as an + // identifier for 'this resource isn't annotated' + RDCASSERT(feedbackSlot > 0); + + sampBaseSlots[id] = {feedbackSlot, key.bind}; + } + key.type = DXBCBytecode::TYPE_UNORDERED_ACCESS_VIEW; for(size_t i = 0; i < uavs->children.size(); i++) @@ -616,6 +665,8 @@ static bool AnnotateDXILShader(const DXBC::DXBCContainer *dxbc, uint32_t space, slotInfo = srvBaseSlots[id]; else if(kind == HandleKind::UAV && id < uavBaseSlots.size()) slotInfo = uavBaseSlots[id]; + else if(kind == HandleKind::Sampler && id < sampBaseSlots.size()) + slotInfo = sampBaseSlots[id]; } else { @@ -658,10 +709,14 @@ static bool AnnotateDXILShader(const DXBC::DXBCContainer *dxbc, uint32_t space, } HandleKind kind = (HandleKind)kindArg->getU32(); - if(kind != HandleKind::SRV && kind != HandleKind::UAV) + if(kind == HandleKind::SRV) + key.type = DXBCBytecode::TYPE_RESOURCE; + else if(kind == HandleKind::UAV) + key.type = DXBCBytecode::TYPE_UNORDERED_ACCESS_VIEW; + else if(kind == HandleKind::Sampler) + key.type = DXBCBytecode::TYPE_SAMPLER; + else continue; - key.type = kind == HandleKind::UAV ? DXBCBytecode::TYPE_UNORDERED_ACCESS_VIEW - : DXBCBytecode::TYPE_RESOURCE; key.space = spaceArg->getU32(); key.bind = regArg->getU32(); } @@ -942,6 +997,32 @@ static bool AddArraySlots(WrappedID3D12PipelineState::ShaderEntry *shad, uint32_ } } + for(size_t i = 0; i < refl.samplers.size(); i++) + { + const ShaderSampler &s = refl.samplers[i]; + if(s.bindArraySize > 1) + { + D3D12FeedbackKey key; + key.stage = refl.stage; + key.type = DXBCBytecode::TYPE_SAMPLER; + key.space = s.fixedBindSetOrSpace; + key.bind = s.fixedBindNumber; + + DescriptorAccess access; + access.stage = refl.stage; + access.type = DescriptorType::Sampler; + access.index = i & 0xffff; + // descriptor storage side will be calculated later when finalising this with the root + // signature information. + + slots[key].numDescriptors = RDCMIN(maxDescriptors, s.bindArraySize); + slots[key].access = access; + slots[key].slot = numSlots; + numSlots += slots[key].numDescriptors; + dynamicUsed = true; + } + } + for(size_t i = 0; i < refl.readWriteResources.size(); i++) { const ShaderResource &rw = refl.readWriteResources[i]; @@ -1504,7 +1585,7 @@ bool D3D12Replay::FetchShaderFeedback(uint32_t eventId) D3D12_DESCRIPTOR_RANGE_TYPE rangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV; if(IsConstantBlockDescriptor(access.type)) rangeType = D3D12_DESCRIPTOR_RANGE_TYPE_CBV; - else if(IsConstantBlockDescriptor(access.type)) + else if(IsSamplerDescriptor(access.type)) rangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER; else if(IsReadOnlyDescriptor(access.type)) rangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV; diff --git a/util/test/demos/d3d12/d3d12_descriptor_indexing.cpp b/util/test/demos/d3d12/d3d12_descriptor_indexing.cpp index e65b9c8c0..596a64683 100644 --- a/util/test/demos/d3d12/d3d12_descriptor_indexing.cpp +++ b/util/test/demos/d3d12/d3d12_descriptor_indexing.cpp @@ -90,6 +90,8 @@ Texture2D texArray1[32] : register(t0, space1); Texture2D texArray2[32] : register(t40, space1); Texture2D texArray3[32] : register(t80, space1); +SamplerState sArray1[32] : register(s0, space1); + struct alias1 { float4 Color; @@ -126,9 +128,9 @@ float4 main(v2f IN) : SV_Target0 if(t.tex == 0) { - ret *= texArray1[t.binding].SampleLevel(s, uv.xy, 0); - ret *= texArray1[t.binding+1].SampleLevel(s, uv.xy, 0); - ret *= texArray1[t.binding+2].SampleLevel(s, uv.xy, 0); + ret *= texArray1[t.binding].SampleLevel(sArray1[t.binding], uv.xy, 0); + ret *= texArray1[t.binding+1].SampleLevel(sArray1[t.binding+1], uv.xy, 0); + ret *= texArray1[t.binding+2].SampleLevel(sArray1[t.binding+2], uv.xy, 0); } else if(t.tex == 1) { @@ -219,9 +221,9 @@ float4 main(v2f IN) : SV_Target0 } else if(t.tex == 1) { - SamplerState s1 = SamplerDescriptorHeap[4]; - SamplerState s2 = SamplerDescriptorHeap[5]; - SamplerState s3 = SamplerDescriptorHeap[6]; + SamplerState s1 = SamplerDescriptorHeap[19]; + SamplerState s2 = SamplerDescriptorHeap[20]; + SamplerState s3 = SamplerDescriptorHeap[21]; Texture2D tex1 = ResourceDescriptorHeap[40+t.binding]; Texture2D tex2 = ResourceDescriptorHeap[40+t.binding+10]; ConstantBuffer cbv = ResourceDescriptorHeap[9]; @@ -232,7 +234,7 @@ float4 main(v2f IN) : SV_Target0 } else if(t.tex == 2) { - SamplerState s = SamplerDescriptorHeap[7]; + SamplerState s = SamplerDescriptorHeap[25]; Texture2D tex = ResourceDescriptorHeap[80+t.binding]; ret *= tex.SampleLevel(s, uv.xy, 0); } @@ -302,6 +304,7 @@ float4 main(v2f IN) : SV_Target0 tableParam(D3D12_SHADER_VISIBILITY_PIXEL, D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 0, 0, 20, 0), tableParam(D3D12_SHADER_VISIBILITY_PIXEL, D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 1, 0, 150, 0), tableParam(D3D12_SHADER_VISIBILITY_PIXEL, D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 2, 15, 32, 150), + tableParam(D3D12_SHADER_VISIBILITY_PIXEL, D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER, 1, 0, 32, 0), }, D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT, 1, &staticSamp); ID3D12PipelineStatePtr graphicspso[4]; @@ -479,7 +482,7 @@ float4 main(v2f IN) : SV_Target0 D3D12_TEXTURE_ADDRESS_MODE_WRAP; UINT increment = dev->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER); D3D12_CPU_DESCRIPTOR_HANDLE samplerStart = m_Sampler->GetCPUDescriptorHandleForHeapStart(); - for(int i = 0; i < 8; ++i) + for(int i = 0; i < 128; ++i) dev->CreateSampler(&samplerDesc, {samplerStart.ptr + increment * i}); while(Running()) @@ -525,6 +528,7 @@ float4 main(v2f IN) : SV_Target0 { cmd->SetGraphicsRootDescriptorTable(0, m_CBVUAVSRV->GetGPUDescriptorHandleForHeapStart()); cmd->SetGraphicsRootDescriptorTable(1, m_CBVUAVSRV->GetGPUDescriptorHandleForHeapStart()); + cmd->SetGraphicsRootDescriptorTable(3, m_Sampler->GetGPUDescriptorHandleForHeapStart()); } RSSetViewport(cmd, {0.0f, 0.0f, (float)screenWidth, (float)screenHeight, 0.0f, 1.0f}); diff --git a/util/test/demos/d3d12/d3d12_test.cpp b/util/test/demos/d3d12/d3d12_test.cpp index 8e203b8cd..f546b5e5c 100644 --- a/util/test/demos/d3d12/d3d12_test.cpp +++ b/util/test/demos/d3d12/d3d12_test.cpp @@ -499,7 +499,7 @@ void D3D12GraphicsTest::PostDeviceCreate() desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE; - desc.NumDescriptors = 8; + desc.NumDescriptors = 128; desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER; CHECK_HR(dev->CreateDescriptorHeap(&desc, __uuidof(ID3D12DescriptorHeap), (void **)&m_Sampler)); diff --git a/util/test/tests/D3D12/D3D12_Descriptor_Indexing.py b/util/test/tests/D3D12/D3D12_Descriptor_Indexing.py index c849a45c1..bffa4dcb2 100644 --- a/util/test/tests/D3D12/D3D12_Descriptor_Indexing.py +++ b/util/test/tests/D3D12/D3D12_Descriptor_Indexing.py @@ -77,6 +77,10 @@ class D3D12_Descriptor_Indexing(rdtest.TestCase): 'loc': (1, 80), 'elems': [19, 23] }, + (rd.DescriptorCategory.Sampler, 1): { + 'loc': (1, 0), + 'elems': [19, 20, 21] + }, } ro = pipe.GetReadOnlyResources(rd.ShaderStage.Pixel) @@ -84,23 +88,26 @@ class D3D12_Descriptor_Indexing(rdtest.TestCase): rw = pipe.GetReadWriteResources(rd.ShaderStage.Pixel) self.check_eq(len(ro), 2 + 8) - self.check_eq(len(samp), 1) + self.check_eq(len(samp), 1 + 3) self.check_eq(len(rw), 0) refl = pipe.GetShaderReflection(rd.ShaderStage.Pixel) for a in samp + ro: idx = (rd.CategoryForDescriptorType(a.access.type), a.access.index) - if a.access.type == rd.DescriptorType.Sampler: # static sampler + if a.access.type == rd.DescriptorType.Sampler and a.access.index == 0: # static sampler # descriptor store should not be a heap, but we don't verify exactly where it comes from self.check(a.access.descriptorStore not in d3d12pipe.descriptorHeaps) - self.check_eq(a.access.index, 0) continue + heapName = "ResourceDescriptorHeap" if rd.IsReadOnlyDescriptor(a.access.type): res = refl.readOnlyResources[a.access.index] + elif rd.IsReadWriteDescriptor(a.access.type): + res = refl.readOnlyResources[a.access.index] else: - res = refl.readWriteResources[a.access.index] + res = refl.samplers[a.access.index] + heapName = "SamplerDescriptorHeap" if idx not in bind_info.keys(): raise rdtest.TestFailureException( @@ -121,12 +128,13 @@ class D3D12_Descriptor_Indexing(rdtest.TestCase): loc = self.controller.GetDescriptorLocations(a.access.descriptorStore, [rd.DescriptorRange(a.access)])[0] if loc.fixedBindNumber != bind_info[idx]['loc'][1] + a.access.arrayElement: - raise rdtest.TestFailureException("Location {} not expected for space,reg {} array element {}".format( - loc.fixedBindNumber, bind_info[idx]['loc'], a.access.arrayElement)) - if loc.logicalBindName != "ResourceDescriptorHeap[{}]".format(bind_info[idx]['loc'][1] + - a.access.arrayElement): - raise rdtest.TestFailureException("Location {} not expected for space,reg {} array element {}".format( - loc.logicalBindName, bind_info[idx]['loc'], a.access.arrayElement)) + raise rdtest.TestFailureException( + "Location {} not expected for {} at space,reg {} array element {}".format( + loc.fixedBindNumber, str(a.access.type), bind_info[idx]['loc'], a.access.arrayElement)) + if loc.logicalBindName != "{}[{}]".format(heapName, bind_info[idx]['loc'][1] + a.access.arrayElement): + raise rdtest.TestFailureException( + "Location {} not expected for space,reg {} array element {}".format( + loc.logicalBindName, bind_info[idx]['loc'], a.access.arrayElement)) bind_info[idx]['elems'].remove(a.access.arrayElement) @@ -152,7 +160,7 @@ class D3D12_Descriptor_Indexing(rdtest.TestCase): # - UAV resources bind_info = { rd.DescriptorCategory.ConstantBlock: [9], - rd.DescriptorCategory.Sampler: [0, 1, 2, 4, 5, 6, 7], + rd.DescriptorCategory.Sampler: [0, 1, 2, 19, 20, 21, 25], rd.DescriptorCategory.ReadOnlyResource: [8, 12, 19, 20, 21, 49, 59, 6, 99, 103, 156, 162], rd.DescriptorCategory.ReadWriteResource: [10], }