From 46266a9dd6772ec864c0e8e78425a80cdcecb756 Mon Sep 17 00:00:00 2001 From: baldurk Date: Wed, 14 Jul 2021 19:08:35 +0100 Subject: [PATCH] Implement bindless feedback for DXBC shaders on D3D12 with patching --- .../D3D12PipelineStateViewer.cpp | 55 +- .../PipelineState/D3D12PipelineStateViewer.h | 3 + renderdoc/api/replay/d3d12_pipestate.h | 42 +- renderdoc/api/replay/pipestate.inl | 46 +- renderdoc/api/replay/replay_enums.h | 4 +- renderdoc/api/replay/vk_pipestate.h | 5 + .../d3d11/renderdoc_d3d11.vcxproj.filters | 4 +- renderdoc/driver/d3d12/d3d12_debug.h | 2 + renderdoc/driver/d3d12/d3d12_replay.cpp | 113 +++- renderdoc/driver/d3d12/d3d12_replay.h | 40 +- .../driver/d3d12/d3d12_shader_feedback.cpp | 604 ++++++++++++++++++ .../driver/d3d12/renderdoc_d3d12.vcxproj | 1 + .../d3d12/renderdoc_d3d12.vcxproj.filters | 7 +- .../shaders/dxbc/dxbc_bytecode_editor.h | 10 +- renderdoc/driver/vulkan/vk_replay.cpp | 2 +- renderdoc/driver/vulkan/vk_replay.h | 4 +- .../driver/vulkan/vk_shader_feedback.cpp | 2 +- renderdoc/replay/renderdoc_serialise.inl | 28 +- 18 files changed, 904 insertions(+), 68 deletions(-) create mode 100644 renderdoc/driver/d3d12/d3d12_shader_feedback.cpp diff --git a/qrenderdoc/Windows/PipelineState/D3D12PipelineStateViewer.cpp b/qrenderdoc/Windows/PipelineState/D3D12PipelineStateViewer.cpp index a3f765826..2adb1f8cd 100644 --- a/qrenderdoc/Windows/PipelineState/D3D12PipelineStateViewer.cpp +++ b/qrenderdoc/Windows/PipelineState/D3D12PipelineStateViewer.cpp @@ -732,7 +732,7 @@ void D3D12PipelineStateViewer::addResourceRow(const D3D12ViewTag &view, const Bi bool uav = view.type == D3D12ViewTag::UAV; bool filledSlot = (r.resourceId != ResourceId()); - bool usedSlot = (bind && bind->used); + bool usedSlot = (bind && bind->used && r.dynamicallyUsed); // if a target is set to RTVs or DSV, it is implicitly used if(filledSlot) @@ -872,19 +872,16 @@ void D3D12PipelineStateViewer::addResourceRow(const D3D12ViewTag &view, const Bi bool D3D12PipelineStateViewer::showNode(bool usedSlot, bool filledSlot) { - const bool showUnused = ui->showUnused->isChecked(); - const bool showEmpty = ui->showEmpty->isChecked(); - // show if it's referenced by the shader - regardless of empty or not if(usedSlot) return true; // it's not referenced, but if it's bound and we have "show unused" then show it - if(showUnused && filledSlot) + if(m_ShowUnused && filledSlot) return true; // it's empty, and we have "show empty" - if(showEmpty && !filledSlot) + if(m_ShowEmpty && !filledSlot) return true; return false; @@ -1104,7 +1101,16 @@ void D3D12PipelineStateViewer::setShaderState( const rdcarray &views = rootElements[i].views; - for(size_t j = 0; j < views.size();) + size_t firstView = rootElements[i].firstUsedIndex; + size_t lastView = qMin(views.size() - 1, size_t(rootElements[i].lastUsedIndex)); + + if(m_ShowUnused) + { + firstView = 0; + lastView = views.size() - 1; + } + + for(size_t j = firstView; j <= lastView;) { int shaderReg = (int)views[j].bind; @@ -1137,22 +1143,37 @@ void D3D12PipelineStateViewer::setShaderState( } } - uint32_t arraySize = bind ? qMax(1U, bind->arraySize) : 0; + bool usedSlot = (bind && bind->used); + + uint32_t arraySize = bind ? qMax(1U, bind->arraySize) : 1; + + // if this bind isn't used, skip + if(!showNode(usedSlot, true)) + { + omittingEmpty = false; + j += arraySize; + continue; + } if(arraySize > 1) { // if this is an array bind, iterate over it trying to elide large empty ranges - for(uint32_t k = 0; k < arraySize && j < views.size(); k++) + for(uint32_t k = 0; k < arraySize && j <= lastView; k++) { if( // if current element is empty views[j].resourceId == ResourceId() && + // and will be shown + showNode(usedSlot && views[j].dynamicallyUsed, false) && // and we have two behind us and one ahead - j >= 2 && j + 1 < views.size() && - // last two are empty + j >= 2 && j + 1 <= lastView && + // last two are empty and were be shown views[j - 2].resourceId == ResourceId() && views[j - 1].resourceId == ResourceId() && - // next one is empty - views[j + 1].resourceId == ResourceId()) + showNode(usedSlot && views[j - 2].dynamicallyUsed, false) && + showNode(usedSlot && views[j - 1].dynamicallyUsed, false) && + // next one is empty and will be shown + views[j + 1].resourceId == ResourceId() && + showNode(usedSlot && views[j + 1].dynamicallyUsed, false)) { // if we haven't started omitting empty rows, add an empty row if(!omittingEmpty) @@ -1443,6 +1464,10 @@ void D3D12PipelineStateViewer::setState() return; } + // cache latest state of these checkboxes + m_ShowUnused = ui->showUnused->isChecked(); + m_ShowEmpty = ui->showEmpty->isChecked(); + const D3D12Pipe::State &state = *m_Ctx.CurD3D12PipelineState(); const ActionDescription *action = m_Ctx.CurAction(); @@ -1542,7 +1567,7 @@ void D3D12PipelineStateViewer::setState() if(state.inputAssembly.indexBuffer.resourceId != ResourceId()) { - if(ibufferUsed || ui->showUnused->isChecked()) + if(ibufferUsed || m_ShowUnused) { uint64_t length = state.inputAssembly.indexBuffer.byteSize; @@ -1599,7 +1624,7 @@ void D3D12PipelineStateViewer::setState() } else { - if(ibufferUsed || ui->showEmpty->isChecked()) + if(ibufferUsed || m_ShowEmpty) { RDTreeWidgetItem *node = new RDTreeWidgetItem( {tr("Index"), tr("No Buffer Set"), lit("-"), lit("-"), lit("-"), QString()}); diff --git a/qrenderdoc/Windows/PipelineState/D3D12PipelineStateViewer.h b/qrenderdoc/Windows/PipelineState/D3D12PipelineStateViewer.h index aadf96b00..7f825627c 100644 --- a/qrenderdoc/Windows/PipelineState/D3D12PipelineStateViewer.h +++ b/qrenderdoc/Windows/PipelineState/D3D12PipelineStateViewer.h @@ -108,6 +108,9 @@ private: bool showNode(bool usedSlot, bool filledSlot); + bool m_ShowUnused = false; + bool m_ShowEmpty = false; + QVariantList exportViewHTML(const D3D12Pipe::View &view, bool rw, const ShaderResource *shaderInput, const QString &extraParams); void exportHTML(QXmlStreamWriter &xml, const D3D12Pipe::InputAssembly &ia); diff --git a/renderdoc/api/replay/d3d12_pipestate.h b/renderdoc/api/replay/d3d12_pipestate.h index 66a6a3d22..64c22fcd4 100644 --- a/renderdoc/api/replay/d3d12_pipestate.h +++ b/renderdoc/api/replay/d3d12_pipestate.h @@ -282,6 +282,15 @@ struct View :type: TextureSwizzle4 )"); TextureSwizzle4 swizzle; + DOCUMENT(R"(``True`` if this binding element is dynamically used. + +If set to ``False`` this means that the binding was available to the shader but during execution it +was not referenced. The data gathered for setting this variable is conservative, meaning that only +accesses through arrays will have this calculated to reduce the required feedback bandwidth - single +non-arrayed descriptors may have this value set to ``True`` even if the shader did not use them, +since single descriptors may only be dynamically skipped by control flow. +)"); + bool dynamicallyUsed = true; DOCUMENT("The :class:`D3DBufferViewFlags` set for the buffer."); D3DBufferViewFlags bufferFlags = D3DBufferViewFlags::NoFlags; DOCUMENT("If the view has a hidden counter, this stores the current value of the counter."); @@ -464,8 +473,9 @@ struct RootSignatureRange bool operator==(const RootSignatureRange &o) const { return immediate == o.immediate && rootElement == o.rootElement && visibility == o.visibility && - registerSpace == o.registerSpace && constantBuffers == o.constantBuffers && - samplers == o.samplers && views == o.views; + registerSpace == o.registerSpace && dynamicallyUsedCount == o.dynamicallyUsedCount && + firstUsedIndex == o.firstUsedIndex && lastUsedIndex == o.lastUsedIndex && + constantBuffers == o.constantBuffers && samplers == o.samplers && views == o.views; } bool operator<(const RootSignatureRange &o) const { @@ -477,6 +487,12 @@ struct RootSignatureRange return visibility < o.visibility; if(!(registerSpace == o.registerSpace)) return registerSpace < o.registerSpace; + if(!(dynamicallyUsedCount == o.dynamicallyUsedCount)) + return dynamicallyUsedCount < o.dynamicallyUsedCount; + if(!(firstUsedIndex == o.firstUsedIndex)) + return firstUsedIndex < o.firstUsedIndex; + if(!(lastUsedIndex == o.lastUsedIndex)) + return lastUsedIndex < o.lastUsedIndex; if(!(constantBuffers == o.constantBuffers)) return constantBuffers < o.constantBuffers; if(!(samplers == o.samplers)) @@ -496,6 +512,28 @@ struct RootSignatureRange ShaderStageMask visibility = ShaderStageMask::All; DOCUMENT("The register space of this element."); uint32_t registerSpace; + DOCUMENT(R"(Lists how many bindings in :data:`views` are dynamically used. Useful to avoid +redundant iteration to determine whether any bindings are present. + +For more information see :data:`D3D12View.dynamicallyUsed`. +)"); + uint32_t dynamicallyUsedCount = ~0U; + DOCUMENT(R"(Gives the index of the first binding in :data:`views` that is dynamically used. Useful +to avoid redundant iteration in very large descriptor arrays with a small subset that are used. + +For more information see :data:`D3D12View.dynamicallyUsed`. +)"); + int32_t firstUsedIndex = 0; + DOCUMENT(R"(Gives the index of the first binding in :data:`views` that is dynamically used. Useful +to avoid redundant iteration in very large descriptor arrays with a small subset that are used. + +.. note:: + This may be set to a higher value than the number of bindings, if no dynamic use information is + available. Ensure that this is an additional check on the bind and the count is still respected. + +For more information see :data:`D3D12View.dynamicallyUsed`. +)"); + int32_t lastUsedIndex = 0x7fffffff; DOCUMENT(R"(The constant buffers in this range. :type: List[D3D12ConstantBuffer] diff --git a/renderdoc/api/replay/pipestate.inl b/renderdoc/api/replay/pipestate.inl index e226400b9..5d58e321d 100644 --- a/renderdoc/api/replay/pipestate.inl +++ b/renderdoc/api/replay/pipestate.inl @@ -1272,6 +1272,8 @@ rdcarray PipeState::GetReadOnlyResources(ShaderStage stage, rdcarray &val = ret.back().resources; + ret.back().dynamicallyUsedCount = 0; + for(size_t i = 0; i < m_D3D12->rootElements.size(); ++i) { const D3D12Pipe::RootSignatureRange &element = m_D3D12->rootElements[i]; @@ -1281,7 +1283,19 @@ rdcarray PipeState::GetReadOnlyResources(ShaderStage stage, if(element.type == BindType::ReadOnlyResource && element.registerSpace == (uint32_t)bind.bindset) { - for(size_t j = 0; j < element.views.size(); ++j) + size_t firstIdx = 0; + size_t count = element.views.size(); + + if(onlyUsed && val.empty()) + { + firstIdx = (uint32_t)element.firstUsedIndex; + count = std::min(count - firstIdx, + size_t(element.lastUsedIndex - element.firstUsedIndex + 1)); + + ret.back().firstIndex = (int32_t)firstIdx; + } + + for(size_t j = firstIdx; j < count; ++j) { const D3D12Pipe::View &view = element.views[j]; if(view.bind >= start && view.bind < end) @@ -1289,9 +1303,13 @@ rdcarray PipeState::GetReadOnlyResources(ShaderStage stage, val.push_back(BoundResource()); BoundResource &b = val.back(); b.resourceId = view.resourceId; + b.dynamicallyUsed = view.dynamicallyUsed; b.firstMip = (int)view.firstMip; b.firstSlice = (int)view.firstSlice; b.typeCast = view.viewFormat.compType; + + if(view.dynamicallyUsed) + ret.back().dynamicallyUsedCount++; } } } @@ -1352,7 +1370,8 @@ rdcarray PipeState::GetReadOnlyResources(ShaderStage stage, if(onlyUsed) { firstIdx = (uint32_t)bind.firstUsedIndex; - count = std::min(count, uint32_t(bind.lastUsedIndex - bind.firstUsedIndex + 1)); + count = + std::min(count - firstIdx, uint32_t(bind.lastUsedIndex - bind.firstUsedIndex + 1)); } rdcarray &val = ret.back().resources; @@ -1454,6 +1473,8 @@ rdcarray PipeState::GetReadWriteResources(ShaderStage stage, rdcarray &val = ret.back().resources; + ret.back().dynamicallyUsedCount = 0; + for(size_t i = 0; i < m_D3D12->rootElements.size(); ++i) { const D3D12Pipe::RootSignatureRange &element = m_D3D12->rootElements[i]; @@ -1463,7 +1484,19 @@ rdcarray PipeState::GetReadWriteResources(ShaderStage stage, if(element.type == BindType::ReadWriteResource && element.registerSpace == (uint32_t)bind.bindset) { - for(size_t j = 0; j < element.views.size(); ++j) + size_t firstIdx = 0; + size_t count = element.views.size(); + + if(onlyUsed && val.empty()) + { + firstIdx = (uint32_t)element.firstUsedIndex; + count = std::min(count - firstIdx, + size_t(element.lastUsedIndex - element.firstUsedIndex + 1)); + + ret.back().firstIndex = (int32_t)firstIdx; + } + + for(size_t j = firstIdx; j < count; ++j) { const D3D12Pipe::View &view = element.views[j]; if(view.bind >= start && view.bind < end) @@ -1471,9 +1504,13 @@ rdcarray PipeState::GetReadWriteResources(ShaderStage stage, val.push_back(BoundResource()); BoundResource &b = val.back(); b.resourceId = view.resourceId; + b.dynamicallyUsed = view.dynamicallyUsed; b.firstMip = (int)view.firstMip; b.firstSlice = (int)view.firstSlice; b.typeCast = view.viewFormat.compType; + + if(view.dynamicallyUsed) + ret.back().dynamicallyUsedCount++; } } } @@ -1530,7 +1567,8 @@ rdcarray PipeState::GetReadWriteResources(ShaderStage stage, if(onlyUsed) { firstIdx = (uint32_t)bind.firstUsedIndex; - count = std::min(count, uint32_t(bind.lastUsedIndex - bind.firstUsedIndex + 1)); + count = + std::min(count - firstIdx, uint32_t(bind.lastUsedIndex - bind.firstUsedIndex + 1)); } rdcarray &val = ret.back().resources; diff --git a/renderdoc/api/replay/replay_enums.h b/renderdoc/api/replay/replay_enums.h index 68fb33d57..2dc5c8991 100644 --- a/renderdoc/api/replay/replay_enums.h +++ b/renderdoc/api/replay/replay_enums.h @@ -421,7 +421,7 @@ DOCUMENT(R"(A single source component for a destination texture swizzle. The fixed value ``1``. )"); -enum class TextureSwizzle : uint32_t +enum class TextureSwizzle : uint8_t { Red, Green, @@ -4077,7 +4077,7 @@ DOCUMENT(R"(A set of flags for D3D buffer view properties. The buffer is used with a structured buffer with associated hidden counter. )"); -enum class D3DBufferViewFlags : uint32_t +enum class D3DBufferViewFlags : uint8_t { NoFlags = 0x0, Raw = 0x1, diff --git a/renderdoc/api/replay/vk_pipestate.h b/renderdoc/api/replay/vk_pipestate.h index 7cfd69e76..7206c26b1 100644 --- a/renderdoc/api/replay/vk_pipestate.h +++ b/renderdoc/api/replay/vk_pipestate.h @@ -231,6 +231,7 @@ struct DescriptorBinding bool operator==(const DescriptorBinding &o) const { return descriptorCount == o.descriptorCount && dynamicallyUsedCount == o.dynamicallyUsedCount && + firstUsedIndex == o.firstUsedIndex && lastUsedIndex == o.lastUsedIndex && type == o.type && stageFlags == o.stageFlags && binds == o.binds; } bool operator<(const DescriptorBinding &o) const @@ -239,6 +240,10 @@ struct DescriptorBinding return descriptorCount < o.descriptorCount; if(!(dynamicallyUsedCount == o.dynamicallyUsedCount)) return dynamicallyUsedCount < o.dynamicallyUsedCount; + if(!(firstUsedIndex == o.firstUsedIndex)) + return firstUsedIndex < o.firstUsedIndex; + if(!(lastUsedIndex == o.lastUsedIndex)) + return lastUsedIndex < o.lastUsedIndex; if(!(type == o.type)) return type < o.type; if(!(stageFlags == o.stageFlags)) diff --git a/renderdoc/driver/d3d11/renderdoc_d3d11.vcxproj.filters b/renderdoc/driver/d3d11/renderdoc_d3d11.vcxproj.filters index 0b0c771e3..fff686354 100644 --- a/renderdoc/driver/d3d11/renderdoc_d3d11.vcxproj.filters +++ b/renderdoc/driver/d3d11/renderdoc_d3d11.vcxproj.filters @@ -185,6 +185,8 @@ Wrapped - + + Hooks + \ No newline at end of file diff --git a/renderdoc/driver/d3d12/d3d12_debug.h b/renderdoc/driver/d3d12/d3d12_debug.h index f27a47a2b..5b7e822bf 100644 --- a/renderdoc/driver/d3d12/d3d12_debug.h +++ b/renderdoc/driver/d3d12/d3d12_debug.h @@ -64,6 +64,8 @@ enum CBVUAVSRVSlot TMP_UAV, + FEEDBACK_CLEAR_UAV, + MSAA_SRV2x, MSAA_SRV4x, MSAA_SRV8x, diff --git a/renderdoc/driver/d3d12/d3d12_replay.cpp b/renderdoc/driver/d3d12/d3d12_replay.cpp index 4b129cfcc..6dae02236 100644 --- a/renderdoc/driver/d3d12/d3d12_replay.cpp +++ b/renderdoc/driver/d3d12/d3d12_replay.cpp @@ -168,6 +168,7 @@ void D3D12Replay::CreateResources() void D3D12Replay::DestroyResources() { ClearPostVSCache(); + ClearFeedbackCache(); m_General.Release(); m_TexRender.Release(); @@ -176,6 +177,8 @@ void D3D12Replay::DestroyResources() m_PixelPick.Release(); m_Histogram.Release(); + SAFE_RELEASE(m_BindlessFeedback.FeedbackBuffer); + SAFE_RELEASE(m_SOBuffer); SAFE_RELEASE(m_SOStagingBuffer); SAFE_RELEASE(m_SOPatchedIndexBuffer); @@ -926,7 +929,7 @@ ShaderStageMask ToShaderStageMask(D3D12_SHADER_VISIBILITY vis) } } -void D3D12Replay::FillRootElements(const D3D12RenderState::RootSignature &rootSig, +void D3D12Replay::FillRootElements(uint32_t eventId, const D3D12RenderState::RootSignature &rootSig, const ShaderBindpointMapping *mappings[(uint32_t)ShaderStage::Count], rdcarray &rootElements) { @@ -935,6 +938,12 @@ void D3D12Replay::FillRootElements(const D3D12RenderState::RootSignature &rootSi D3D12ResourceManager *rm = m_pDevice->GetResourceManager(); + const D3D12DynamicShaderFeedback &usage = m_BindlessFeedback.Usage[eventId]; + + const D3D12FeedbackBindIdentifier *curUsage = usage.used.begin(); + const D3D12FeedbackBindIdentifier *lastUsage = usage.used.end(); + D3D12FeedbackBindIdentifier curIdentifier; + WrappedID3D12RootSignature *sig = m_pDevice->GetResourceManager()->GetCurrentAs(rootSig.rootsig); @@ -944,6 +953,8 @@ void D3D12Replay::FillRootElements(const D3D12RenderState::RootSignature &rootSi for(size_t rootEl = 0; rootEl < sig->sig.Parameters.size(); rootEl++) { + curIdentifier.rootEl = rootEl; + const D3D12RootSignatureParameter &p = sig->sig.Parameters[rootEl]; if(p.ParameterType == D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS) @@ -1027,6 +1038,8 @@ void D3D12Replay::FillRootElements(const D3D12RenderState::RootSignature &rootSi element.views.push_back(D3D12Pipe::View(p.Descriptor.ShaderRegister)); D3D12Pipe::View &view = element.views.back(); + view.dynamicallyUsed = true; + if(rootEl < rootSig.sigelems.size()) { const D3D12RenderState::SignatureElement &e = rootSig.sigelems[rootEl]; @@ -1065,6 +1078,8 @@ void D3D12Replay::FillRootElements(const D3D12RenderState::RootSignature &rootSi element.views.push_back(D3D12Pipe::View(p.Descriptor.ShaderRegister)); D3D12Pipe::View &view = element.views.back(); + view.dynamicallyUsed = true; + if(rootEl < rootSig.sigelems.size()) { const D3D12RenderState::SignatureElement &e = rootSig.sigelems[rootEl]; @@ -1104,6 +1119,8 @@ void D3D12Replay::FillRootElements(const D3D12RenderState::RootSignature &rootSi { const D3D12_DESCRIPTOR_RANGE1 &range = p.ranges[r]; + curIdentifier.rangeIndex = r; + // Here we diverge slightly from how root signatures store data. A descriptor table can // contain multiple ranges which can each contain different types. D3D12Pipe treats // each range as a separate RootElement @@ -1146,10 +1163,13 @@ void D3D12Replay::FillRootElements(const D3D12RenderState::RootSignature &rootSi // Find shader binds that map any or all of this range to see if we can trim it for(ShaderStage stage = ShaderStage::Vertex; stage < ShaderStage::Count; ++stage) { + const ShaderBindpointMapping *mapping = mappings[(uint32_t)stage]; if((element.visibility & MaskForStage(stage)) != ShaderStageMask::Unknown) { // This range is visible to this shader stage, check its mappings - const rdcarray &bps = mappings[(uint32_t)stage]->readOnlyResources; + const rdcarray &bps = range.RangeType == D3D12_DESCRIPTOR_RANGE_TYPE_UAV + ? mapping->readWriteResources + : mapping->readOnlyResources; for(size_t b = 0; b < bps.size(); ++b) { if(bps[b].bindset == (int32_t)element.registerSpace && @@ -1237,9 +1257,16 @@ void D3D12Replay::FillRootElements(const D3D12RenderState::RootSignature &rootSi } } } - else if(range.RangeType == D3D12_DESCRIPTOR_RANGE_TYPE_SRV) + else if(range.RangeType == D3D12_DESCRIPTOR_RANGE_TYPE_SRV || + range.RangeType == D3D12_DESCRIPTOR_RANGE_TYPE_UAV) { - element.type = BindType::ReadOnlyResource; + if(range.RangeType == D3D12_DESCRIPTOR_RANGE_TYPE_SRV) + element.type = BindType::ReadOnlyResource; + else + element.type = BindType::ReadWriteResource; + + if(usage.valid) + element.dynamicallyUsedCount = 0; for(UINT i = 0; i < num; i++, shaderReg++) { @@ -1247,22 +1274,59 @@ void D3D12Replay::FillRootElements(const D3D12RenderState::RootSignature &rootSi D3D12Pipe::View &view = element.views.back(); view.tableIndex = offset + i; - if(desc) + if(usage.valid) { - FillResourceView(view, desc); - desc++; - } - } - } - else if(range.RangeType == D3D12_DESCRIPTOR_RANGE_TYPE_UAV) - { - element.type = BindType::ReadWriteResource; + curIdentifier.descIndex = i; - for(UINT i = 0; i < num; i++, shaderReg++) - { - element.views.push_back(D3D12Pipe::View(shaderReg)); - D3D12Pipe::View &view = element.views.back(); - view.tableIndex = offset + i; + // if the usage data is valid we expect entries for all descriptors that are used. + // Note that because of the messed up root signature/binding split, some of these + // entries will just be statically inserted and not actually from dynamic feedback + // + // usage data is only valid when at least one array exists to have feedback + if(curUsage >= lastUsage) + { + // if we exhausted the list, all other elements are unused + view.dynamicallyUsed = false; + } + else + { + // we never saw the current value of curUsage (which is odd, we should have + // when iterating over all descriptors. This could only happen if there's some + // layout mismatch or a feedback bug that lead to an invalid entry in the list). + // Keep advancing until we get to one that is >= our current bind + while(*curUsage < curIdentifier && curUsage < lastUsage) + { + curUsage++; + } + + // the next used bind is equal to this one. Mark it as dynamically used, and consume + if(curUsage < lastUsage && *curUsage == curIdentifier) + { + view.dynamicallyUsed = true; + curUsage++; + } + // the next used bind is after the current one, this is not used. + else if(curUsage < lastUsage && curIdentifier < *curUsage) + { + view.dynamicallyUsed = false; + } + } + } + else + { + view.dynamicallyUsed = true; + } + + if(view.dynamicallyUsed) + { + element.dynamicallyUsedCount++; + // we iterate in forward order, so we can unconditinoally set the last bind to the + // current one, and only set the first bind if we haven't encountered one before + element.lastUsedIndex = i; + + if(element.firstUsedIndex < 0) + element.firstUsedIndex = i; + } if(desc) { @@ -1340,6 +1404,13 @@ void D3D12Replay::SavePipelineState(uint32_t eventId) { const D3D12RenderState &rs = m_pDevice->GetQueue()->GetCommandData()->m_RenderState; + D3D12MarkerRegion::Begin(m_pDevice->GetQueue(), + StringFormat::Fmt("FetchShaderFeedback for %u", eventId)); + + FetchShaderFeedback(eventId); + + D3D12MarkerRegion::End(m_pDevice->GetQueue()); + D3D12Pipe::State &state = m_PipelineState; ///////////////////////////////////////////////// @@ -1464,12 +1535,12 @@ void D3D12Replay::SavePipelineState(uint32_t eventId) if(pipe && pipe->IsCompute()) { - FillRootElements(rs.compute, mappings, state.rootElements); + FillRootElements(eventId, rs.compute, mappings, state.rootElements); state.rootSignatureResourceId = rm->GetOriginalID(rs.compute.rootsig); } else if(pipe) { - FillRootElements(rs.graphics, mappings, state.rootElements); + FillRootElements(eventId, rs.graphics, mappings, state.rootElements); state.rootSignatureResourceId = rm->GetOriginalID(rs.graphics.rootsig); } } @@ -2991,6 +3062,7 @@ void D3D12Replay::ReplaceResource(ResourceId from, ResourceId to) RefreshDerivedReplacements(); ClearPostVSCache(); + ClearFeedbackCache(); } void D3D12Replay::RemoveReplacement(ResourceId id) @@ -3002,6 +3074,7 @@ void D3D12Replay::RemoveReplacement(ResourceId id) RefreshDerivedReplacements(); ClearPostVSCache(); + ClearFeedbackCache(); } } diff --git a/renderdoc/driver/d3d12/d3d12_replay.h b/renderdoc/driver/d3d12/d3d12_replay.h index e7841f4b7..af1f68b35 100644 --- a/renderdoc/driver/d3d12/d3d12_replay.h +++ b/renderdoc/driver/d3d12/d3d12_replay.h @@ -50,6 +50,27 @@ enum TexDisplayFlags eTexDisplay_RemapSInt = 0x40, }; +struct D3D12FeedbackBindIdentifier +{ + size_t rootEl; + size_t rangeIndex; + UINT descIndex; + + bool operator<(const D3D12FeedbackBindIdentifier &o) const + { + if(rootEl != o.rootEl) + return rootEl < o.rootEl; + if(rangeIndex != o.rangeIndex) + return rangeIndex < o.rangeIndex; + return descIndex < o.descIndex; + } + + bool operator==(const D3D12FeedbackBindIdentifier &o) const + { + return rootEl == o.rootEl && rangeIndex == o.rangeIndex && descIndex == o.descIndex; + } +}; + class D3D12Replay : public IReplayDriver { public: @@ -214,14 +235,16 @@ public: void FileChanged() {} AMDCounters *GetAMDCounters() { return m_pAMDCounters; } private: - void FillRootElements(const D3D12RenderState::RootSignature &rootSig, + void FillRootElements(uint32_t eventId, const D3D12RenderState::RootSignature &rootSig, const ShaderBindpointMapping *mappings[(uint32_t)ShaderStage::Count], rdcarray &rootElements); void FillResourceView(D3D12Pipe::View &view, const D3D12Descriptor *desc); + bool CreateSOBuffers(); void ClearPostVSCache(); - bool CreateSOBuffers(); + void FetchShaderFeedback(uint32_t eventId); + void ClearFeedbackCache(); void RefreshDerivedReplacements(); @@ -238,6 +261,19 @@ private: m_OutputHeight = (float)h; } + struct D3D12DynamicShaderFeedback + { + bool compute = false, valid = false; + rdcarray used; + }; + + struct Feedback + { + ID3D12Resource *FeedbackBuffer = NULL; + + std::unordered_map Usage; + } m_BindlessFeedback; + struct D3D12PostVSData { struct InstData diff --git a/renderdoc/driver/d3d12/d3d12_shader_feedback.cpp b/renderdoc/driver/d3d12/d3d12_shader_feedback.cpp new file mode 100644 index 000000000..759db1fe2 --- /dev/null +++ b/renderdoc/driver/d3d12/d3d12_shader_feedback.cpp @@ -0,0 +1,604 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2021 Baldur Karlsson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + ******************************************************************************/ + +#include "core/settings.h" +#include "driver/shaders/dxbc/dxbc_bytecode_editor.h" +#include "d3d12_command_queue.h" +#include "d3d12_debug.h" +#include "d3d12_device.h" +#include "d3d12_replay.h" +#include "d3d12_resources.h" +#include "d3d12_shader_cache.h" + +RDOC_CONFIG(rdcstr, D3D12_Debug_FeedbackDumpDirPath, "", + "Path to dump bindless feedback annotation generated DXBC/DXIL files."); +RDOC_CONFIG(bool, D3D12_Experimental_BindlessFeedback, true, + "EXPERIMENTAL: Enable fetching from GPU which descriptors were dynamically used in " + "descriptor arrays."); + +struct D3D12FeedbackKey +{ + DXBCBytecode::OperandType type; + Bindpoint bind; + + bool operator<(const D3D12FeedbackKey &key) const + { + if(type != key.type) + return type < key.type; + return bind < key.bind; + } +}; + +struct D3D12FeedbackSlot +{ +public: + D3D12FeedbackSlot() + { + slot = 0; + used = 0; + } + void SetSlot(uint32_t s) { slot = s; } + void SetStaticUsed() { used = 0x1; } + bool StaticUsed() const { return used != 0x0; } + uint32_t Slot() const { return slot; } +private: + uint32_t slot : 31; + uint32_t used : 1; +}; + +static bool AnnotateShader(const DXBC::DXBCContainer *dxbc, uint32_t space, + const std::map &slots, + bytebuf &editedBlob) +{ + using namespace DXBCBytecode; + using namespace DXBCBytecode::Edit; + + ProgramEditor editor(dxbc, editedBlob); + + // get ourselves a temp + uint32_t t = editor.AddTemp(); + + // declare the output UAV + ResourceDecl desc; + desc.compType = CompType::UInt; + desc.type = TextureType::Buffer; + desc.raw = true; + + ResourceIdentifier u = {~0U, ~0U}; + + for(size_t i = 0; i < editor.GetNumInstructions(); i++) + { + const Operation &op = editor.GetInstruction(i); + + for(const Operand &operand : op.operands) + { + if(operand.type != TYPE_RESOURCE && operand.type != TYPE_UNORDERED_ACCESS_VIEW) + continue; + + const Declaration *decl = + editor.FindDeclaration(operand.type, (uint32_t)operand.indices[0].index); + + if(!decl) + { + RDCERR("Couldn't find declaration for %d operand identifier %u", operand.type, + (uint32_t)operand.indices[0].index); + continue; + } + + // ignore non-arrayed declarations + if(decl->operand.indices[1].index == decl->operand.indices[2].index) + continue; + + // the operand should be relative addressing like r0.x + 6 for a t6 resource being indexed + // with [r0.x] + RDCASSERT(operand.indices[1].relative && + operand.indices[1].index == decl->operand.indices[1].index); + + D3D12FeedbackKey key; + key.type = operand.type; + key.bind.bindset = decl->space; + key.bind.bind = (int32_t)decl->operand.indices[1].index; + + auto it = slots.find(key); + + if(it == slots.end()) + { + RDCERR("Couldn't find reserved base slot for %d at space %u and bind %u", key.type, + key.bind.bindset, key.bind.bind); + continue; + } + + // should be getting a scalar index + if(operand.indices[1].operand.comps[1] != 0xff || + operand.indices[1].operand.comps[2] != 0xff || operand.indices[1].operand.comps[3] != 0xff) + { + RDCERR("Unexpected vector index for resource: %s", + operand.toString(dxbc->GetReflection(), ToString::None).c_str()); + continue; + } + + if(u.first == ~0U && u.second == ~0U) + u = editor.DeclareUAV(desc, space, 0, 0); + + // resource base plus index + editor.InsertOperation(i++, oper(OPCODE_IADD, {temp(t).swizzle(0), imm(it->second.Slot()), + operand.indices[1].operand})); + // multiply by 4 for byte index + editor.InsertOperation(i++, + oper(OPCODE_ISHL, {temp(t).swizzle(0), temp(t).swizzle(0), imm(2)})); + // atomic or the slot + editor.InsertOperation(i++, oper(OPCODE_ATOMIC_OR, {uav(u), temp(t).swizzle(0), imm(~0U)})); + + // only one resource operand per instruction + break; + } + } + + if(u.first != ~0U || u.second != ~0U) + { + editor.InsertOperation(0, oper(OPCODE_MOV, {temp(t).swizzle(0), imm(0)})); + editor.InsertOperation(1, oper(OPCODE_ATOMIC_OR, {uav(u), temp(t).swizzle(0), imm(~0U)})); + return true; + } + + return false; +} + +static void AddArraySlots(WrappedID3D12PipelineState::ShaderEntry *shad, uint32_t space, + uint32_t maxDescriptors, + std::map &slots, uint32_t &numSlots, + bytebuf &editedBlob, D3D12_SHADER_BYTECODE &desc) +{ + if(!shad) + return; + + ShaderReflection &refl = shad->GetDetails(); + const ShaderBindpointMapping &mapping = shad->GetMapping(); + + for(const ShaderResource &ro : refl.readOnlyResources) + { + const Bindpoint &bind = mapping.readOnlyResources[ro.bindPoint]; + if(bind.arraySize > 1) + { + D3D12FeedbackKey key; + key.type = DXBCBytecode::TYPE_RESOURCE; + key.bind = bind; + + slots[key].SetSlot(numSlots); + numSlots += RDCMIN(maxDescriptors, bind.arraySize); + } + else if(bind.arraySize <= 1 && bind.used) + { + // since the eventual descriptor range iteration won't know which descriptors map to arrays + // and which to fixed slots, it can't mark fixed descriptors as dynamically used itself. So + // instead we don't reserve a slot and set the top bit for these binds to indicate that + // they're fixed used. This allows for overlap between an array and a fixed resource which is + // allowed + D3D12FeedbackKey key; + key.type = DXBCBytecode::TYPE_RESOURCE; + key.bind = bind; + + slots[key].SetStaticUsed(); + } + } + + for(const ShaderResource &rw : refl.readWriteResources) + { + const Bindpoint &bind = mapping.readWriteResources[rw.bindPoint]; + if(bind.arraySize > 1) + { + D3D12FeedbackKey key; + key.type = DXBCBytecode::TYPE_UNORDERED_ACCESS_VIEW; + key.bind = bind; + + slots[key].SetSlot(numSlots); + numSlots += RDCMIN(maxDescriptors, bind.arraySize); + } + else if(bind.arraySize <= 1 && bind.used) + { + // since the eventual descriptor range iteration won't know which descriptors map to arrays + // and which to fixed slots, it can't mark fixed descriptors as dynamically used itself. So + // instead we don't reserve a slot and set the top bit for these binds to indicate that + // they're fixed used. This allows for overlap between an array and a fixed resource which is + // allowed + D3D12FeedbackKey key; + key.type = DXBCBytecode::TYPE_UNORDERED_ACCESS_VIEW; + key.bind = bind; + + slots[key].SetStaticUsed(); + } + } + + if(shad->GetDXBC()->m_Version.Major > 6) + { + RDCERR("DXIL shaders are not supported for bindless feedback currently"); + } + else + { + // only SM5.1 can have dynamic array indexing + if(shad->GetDXBC()->m_Version.Major == 5 && shad->GetDXBC()->m_Version.Minor == 1) + { + if(AnnotateShader(shad->GetDXBC(), space, slots, editedBlob)) + { + if(!D3D12_Debug_FeedbackDumpDirPath().empty()) + FileIO::WriteAll(D3D12_Debug_FeedbackDumpDirPath() + "/before_dxbc_" + + ToStr(shad->GetDetails().stage).c_str() + ".dxbc", + shad->GetDXBC()->GetShaderBlob()); + + if(!D3D12_Debug_FeedbackDumpDirPath().empty()) + FileIO::WriteAll(D3D12_Debug_FeedbackDumpDirPath() + "/after_dxbc_" + + ToStr(shad->GetDetails().stage).c_str() + ".dxbc", + editedBlob); + + desc.pShaderBytecode = editedBlob.data(); + desc.BytecodeLength = editedBlob.size(); + } + } + } +} + +void D3D12Replay::FetchShaderFeedback(uint32_t eventId) +{ + if(m_BindlessFeedback.Usage.find(eventId) != m_BindlessFeedback.Usage.end()) + return; + + if(!D3D12_Experimental_BindlessFeedback()) + return; + + // create it here so we won't re-run any code if the event is re-selected. We'll mark it as valid + // if it actually has any data in it later. + D3D12DynamicShaderFeedback &result = m_BindlessFeedback.Usage[eventId]; + + const ActionDescription *action = m_pDevice->GetAction(eventId); + + if(action == NULL || !(action->flags & (ActionFlags::Dispatch | ActionFlags::Drawcall))) + return; + + result.compute = bool(action->flags & ActionFlags::Dispatch); + + D3D12RenderState &rs = m_pDevice->GetQueue()->GetCommandData()->m_RenderState; + + D3D12ResourceManager *rm = m_pDevice->GetResourceManager(); + + WrappedID3D12PipelineState *pipe = + (WrappedID3D12PipelineState *)rm->GetCurrentAs(rs.pipe); + D3D12RootSignature modsig; + + bytebuf editedBlob[5]; + + D3D12_EXPANDED_PIPELINE_STATE_STREAM_DESC pipeDesc; + pipe->Fill(pipeDesc); + + uint32_t space = 1; + + uint32_t maxDescriptors = 0; + for(ResourceId id : rs.heaps) + { + D3D12_DESCRIPTOR_HEAP_DESC desc = rm->GetCurrentAs(id)->GetDesc(); + + if(desc.Type == D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV) + { + maxDescriptors = desc.NumDescriptors; + RDCDEBUG("Clamping any unbounded ranges to %u descriptors", maxDescriptors); + break; + } + } + + std::map slots[6]; + + // reserve the first 4 dwords for debug info and a validity flag + uint32_t numSlots = 4; + + if(result.compute) + { + ID3D12RootSignature *sig = rm->GetCurrentAs(rs.compute.rootsig); + modsig = ((WrappedID3D12RootSignature *)sig)->sig; + + space = modsig.maxSpaceIndex; + + AddArraySlots(pipe->CS(), space, maxDescriptors, slots[0], numSlots, editedBlob[0], pipeDesc.CS); + } + else + { + ID3D12RootSignature *sig = rm->GetCurrentAs(rs.graphics.rootsig); + modsig = ((WrappedID3D12RootSignature *)sig)->sig; + + space = modsig.maxSpaceIndex; + + AddArraySlots(pipe->VS(), space, maxDescriptors, slots[0], numSlots, editedBlob[0], pipeDesc.VS); + AddArraySlots(pipe->HS(), space, maxDescriptors, slots[1], numSlots, editedBlob[1], pipeDesc.HS); + AddArraySlots(pipe->DS(), space, maxDescriptors, slots[2], numSlots, editedBlob[2], pipeDesc.DS); + AddArraySlots(pipe->GS(), space, maxDescriptors, slots[3], numSlots, editedBlob[3], pipeDesc.GS); + AddArraySlots(pipe->PS(), space, maxDescriptors, slots[4], numSlots, editedBlob[4], pipeDesc.PS); + } + + // if numSlots was 0, none of the resources were arrayed so we have nothing to do. Silently return + if(numSlots == 0) + return; + + // need to be able to add a descriptor of our UAV without hitting the 64 DWORD limit + if(modsig.dwordLength > 62) + { + RDCWARN("Root signature is 64 DWORDS, adding feedback buffer might fail"); + } + + // add root UAV element + modsig.Parameters.push_back(D3D12RootSignatureParameter()); + { + D3D12RootSignatureParameter ¶m = modsig.Parameters.back(); + param.ParameterType = D3D12_ROOT_PARAMETER_TYPE_UAV; + param.ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL; + param.Descriptor.Flags = D3D12_ROOT_DESCRIPTOR_FLAG_DATA_VOLATILE; + param.Descriptor.RegisterSpace = space; + param.Descriptor.ShaderRegister = 0; + } + + if(m_BindlessFeedback.FeedbackBuffer == NULL || + m_BindlessFeedback.FeedbackBuffer->GetDesc().Width < numSlots * sizeof(uint32_t)) + { + SAFE_RELEASE(m_BindlessFeedback.FeedbackBuffer); + + D3D12_RESOURCE_DESC desc = {}; + desc.Alignment = 0; + desc.DepthOrArraySize = 1; + desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; + desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS; + desc.Format = DXGI_FORMAT_UNKNOWN; + desc.Height = 1; + desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; + desc.MipLevels = 1; + desc.SampleDesc.Count = 1; + desc.SampleDesc.Quality = 0; + desc.Width = numSlots * sizeof(uint32_t); + + D3D12_HEAP_PROPERTIES heapProps; + heapProps.Type = D3D12_HEAP_TYPE_DEFAULT; + heapProps.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN; + heapProps.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN; + heapProps.CreationNodeMask = 1; + heapProps.VisibleNodeMask = 1; + + HRESULT hr = m_pDevice->CreateCommittedResource( + &heapProps, D3D12_HEAP_FLAG_NONE, &desc, D3D12_RESOURCE_STATE_UNORDERED_ACCESS, NULL, + __uuidof(ID3D12Resource), (void **)&m_BindlessFeedback.FeedbackBuffer); + + if(m_BindlessFeedback.FeedbackBuffer == NULL || FAILED(hr)) + { + RDCERR("Couldn't create feedback buffer with %u slots: %s", numSlots, ToStr(hr).c_str()); + return; + } + + m_BindlessFeedback.FeedbackBuffer->SetName(L"m_BindlessFeedback.FeedbackBuffer"); + } + + { + D3D12_UNORDERED_ACCESS_VIEW_DESC uavDesc = {}; + uavDesc.ViewDimension = D3D12_UAV_DIMENSION_BUFFER; + uavDesc.Format = DXGI_FORMAT_R32_UINT; + uavDesc.Buffer.CounterOffsetInBytes = 0; + // start with elements after the counter + uavDesc.Buffer.FirstElement = 0; + uavDesc.Buffer.NumElements = numSlots; + uavDesc.Buffer.StructureByteStride = 0; + + m_pDevice->CreateUnorderedAccessView(m_BindlessFeedback.FeedbackBuffer, NULL, &uavDesc, + GetDebugManager()->GetCPUHandle(FEEDBACK_CLEAR_UAV)); + m_pDevice->CreateUnorderedAccessView(m_BindlessFeedback.FeedbackBuffer, NULL, &uavDesc, + GetDebugManager()->GetUAVClearHandle(FEEDBACK_CLEAR_UAV)); + + ID3D12GraphicsCommandList *list = m_pDevice->GetNewList(); + + UINT zeroes[4] = {0, 0, 0, 0}; + list->ClearUnorderedAccessViewUint(GetDebugManager()->GetGPUHandle(FEEDBACK_CLEAR_UAV), + GetDebugManager()->GetUAVClearHandle(FEEDBACK_CLEAR_UAV), + m_BindlessFeedback.FeedbackBuffer, zeroes, 0, NULL); + + list->Close(); + } + + ID3D12RootSignature *annotatedSig = NULL; + + { + ID3DBlob *root = m_pDevice->GetShaderCache()->MakeRootSig(modsig); + HRESULT hr = + m_pDevice->CreateRootSignature(0, root->GetBufferPointer(), root->GetBufferSize(), + __uuidof(ID3D12RootSignature), (void **)&annotatedSig); + + if(annotatedSig == NULL || FAILED(hr)) + { + RDCERR("Couldn't create feedback modified root signature: %s", ToStr(hr).c_str()); + return; + } + } + + ID3D12PipelineState *annotatedPipe = NULL; + + { + pipeDesc.pRootSignature = annotatedSig; + + HRESULT hr = m_pDevice->CreatePipeState(pipeDesc, &annotatedPipe); + if(annotatedPipe == NULL || FAILED(hr)) + { + SAFE_RELEASE(annotatedSig); + RDCERR("Couldn't create feedback modified pipeline: %s", ToStr(hr).c_str()); + return; + } + } + + D3D12RenderState prev = rs; + + rs.pipe = GetResID(annotatedPipe); + + if(result.compute) + { + rs.compute.rootsig = GetResID(annotatedSig); + size_t idx = modsig.Parameters.size() - 1; + rs.compute.sigelems.resize_for_index(idx); + rs.compute.sigelems[idx] = + D3D12RenderState::SignatureElement(eRootUAV, GetResID(m_BindlessFeedback.FeedbackBuffer), 0); + } + else + { + rs.graphics.rootsig = GetResID(annotatedSig); + size_t idx = modsig.Parameters.size() - 1; + rs.graphics.sigelems.resize_for_index(idx); + rs.graphics.sigelems[idx] = + D3D12RenderState::SignatureElement(eRootUAV, GetResID(m_BindlessFeedback.FeedbackBuffer), 0); + } + + m_pDevice->ReplayLog(0, eventId, eReplay_OnlyDraw); + + m_pDevice->ExecuteLists(); + m_pDevice->FlushLists(); + + SAFE_RELEASE(annotatedPipe); + SAFE_RELEASE(annotatedSig); + + rs = prev; + + bytebuf results; + GetDebugManager()->GetBufferData(m_BindlessFeedback.FeedbackBuffer, 0, 0, results); + + if(results.size() < numSlots * sizeof(uint32_t)) + { + RDCERR("Results buffer not the right size!"); + } + else + { + uint32_t *slotsData = (uint32_t *)results.data(); + + result.valid = true; + + // now we iterate over descriptor ranges and find which (of potentially multiple) registers each + // descriptor maps to and store the index if it's dynamically or statically used. We do this + // here so it only happens once instead of doing it when looking up the data. + + D3D12FeedbackKey curKey; + D3D12FeedbackBindIdentifier curIdentifier = {}; + // don't iterate the last signature element because that's ours! + for(size_t rootEl = 0; rootEl < modsig.Parameters.size() - 1; rootEl++) + { + curIdentifier.rootEl = rootEl; + + const D3D12RootSignatureParameter &p = modsig.Parameters[rootEl]; + + // only tables need feedback data, others all are treated as dynamically used + if(p.ParameterType == D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE) + { + for(size_t r = 0; r < p.ranges.size(); r++) + { + const D3D12_DESCRIPTOR_RANGE1 &range = p.ranges[r]; + + curIdentifier.rangeIndex = r; + + curKey.bind.bindset = range.RegisterSpace; + curKey.bind.bind = range.BaseShaderRegister; + + UINT num = range.NumDescriptors; + uint32_t visMask = 0; + // see which shader's binds we should look up for this range + switch(p.ShaderVisibility) + { + case D3D12_SHADER_VISIBILITY_ALL: visMask = result.compute ? 0x1 : 0xff; break; + case D3D12_SHADER_VISIBILITY_VERTEX: visMask = 1 << 0; break; + case D3D12_SHADER_VISIBILITY_HULL: visMask = 1 << 1; break; + case D3D12_SHADER_VISIBILITY_DOMAIN: visMask = 1 << 2; break; + case D3D12_SHADER_VISIBILITY_GEOMETRY: visMask = 1 << 3; break; + case D3D12_SHADER_VISIBILITY_PIXEL: visMask = 1 << 4; break; + default: RDCERR("Unexpected shader visibility %d", p.ShaderVisibility); return; + } + + // set the key type + if(range.RangeType == D3D12_DESCRIPTOR_RANGE_TYPE_SRV) + curKey.type = DXBCBytecode::TYPE_RESOURCE; + else if(range.RangeType == D3D12_DESCRIPTOR_RANGE_TYPE_UAV) + curKey.type = DXBCBytecode::TYPE_UNORDERED_ACCESS_VIEW; + + for(uint32_t st = 0; st < 5; st++) + { + if(visMask & (1 << st)) + { + // the feedback entries start here + auto slotIt = slots[st].lower_bound(curKey); + + curIdentifier.descIndex = 0; + + // iterate over the declared range. This could be unbounded, so we might exit + // another way + for(uint32_t i = 0; i < num; i++) + { + // stop when we've run out of recorded used slots + if(slotIt == slots[st].end()) + break; + + Bindpoint bind = slotIt->first.bind; + + // stop if the next used slot is in another space or is another type + if(bind.bindset > curKey.bind.bindset || slotIt->first.type != curKey.type) + break; + + // if the next bind is definitely outside this range, early out now instead of + // iterating fruitlessly + if((uint32_t)bind.bind > range.BaseShaderRegister + num) + break; + + int32_t lastBind = bind.bind + (int32_t)RDCCLAMP(bind.arraySize, 1U, maxDescriptors); + + // if this slot's array covers the current bind, check the result + if(bind.bind <= curKey.bind.bind && curKey.bind.bind < lastBind) + { + // if it's static used by having a fixed result declared, it's used + const bool staticUsed = slotIt->second.StaticUsed(); + + // otherwise check the feedback we got + const uint32_t baseSlot = slotIt->second.Slot(); + const uint32_t arrayIndex = curKey.bind.bind - bind.bind; + + if(staticUsed || slotsData[baseSlot + arrayIndex]) + result.used.push_back(curIdentifier); + } + + curKey.bind.bind++; + curIdentifier.descIndex++; + + // if we've passed this slot, move to the next one. Because we're iterating a + // contiguous range of binds the next slot will be enough for the next iteration + if(curKey.bind.bind >= lastBind) + slotIt++; + } + } + } + } + } + } + } + + // replay from the start as we may have corrupted state while fetching the above feedback. + m_pDevice->ReplayLog(0, eventId, eReplay_Full); +} + +void D3D12Replay::ClearFeedbackCache() +{ + m_BindlessFeedback.Usage.clear(); +} diff --git a/renderdoc/driver/d3d12/renderdoc_d3d12.vcxproj b/renderdoc/driver/d3d12/renderdoc_d3d12.vcxproj index 366f7920b..39c09c81b 100644 --- a/renderdoc/driver/d3d12/renderdoc_d3d12.vcxproj +++ b/renderdoc/driver/d3d12/renderdoc_d3d12.vcxproj @@ -140,6 +140,7 @@ + diff --git a/renderdoc/driver/d3d12/renderdoc_d3d12.vcxproj.filters b/renderdoc/driver/d3d12/renderdoc_d3d12.vcxproj.filters index ad5f6f928..742c80539 100644 --- a/renderdoc/driver/d3d12/renderdoc_d3d12.vcxproj.filters +++ b/renderdoc/driver/d3d12/renderdoc_d3d12.vcxproj.filters @@ -75,7 +75,9 @@ official - + + Hooks + @@ -204,5 +206,8 @@ Util + + Replay + \ No newline at end of file diff --git a/renderdoc/driver/shaders/dxbc/dxbc_bytecode_editor.h b/renderdoc/driver/shaders/dxbc/dxbc_bytecode_editor.h index 17822b2a4..509f82174 100644 --- a/renderdoc/driver/shaders/dxbc/dxbc_bytecode_editor.h +++ b/renderdoc/driver/shaders/dxbc/dxbc_bytecode_editor.h @@ -128,13 +128,13 @@ Operation oper(OpcodeType o, const rdcarray &operands); struct ResourceDecl { - TextureType type; - CompType compType; - uint32_t sampleCount; + TextureType type = TextureType::Buffer; + CompType compType = CompType::Float; + uint32_t sampleCount = 0; bool structured = false; - uint32_t stride; - bool hasCounter, globallyCoherant, rov; + uint32_t stride = 0; + bool hasCounter = false, globallyCoherant = false, rov = false; bool raw = false; }; diff --git a/renderdoc/driver/vulkan/vk_replay.cpp b/renderdoc/driver/vulkan/vk_replay.cpp index a9f7d34c6..13437b1f4 100644 --- a/renderdoc/driver/vulkan/vk_replay.cpp +++ b/renderdoc/driver/vulkan/vk_replay.cpp @@ -1623,7 +1623,7 @@ void VulkanReplay::SavePipelineState(uint32_t eventId) &state.graphics.descSets, &state.compute.descSets, }; - const DynamicShaderFeedback &usage = m_BindlessFeedback.Usage[eventId]; + const VKDynamicShaderFeedback &usage = m_BindlessFeedback.Usage[eventId]; m_VulkanPipelineState.shaderMessages = usage.messages; diff --git a/renderdoc/driver/vulkan/vk_replay.h b/renderdoc/driver/vulkan/vk_replay.h index 33d848792..337d940f4 100644 --- a/renderdoc/driver/vulkan/vk_replay.h +++ b/renderdoc/driver/vulkan/vk_replay.h @@ -209,7 +209,7 @@ struct VulkanPostVSData } }; -struct DynamicShaderFeedback +struct VKDynamicShaderFeedback { bool compute = false, valid = false; rdcarray used; @@ -744,7 +744,7 @@ private: GPUBuffer FeedbackBuffer; - std::map Usage; + std::map Usage; } m_BindlessFeedback; ShaderDebugData m_ShaderDebugData; diff --git a/renderdoc/driver/vulkan/vk_shader_feedback.cpp b/renderdoc/driver/vulkan/vk_shader_feedback.cpp index 4bbab5882..778a732b1 100644 --- a/renderdoc/driver/vulkan/vk_shader_feedback.cpp +++ b/renderdoc/driver/vulkan/vk_shader_feedback.cpp @@ -1310,7 +1310,7 @@ void VulkanReplay::FetchShaderFeedback(uint32_t eventId) // create it here so we won't re-run any code if the event is re-selected. We'll mark it as valid // if it actually has any data in it later. - DynamicShaderFeedback &result = m_BindlessFeedback.Usage[eventId]; + VKDynamicShaderFeedback &result = m_BindlessFeedback.Usage[eventId]; bool useBufferAddress = (m_pDriver->GetExtensions(NULL).ext_KHR_buffer_device_address || m_pDriver->GetExtensions(NULL).ext_EXT_buffer_device_address) && diff --git a/renderdoc/replay/renderdoc_serialise.inl b/renderdoc/replay/renderdoc_serialise.inl index 82131bdd1..cef612de3 100644 --- a/renderdoc/replay/renderdoc_serialise.inl +++ b/renderdoc/replay/renderdoc_serialise.inl @@ -441,7 +441,7 @@ void DoSerialise(SerialiserType &ser, TextureSwizzle4 &el) SERIALISE_MEMBER(blue); SERIALISE_MEMBER(alpha); - SIZE_CHECK(16); + SIZE_CHECK(4); } template @@ -1374,11 +1374,14 @@ void DoSerialise(SerialiserType &ser, D3D12Pipe::RootSignatureRange &el) SERIALISE_MEMBER(type); SERIALISE_MEMBER(visibility); SERIALISE_MEMBER(registerSpace); + SERIALISE_MEMBER(dynamicallyUsedCount); + SERIALISE_MEMBER(firstUsedIndex); + SERIALISE_MEMBER(lastUsedIndex); SERIALISE_MEMBER(constantBuffers); SERIALISE_MEMBER(samplers); SERIALISE_MEMBER(views); - SIZE_CHECK(96); + SIZE_CHECK(104); } template @@ -1391,6 +1394,7 @@ void DoSerialise(SerialiserType &ser, D3D12Pipe::View &el) SERIALISE_MEMBER(viewFormat); SERIALISE_MEMBER(swizzle); + SERIALISE_MEMBER(dynamicallyUsed); SERIALISE_MEMBER(bufferFlags); SERIALISE_MEMBER(bufferStructCount); SERIALISE_MEMBER(elementByteSize); @@ -1407,7 +1411,7 @@ void DoSerialise(SerialiserType &ser, D3D12Pipe::View &el) SERIALISE_MEMBER(minLODClamp); - SIZE_CHECK(112); + SIZE_CHECK(96); } template @@ -1547,7 +1551,7 @@ void DoSerialise(SerialiserType &ser, D3D12Pipe::OM &el) SERIALISE_MEMBER(multiSampleCount); SERIALISE_MEMBER(multiSampleQuality); - SIZE_CHECK(280); + SIZE_CHECK(264); } template @@ -1591,7 +1595,7 @@ void DoSerialise(SerialiserType &ser, D3D12Pipe::State &el) SERIALISE_MEMBER(resourceStates); - SIZE_CHECK(1432); + SIZE_CHECK(1416); } #pragma endregion D3D12 pipeline state @@ -1678,7 +1682,7 @@ void DoSerialise(SerialiserType &ser, GLPipe::Texture &el) SERIALISE_MEMBER(depthReadChannel); SERIALISE_MEMBER(completeStatus); - SIZE_CHECK(64); + SIZE_CHECK(56); } template @@ -1811,7 +1815,7 @@ void DoSerialise(SerialiserType &ser, GLPipe::Attachment &el) SERIALISE_MEMBER(mipLevel); SERIALISE_MEMBER(swizzle); - SIZE_CHECK(40); + SIZE_CHECK(24); } template @@ -1824,7 +1828,7 @@ void DoSerialise(SerialiserType &ser, GLPipe::FBO &el) SERIALISE_MEMBER(drawBuffers); SERIALISE_MEMBER(readBuffer); - SIZE_CHECK(144); + SIZE_CHECK(112); } template @@ -1845,7 +1849,7 @@ void DoSerialise(SerialiserType &ser, GLPipe::FrameBuffer &el) SERIALISE_MEMBER(readFBO); SERIALISE_MEMBER(blendState); - SIZE_CHECK(336); + SIZE_CHECK(272); } template @@ -1894,7 +1898,7 @@ void DoSerialise(SerialiserType &ser, GLPipe::State &el) SERIALISE_MEMBER(hints); - SIZE_CHECK(2024); + SIZE_CHECK(1960); } #pragma endregion OpenGL pipeline state @@ -1942,7 +1946,7 @@ void DoSerialise(SerialiserType &ser, VKPipe::BindingElement &el) SERIALISE_MEMBER(chromaFilter); SERIALISE_MEMBER(forceExplicitReconstruction); - SIZE_CHECK(200); + SIZE_CHECK(184); }; template @@ -2223,7 +2227,7 @@ void DoSerialise(SerialiserType &ser, VKPipe::Attachment &el) SERIALISE_MEMBER(numMips); SERIALISE_MEMBER(numSlices); - SIZE_CHECK(56); + SIZE_CHECK(48); } template