From 35437065a1f56fc14034bd11458fa59335670d59 Mon Sep 17 00:00:00 2001 From: Jake Turner Date: Tue, 14 Apr 2026 13:21:48 +0100 Subject: [PATCH] DXIL Debugger support for direct heap access Constant Buffers --- renderdoc/driver/d3d12/d3d12_dxil_debug.cpp | 79 ++++++++++++++ renderdoc/driver/d3d12/d3d12_dxil_debug.h | 7 ++ renderdoc/driver/shaders/dxil/dxil_debug.cpp | 106 ++++++++++++------- renderdoc/driver/shaders/dxil/dxil_debug.h | 4 + 4 files changed, 160 insertions(+), 36 deletions(-) diff --git a/renderdoc/driver/d3d12/d3d12_dxil_debug.cpp b/renderdoc/driver/d3d12/d3d12_dxil_debug.cpp index 8e8e1d422..45709b406 100644 --- a/renderdoc/driver/d3d12/d3d12_dxil_debug.cpp +++ b/renderdoc/driver/d3d12/d3d12_dxil_debug.cpp @@ -1297,6 +1297,85 @@ SRVInfo D3D12APIWrapper::FetchSRV(const BindingSlot &slot) return srvData; } +// Called from any thread +bool D3D12APIWrapper::IsCBVCached(const BindingSlot &slot) const +{ + SCOPED_READLOCK(m_CBVsLock); + return m_CBVBuffers.find(slot) != m_CBVBuffers.end(); +} + +// Called from any thread +void D3D12APIWrapper::GetCBV(const BindingSlot &slot) +{ + { + SCOPED_READLOCK(m_CBVsLock); + auto it = m_CBVBuffers.find(slot); + if(it != m_CBVBuffers.end()) + return; + } + + FetchCBV(slot); +} + +// Must be called from the replay manager thread (the debugger thread) +void D3D12APIWrapper::FetchCBV(const BindingSlot &slot) +{ + CHECK_DEVICE_THREAD(); + + const HeapDescriptorType heapType = slot.heapType; + const uint32_t descriptorIndex = slot.descriptorIndex; + const D3D12Descriptor resDescriptor = + D3D12ShaderDebug::FindDescriptor(m_Device, heapType, descriptorIndex); + + bytebuf data; + D3D12ResourceManager *rm = m_Device->GetResourceManager(); + ResourceId cbvId = WrappedID3D12Resource::GetResIDFromAddr(resDescriptor.GetCBV().BufferLocation); + ID3D12Resource *pResource = rm->GetResAs(cbvId); + if(pResource) + m_Device->GetDebugManager()->GetBufferData(pResource, 0, 0, data); + + { + SCOPED_WRITELOCK(m_CBVsLock); + auto bufferIt = m_CBVBuffers.insert(std::make_pair(slot, data)); + RDCASSERT(bufferIt.second); + } +} + +// Called from any thread +// Resource must be cached +ShaderValue D3D12APIWrapper::CBVLoad(const BindingSlot &slot, uint32_t regIndex) const +{ + ShaderValue result; + result.u32v[0] = 0; + result.u32v[1] = 0; + result.u32v[2] = 0; + result.u32v[3] = 0; + + SCOPED_READLOCK(m_CBVsLock); + auto it = m_CBVBuffers.find(slot); + if(it == m_CBVBuffers.end()) + { + RDCERR("CBV Load slot %u space %u desc Index %u no cached data", slot.shaderRegister, + slot.registerSpace, slot.descriptorIndex); + return result; + } + const bytebuf &cbufferData = it->second; + const uint32_t bufferSize = (uint32_t)cbufferData.size(); + const uint32_t maxIndex = AlignUp16(bufferSize) / 16; + RDCASSERTMSG("Out of bounds cbuffer load", regIndex < maxIndex, regIndex, maxIndex); + if(regIndex < maxIndex) + { + const uint32_t dataOffset = regIndex * 16; + const uint32_t byteWidth = 4; + const byte *base = cbufferData.data() + dataOffset; + const uint32_t *data = (const uint32_t *)base; + const uint32_t numComps = RDCMIN(4U, (bufferSize - dataOffset) / byteWidth); + for(uint32_t c = 0; c < numComps; c++) + result.u32v[c] = data[c]; + } + return result; +} + // Called from any thread // Resource must be cached ShaderValue D3D12APIWrapper::TypedUAVLoad(const BindingSlot &slot, const DXILDebug::ViewFmt &fmt, diff --git a/renderdoc/driver/d3d12/d3d12_dxil_debug.h b/renderdoc/driver/d3d12/d3d12_dxil_debug.h index e3602189a..773f2481f 100644 --- a/renderdoc/driver/d3d12/d3d12_dxil_debug.h +++ b/renderdoc/driver/d3d12/d3d12_dxil_debug.h @@ -43,6 +43,7 @@ public: void FetchConstantBufferData(const D3D12RenderState::RootSignature &rootsig); + ShaderValue CBVLoad(const BindingSlot &slot, uint32_t dataOffset) const override; ShaderValue TypedUAVLoad(const BindingSlot &slot, const DXILDebug::ViewFmt &fmt, uint64_t dataOffset) const override; ShaderValue TypedSRVLoad(const BindingSlot &slot, const DXILDebug::ViewFmt &fmt, @@ -51,6 +52,7 @@ public: const ShaderValue &value) override; bool TypedSRVStore(const BindingSlot &slot, const DXILDebug::ViewFmt &fmt, uint64_t dataOffset, const ShaderValue &value) override; + void GetCBV(const BindingSlot &slot) override; UAVInfo GetUAV(const BindingSlot &slot) override; SRVInfo GetSRV(const BindingSlot &slot) override; @@ -75,6 +77,7 @@ public: ShaderDirectAccess GetShaderDirectAccess(DescriptorType type, const DXDebug::BindingSlot &slot) override; + bool IsCBVCached(const DXDebug::BindingSlot &slot) const override; bool IsSRVCached(const DXDebug::BindingSlot &slot) const override; bool IsUAVCached(const DXDebug::BindingSlot &slot) const override; bool IsResourceInfoCached(const DXDebug::BindingSlot &slot, uint32_t mipLevel) override; @@ -138,6 +141,8 @@ private: SRVInfo FetchSRV(const BindingSlot &slot); SRVInfo FetchSRV(const D3D12Descriptor *resDescriptor, const BindingSlot &slot); + void FetchCBV(const BindingSlot &slot); + UAVInfo FetchUAV(const BindingSlot &slot); UAVInfo FetchUAV(const D3D12Descriptor *resDescriptor, const BindingSlot &slot); @@ -177,6 +182,8 @@ private: } }; + mutable Threading::RWLock m_CBVsLock; + std::map m_CBVBuffers; mutable Threading::RWLock m_UAVsLock; std::map m_UAVInfos; std::map m_UAVBuffers; diff --git a/renderdoc/driver/shaders/dxil/dxil_debug.cpp b/renderdoc/driver/shaders/dxil/dxil_debug.cpp index f446f3445..f0728c5e8 100644 --- a/renderdoc/driver/shaders/dxil/dxil_debug.cpp +++ b/renderdoc/driver/shaders/dxil/dxil_debug.cpp @@ -2351,7 +2351,6 @@ bool ThreadState::ExecuteInstruction(const rdcarray &workgroup) SetStepNeedsDeviceThread(); break; } - RDCASSERT(m_DirectHeapAccessBindings.count(resultId) == 0); m_DirectHeapAccessBindings[resultId] = resRefInfo; // Default to unannotated handle @@ -2377,7 +2376,6 @@ bool ThreadState::ExecuteInstruction(const rdcarray &workgroup) // Update m_DirectHeapAccessBindings for the annotated handle // to use the data from the source resource RDCASSERT(m_DirectHeapAccessBindings.count(baseResourceId) > 0); - RDCASSERT(m_DirectHeapAccessBindings.count(resultId) == 0); m_DirectHeapAccessBindings[resultId] = m_DirectHeapAccessBindings.at(baseResourceId); } else @@ -2431,16 +2429,19 @@ bool ThreadState::ExecuteInstruction(const rdcarray &workgroup) } else if(resKind == ResourceKind::CBuffer) { - // Create the cbuffer handle reference for the annotated handle - auto it = m_ConstantBlockHandles.find(baseResourceId); - if(it != m_ConstantBlockHandles.end()) + if(!resource.IsDirectAccess()) { - m_ConstantBlockHandles[resultId] = it->second; - } - else - { - RDCERR("Annotated handle resName:%s %s has no cbuffer handle reference %u", - resName.c_str(), baseResource.c_str(), baseResourceId); + // Create the cbuffer handle reference for the annotated handle + auto it = m_ConstantBlockHandles.find(baseResourceId); + if(it != m_ConstantBlockHandles.end()) + { + m_ConstantBlockHandles[resultId] = it->second; + } + else + { + RDCERR("Annotated handle resName:%s %s has no cbuffer handle reference %u", + resName.c_str(), baseResource.c_str(), baseResourceId); + } } } // Store the annotate properties for the result @@ -2648,42 +2649,63 @@ bool ThreadState::ExecuteInstruction(const rdcarray &workgroup) uint32_t regIndex = arg.value.u32v[0]; RDCASSERT(m_Live[handleId]); - const ShaderVariable &handleVar = m_Variables[handleId]; + ShaderVariable handleVar; + + bool annotatedHandle = false; + ResourceReferenceInfo resRefInfo = GetResource(handleId, annotatedHandle, handleVar); + if(!resRefInfo.Valid()) + { + RDCERR("Invalid cbuffer resource %u", handleId); + break; + } result.value.u32v[0] = 0; result.value.u32v[1] = 0; result.value.u32v[2] = 0; result.value.u32v[3] = 0; - auto constantBlockRefIt = m_ConstantBlockHandles.find(handleId); - if(constantBlockRefIt != m_ConstantBlockHandles.end()) + if(handleVar.IsDirectAccess()) { - const ConstantBlockReference &constantBlockRef = constantBlockRefIt->second; - auto it = m_GlobalState.constantBlocksDatas.find(constantBlockRef); - if(it != m_GlobalState.constantBlocksDatas.end()) + const BindingSlot &slot = resRefInfo.binding; + if(m_Debugger.LoadCBVData(slot, regIndex, result.value) == DeviceOpResult::NeedsDevice) { - const bytebuf &cbufferData = it->second; - const uint32_t bufferSize = (uint32_t)cbufferData.size(); - const uint32_t maxIndex = AlignUp16(bufferSize) / 16; - RDCASSERTMSG("Out of bounds cbuffer load", regIndex < maxIndex, regIndex, maxIndex); - if(regIndex < maxIndex) - { - const uint32_t dataOffset = regIndex * 16; - const uint32_t byteWidth = 4; - const byte *base = cbufferData.data() + dataOffset; - const uint32_t *data = (const uint32_t *)base; - const uint32_t numComps = RDCMIN(4U, (bufferSize - dataOffset) / byteWidth); - for(uint32_t c = 0; c < numComps; c++) - result.value.u32v[c] = data[c]; - } - } - else - { - RDCERR("Failed to find data for constant block data for %s", handleVar.name.c_str()); + SetStepNeedsDeviceThread(); + break; } } else { - RDCERR("Failed to find data for cbuffer %s", handleVar.name.c_str()); + auto constantBlockRefIt = m_ConstantBlockHandles.find(handleId); + if(constantBlockRefIt != m_ConstantBlockHandles.end()) + { + const ConstantBlockReference &constantBlockRef = constantBlockRefIt->second; + auto it = m_GlobalState.constantBlocksDatas.find(constantBlockRef); + if(it != m_GlobalState.constantBlocksDatas.end()) + { + const bytebuf &cbufferData = it->second; + const uint32_t bufferSize = (uint32_t)cbufferData.size(); + const uint32_t maxIndex = AlignUp16(bufferSize) / 16; + RDCASSERTMSG("Out of bounds cbuffer load", regIndex < maxIndex, regIndex, maxIndex); + if(regIndex < maxIndex) + { + const uint32_t dataOffset = regIndex * 16; + const uint32_t byteWidth = 4; + const byte *base = cbufferData.data() + dataOffset; + const uint32_t *data = (const uint32_t *)base; + const uint32_t numComps = RDCMIN(4U, (bufferSize - dataOffset) / byteWidth); + for(uint32_t c = 0; c < numComps; c++) + result.value.u32v[c] = data[c]; + } + } + else + { + RDCERR("Failed to find data for constant block data for %s", + handleVar.name.c_str()); + } + } + else + { + RDCERR("Failed to find data for cbuffer %s", handleVar.name.c_str()); + } } // DXIL will create a vector of a single type with total size of 16-bytes @@ -10452,6 +10474,18 @@ bool Debugger::TypedResourceStore(DXIL::ResourceClass resClass, const BindingSlo return false; } +// Called from any thread +DeviceOpResult Debugger::LoadCBVData(const BindingSlot &slot, uint32_t regIndex, ShaderValue &value) +{ + if(!IsDeviceThread() && !m_ApiWrapper->IsCBVCached(slot)) + return DeviceOpResult::NeedsDevice; + + m_ApiWrapper->GetCBV(slot); + + value = m_ApiWrapper->CBVLoad(slot, regIndex); + return DeviceOpResult::Succeeded; +} + // Called from any thread DeviceOpResult Debugger::GetUAV(const BindingSlot &slot, UAVInfo &uavInfo) const { diff --git a/renderdoc/driver/shaders/dxil/dxil_debug.h b/renderdoc/driver/shaders/dxil/dxil_debug.h index b1294d76c..aa28930fc 100644 --- a/renderdoc/driver/shaders/dxil/dxil_debug.h +++ b/renderdoc/driver/shaders/dxil/dxil_debug.h @@ -299,6 +299,7 @@ class DebugAPIWrapper public: virtual ~DebugAPIWrapper() {} + virtual ShaderValue CBVLoad(const BindingSlot &slot, uint32_t regIndex) const = 0; virtual ShaderValue TypedUAVLoad(const BindingSlot &slot, const DXILDebug::ViewFmt &fmt, uint64_t dataOffset) const = 0; virtual ShaderValue TypedSRVLoad(const BindingSlot &slot, const DXILDebug::ViewFmt &fmt, @@ -309,6 +310,7 @@ public: uint64_t dataOffset, const ShaderValue &value) = 0; // These will fetch the data on demand. + virtual void GetCBV(const BindingSlot &slot) = 0; virtual UAVInfo GetUAV(const BindingSlot &slot) = 0; virtual SRVInfo GetSRV(const BindingSlot &slot) = 0; @@ -332,6 +334,7 @@ public: virtual ShaderDirectAccess GetShaderDirectAccess(DescriptorType type, const DXDebug::BindingSlot &slot) = 0; + virtual bool IsCBVCached(const DXDebug::BindingSlot &slot) const = 0; virtual bool IsSRVCached(const DXDebug::BindingSlot &slot) const = 0; virtual bool IsUAVCached(const DXDebug::BindingSlot &slot) const = 0; virtual bool IsResourceInfoCached(const DXDebug::BindingSlot &slot, uint32_t mipLevel) = 0; @@ -932,6 +935,7 @@ public: bool TypedResourceStore(DXIL::ResourceClass resClass, const BindingSlot &slot, const DXILDebug::ViewFmt &fmt, uint64_t dataOffset, ShaderValue &value); + DeviceOpResult LoadCBVData(const BindingSlot &slot, uint32_t regIndex, ShaderValue &value); DeviceOpResult GetUAV(const BindingSlot &slot, UAVInfo &uavInfo) const; DeviceOpResult GetSRV(const BindingSlot &slot, SRVInfo &srvInfo) const;