From 5e3bfb29b268b848703432736f8f90807e80e960 Mon Sep 17 00:00:00 2001 From: baldurk Date: Wed, 19 Feb 2025 17:48:06 +0000 Subject: [PATCH] Simulate and propagate gsm data in DXBC --- renderdoc/driver/d3d11/d3d11_shaderdebug.cpp | 13 +- renderdoc/driver/d3d12/d3d12_shaderdebug.cpp | 14 +- .../driver/shaders/dxbc/dxbc_bytecode.cpp | 47 +++ renderdoc/driver/shaders/dxbc/dxbc_bytecode.h | 2 + .../driver/shaders/dxbc/dxbc_bytecode_ops.cpp | 8 + .../driver/shaders/dxbc/dxbc_container.h | 6 +- renderdoc/driver/shaders/dxbc/dxbc_debug.cpp | 308 ++++++++++++++---- renderdoc/driver/shaders/dxbc/dxbc_debug.h | 2 + renderdoc/driver/shaders/dxbc/dxbc_spdb.cpp | 173 ++++++++-- renderdoc/driver/shaders/dxbc/dxbc_spdb.h | 8 + 10 files changed, 499 insertions(+), 82 deletions(-) diff --git a/renderdoc/driver/d3d11/d3d11_shaderdebug.cpp b/renderdoc/driver/d3d11/d3d11_shaderdebug.cpp index 3606c8955..22a2da556 100644 --- a/renderdoc/driver/d3d11/d3d11_shaderdebug.cpp +++ b/renderdoc/driver/d3d11/d3d11_shaderdebug.cpp @@ -23,6 +23,7 @@ * THE SOFTWARE. ******************************************************************************/ +#include "core/settings.h" #include "data/resource.h" #include "driver/shaders/dxbc/dx_debug.h" #include "driver/shaders/dxbc/dxbc_bytecode.h" @@ -40,6 +41,8 @@ #include "data/hlsl/hlsl_cbuffers.h" +RDOC_EXTERN_CONFIG(bool, D3D_Hack_EnableGroups); + struct DebugHit { uint32_t numHits; @@ -2613,9 +2616,17 @@ ShaderDebugTrace *D3D11Replay::DebugThread(uint32_t eventId, D3D11RenderState *rs = m_pImmediateContext->GetCurrentPipelineState(); + uint32_t activeIndex = 0; + if(dxbc->GetThreadScope() == DXBC::ThreadScope::Workgroup) + { + if(D3D_Hack_EnableGroups()) + activeIndex = threadid[0] + threadid[1] * refl.dispatchThreadsDimension[0] + + threadid[2] * refl.dispatchThreadsDimension[0] * refl.dispatchThreadsDimension[1]; + } + InterpretDebugger *interpreter = new InterpretDebugger; interpreter->eventId = eventId; - ShaderDebugTrace *ret = interpreter->BeginDebug(dxbc, refl, 0); + ShaderDebugTrace *ret = interpreter->BeginDebug(dxbc, refl, activeIndex); GlobalState &global = interpreter->global; ThreadState &state = interpreter->activeLane(); diff --git a/renderdoc/driver/d3d12/d3d12_shaderdebug.cpp b/renderdoc/driver/d3d12/d3d12_shaderdebug.cpp index 8c80b724c..0db87da62 100644 --- a/renderdoc/driver/d3d12/d3d12_shaderdebug.cpp +++ b/renderdoc/driver/d3d12/d3d12_shaderdebug.cpp @@ -23,6 +23,7 @@ ******************************************************************************/ #include "d3d12_shaderdebug.h" +#include "core/settings.h" #include "driver/dx/official/d3dcompiler.h" #include "driver/dxgi/dxgi_common.h" #include "driver/shaders/dxbc/dxbc_debug.h" @@ -39,6 +40,8 @@ #include "data/hlsl/hlsl_cbuffers.h" +RDOC_EXTERN_CONFIG(bool, D3D_Hack_EnableGroups); + using namespace DXBCBytecode; struct DebugHit @@ -3339,9 +3342,18 @@ ShaderDebugTrace *D3D12Replay::DebugThread(uint32_t eventId, ShaderDebugTrace *ret = NULL; if(dxbc->GetDXBCByteCode()) { + uint32_t activeIndex = 0; + if(dxbc->GetThreadScope() == DXBC::ThreadScope::Workgroup) + { + if(D3D_Hack_EnableGroups()) + activeIndex = + threadid[0] + threadid[1] * refl.dispatchThreadsDimension[0] + + threadid[2] * refl.dispatchThreadsDimension[0] * refl.dispatchThreadsDimension[1]; + } + InterpretDebugger *interpreter = new InterpretDebugger; interpreter->eventId = eventId; - ret = interpreter->BeginDebug(dxbc, refl, 0); + ret = interpreter->BeginDebug(dxbc, refl, activeIndex); GlobalState &global = interpreter->global; ThreadState &state = interpreter->activeLane(); diff --git a/renderdoc/driver/shaders/dxbc/dxbc_bytecode.cpp b/renderdoc/driver/shaders/dxbc/dxbc_bytecode.cpp index 4fe081e01..77a2d03ab 100644 --- a/renderdoc/driver/shaders/dxbc/dxbc_bytecode.cpp +++ b/renderdoc/driver/shaders/dxbc/dxbc_bytecode.cpp @@ -664,6 +664,46 @@ void Program::SetupRegisterFile(rdcarray ®isters) const registers.push_back(makeReg(rdcstr())); if(m_OutputCoverage) registers.push_back(makeReg(rdcstr())); + + for(size_t i = 0; i < m_GroupsharedTempSizes.size(); i++) + { + if(m_GroupsharedTempSizes[i].first == 0) + continue; + + registers.push_back(makeReg(GetRegisterName(TYPE_THREAD_GROUP_SHARED_MEMORY, (uint32_t)i))); + registers.back().members.resize(m_GroupsharedTempSizes[i].second); + // nice case, groupshared is raw or structured with stride less than a register, we can treat + // it as a simple array + if(m_GroupsharedTempSizes[i].first <= 16) + { + for(uint32_t t = 0; t < m_GroupsharedTempSizes[i].second; t++) + { + registers.back().members[t] = makeReg(StringFormat::Fmt("[%u]", t)); + + // truncate columns if it's float[]/int[] or float2[] or something + registers.back().members[t].columns = uint8_t(m_GroupsharedTempSizes[i].first / 4); + } + } + else + { + // unfortunate case. With a larger stride we need to make every array element large enough for + // the 'struct'. We insert fake members since we can't tell what is what and do it component + // wise since that's hopefully slightly better than nothing. It also makes debug-info mapping easier + for(uint32_t t = 0; t < m_GroupsharedTempSizes[i].second; t++) + { + registers.back().members[t] = makeReg(StringFormat::Fmt("[%u]", t)); + + registers.back().members[t].members.resize(AlignUp4(m_GroupsharedTempSizes[i].first) / 4); + + uint32_t idx = 0; + for(ShaderVariable &m : registers.back().members[t].members) + { + m = makeReg(StringFormat::Fmt("_%u", idx++)); + m.columns = 1; + } + } + } + } } const Declaration *Program::FindDeclaration(OperandType declType, uint32_t identifier) const @@ -718,6 +758,11 @@ uint32_t Program::GetRegisterIndex(OperandType type, uint32_t index) const return m_NumTemps + (uint32_t)m_IndexTempSizes.size() + m_NumOutputs + (m_OutputDepth ? 1 : 0) + (m_OutputStencil ? 1 : 0); } + else if(type == TYPE_THREAD_GROUP_SHARED_MEMORY) + { + return m_NumTemps + (uint32_t)m_IndexTempSizes.size() + m_NumOutputs + (m_OutputDepth ? 1 : 0) + + (m_OutputStencil ? 1 : 0) + (m_OutputCoverage ? 1 : 0); + } RDCERR("Unexpected type for register index: %s", ToStr(type).c_str()); @@ -736,6 +781,8 @@ rdcstr Program::GetRegisterName(OperandType oper, uint32_t index) const return StringFormat::Fmt("%s%u", IsShaderModel51() ? "CB" : "cb", index); else if(oper == TYPE_OUTPUT) return StringFormat::Fmt("o%u", index); + else if(oper == TYPE_THREAD_GROUP_SHARED_MEMORY) + return StringFormat::Fmt("g%u", index); else if(oper == TYPE_OUTPUT_DEPTH) return "oDepth"; else if(oper == TYPE_OUTPUT_DEPTH_LESS_EQUAL) diff --git a/renderdoc/driver/shaders/dxbc/dxbc_bytecode.h b/renderdoc/driver/shaders/dxbc/dxbc_bytecode.h index 6dca65bc9..44d226bbb 100644 --- a/renderdoc/driver/shaders/dxbc/dxbc_bytecode.h +++ b/renderdoc/driver/shaders/dxbc/dxbc_bytecode.h @@ -1174,6 +1174,8 @@ protected: uint32_t m_NumTemps = 0; rdcarray m_IndexTempSizes; + // each one is declared with byteStride, elementCount + rdcarray> m_GroupsharedTempSizes; // most regular outputs, including system value outputs like primitive ID are given a register // number diff --git a/renderdoc/driver/shaders/dxbc/dxbc_bytecode_ops.cpp b/renderdoc/driver/shaders/dxbc/dxbc_bytecode_ops.cpp index 8681acd12..c1f2170b1 100644 --- a/renderdoc/driver/shaders/dxbc/dxbc_bytecode_ops.cpp +++ b/renderdoc/driver/shaders/dxbc/dxbc_bytecode_ops.cpp @@ -2196,6 +2196,10 @@ bool Program::DecodeDecl(uint32_t *&tokenStream, Declaration &retDecl, bool frie retDecl.str += retDecl.operand.toString(m_Reflection, flags); retDecl.str += StringFormat::Fmt(", %u", retDecl.tgsmCount); + + uint32_t reg = (uint32_t)retDecl.operand.indices[0].index; + m_GroupsharedTempSizes.resize_for_index(reg); + m_GroupsharedTempSizes[reg] = {4, AlignUp4(retDecl.tgsmCount) / 4}; } else if(op == OPCODE_DCL_THREAD_GROUP_SHARED_MEMORY_STRUCTURED) { @@ -2213,6 +2217,10 @@ bool Program::DecodeDecl(uint32_t *&tokenStream, Declaration &retDecl, bool frie retDecl.str += retDecl.operand.toString(m_Reflection, flags); retDecl.str += StringFormat::Fmt(", %u, %u", retDecl.tsgm_structured.stride, retDecl.tsgm_structured.count); + + uint32_t reg = (uint32_t)retDecl.operand.indices[0].index; + m_GroupsharedTempSizes.resize_for_index(reg); + m_GroupsharedTempSizes[reg] = {retDecl.tsgm_structured.stride, retDecl.tsgm_structured.count}; } else if(op == OPCODE_DCL_INPUT_CONTROL_POINT_COUNT || op == OPCODE_DCL_OUTPUT_CONTROL_POINT_COUNT) { diff --git a/renderdoc/driver/shaders/dxbc/dxbc_container.h b/renderdoc/driver/shaders/dxbc/dxbc_container.h index 6d1163c85..378704ae0 100644 --- a/renderdoc/driver/shaders/dxbc/dxbc_container.h +++ b/renderdoc/driver/shaders/dxbc/dxbc_container.h @@ -207,11 +207,7 @@ public: const IDebugInfo *GetDebugInfo() const { return m_DebugInfo; } const Reflection *GetReflection() const { return m_Reflection; } D3D_PRIMITIVE_TOPOLOGY GetOutputTopology(); - ThreadScope GetThreadScope() - { - GetDisassembly(false); - return m_Threadscope; - } + ThreadScope GetThreadScope() const { return m_Threadscope; } CBufferVariableType GetRayPayload(const ShaderEntryPoint &entry) { diff --git a/renderdoc/driver/shaders/dxbc/dxbc_debug.cpp b/renderdoc/driver/shaders/dxbc/dxbc_debug.cpp index d459aa8af..5381fa8c6 100644 --- a/renderdoc/driver/shaders/dxbc/dxbc_debug.cpp +++ b/renderdoc/driver/shaders/dxbc/dxbc_debug.cpp @@ -26,12 +26,16 @@ #include "dxbc_debug.h" #include #include "common/formatting.h" +#include "core/settings.h" #include "driver/dxgi/dxgi_common.h" #include "maths/formatpacking.h" #include "replay/replay_driver.h" #include "dxbc_bytecode.h" #include "dxbc_container.h" +RDOC_DEBUG_CONFIG(bool, D3D_Hack_EnableGroups, false, + "Work in progress allow shaders to be debugged with workgroup requirements."); + using namespace DXBCBytecode; using namespace DXDebug; @@ -1243,6 +1247,53 @@ void ThreadState::SetDst(ShaderDebugState *state, const Operand &dstoper, const } } +void ThreadState::SetGroupsharedDst(ShaderDebugState *state, uint32_t gsmIndex, + const uint32_t byteOffset, ShaderVariable &val) +{ + const uint32_t gsmStride = global.groupshared[gsmIndex].bytestride; + + const uint32_t regIndex = byteOffset / gsmStride; + const uint32_t component = AlignUp4(byteOffset % gsmStride) / 4; + + ShaderVariable *v = NULL; + + uint32_t idx = program->GetRegisterIndex(TYPE_THREAD_GROUP_SHARED_MEMORY, gsmIndex); + + if(idx < variables.size()) + v = &variables[idx]; + + if(!v) + { + RDCERR("Couldn't find groupshared register %u", gsmIndex); + } + else + { + ShaderVariable *changeVar = v; + + v = &v->members[regIndex]; + + ShaderVariableChange change = {*changeVar}; + + if(gsmStride <= 16) + { + // if the stride is less than a float4, the groupshared storage is a simple array of N + // float4 registers so we can just assign + for(uint32_t i = 0; i < val.columns; i++) + v->value.u32v[component + i] = val.value.u32v[i]; + } + else + { + // otherwise each entry in the groupshared storage array is a series of N + // component-sized registers so unroll that here and assign to the first component + for(uint32_t i = 0; i < val.columns; i++) + v->members[component + i].members[0].value.u32v[0] = val.value.u32v[i]; + } + + change.after = *changeVar; + state->changes.push_back(change); + } +} + ShaderVariable ThreadState::DDX(bool fine, const rdcarray &quad, const DXBCBytecode::Operand &oper, const DXBCBytecode::Operation &op) const @@ -1529,6 +1580,7 @@ ShaderVariable ThreadState::GetSrc(const Operand &oper, const Operation &op, boo numthreads[0] = decl.groupSize[0]; numthreads[1] = decl.groupSize[1]; numthreads[2] = decl.groupSize[2]; + break; } } @@ -2549,13 +2601,67 @@ void ThreadState::StepNext(ShaderDebugState *state, DebugAPIWrapper *apiWrapper, ///////////////////////////////////////////////////////////////////////////////////////////////////// // Misc + case OPCODE_SYNC: + { + // fully refresh all groupshared registers from backing store + if(state) + { + for(uint32_t gsmIndex = 0; gsmIndex < global.groupshared.size(); gsmIndex++) + { + const uint32_t gsmStride = global.groupshared[gsmIndex].bytestride; + + uint32_t idx = program->GetRegisterIndex(TYPE_THREAD_GROUP_SHARED_MEMORY, gsmIndex); + + ShaderVariable *v = NULL; + if(idx < variables.size()) + v = &variables[idx]; + + if(!v) + { + RDCERR("Couldn't find groupshared register %u", gsmIndex); + continue; + } + + ShaderVariable *changeVar = v; + + ShaderVariableChange change = {*changeVar}; + + byte *data = global.groupshared[gsmIndex].data.data(); + for(uint32_t i = 0; i < global.groupshared[gsmIndex].count; i++) + { + if(gsmStride <= 16) + { + // if the stride is less than a float4, the groupshared storage is a simple array of N + // float4 registers so we can just memcpy + memcpy(v->members[i].value.u32v.data(), data, gsmStride); + data += gsmStride; + } + else + { + // otherwise each entry in the groupshared storage array is a series of N + // component-sized registers so unroll that here and copy into each's first component + for(uint32_t c = 0; c < gsmStride; c += sizeof(uint32_t)) + { + memcpy(v->members[i].members[c].value.u32v.data(), data, sizeof(uint32_t)); + + data += sizeof(uint32_t); + } + } + } + + RDCASSERTEQUAL(data, global.groupshared[gsmIndex].data.end()); + + change.after = *changeVar; + state->changes.push_back(change); + } + } + break; + } case OPCODE_NOP: case OPCODE_CUSTOMDATA: case OPCODE_OPAQUE_CUSTOMDATA: case OPCODE_SHADER_MESSAGE: case OPCODE_DCL_IMMEDIATE_CONSTANT_BUFFER: break; - case OPCODE_SYNC: // might never need to implement this. Who knows! - break; case OPCODE_DMOV: case OPCODE_MOV: SetDst(state, op.operands[0], op, srcOpers[0]); break; case OPCODE_DMOVC: @@ -2973,6 +3079,7 @@ void ThreadState::StepNext(ShaderDebugState *state, DebugAPIWrapper *apiWrapper, bool structured = false; byte *data = NULL; + byte *gsm_base = NULL; if(gsm) { @@ -2987,7 +3094,7 @@ void ThreadState::StepNext(ShaderDebugState *state, DebugAPIWrapper *apiWrapper, { numElems = global.groupshared[resIndex].count; stride = global.groupshared[resIndex].bytestride; - data = &global.groupshared[resIndex].data[0]; + gsm_base = data = &global.groupshared[resIndex].data[0]; structured = global.groupshared[resIndex].structured; } } @@ -3088,6 +3195,15 @@ void ThreadState::StepNext(ShaderDebugState *state, DebugAPIWrapper *apiWrapper, case OPCODE_ATOMIC_UMIN: *udst = RDCMIN(*udst, *usrc0); break; default: break; } + + if(gsm && state) + { + // only one uint + ShaderVariable val = ShaderVariable(rdcstr(), *udst, *udst, *udst, *udst); + val.columns = 1; + + SetGroupsharedDst(state, resIndex, uint32_t(data - gsm_base), val); + } } break; @@ -3212,6 +3328,7 @@ void ThreadState::StepNext(ShaderDebugState *state, DebugAPIWrapper *apiWrapper, RDCASSERT(stride != 0); byte *data = NULL; + byte *gsm_base = NULL; size_t dataSize = 0; bool texData = false; uint32_t rowPitch = 0; @@ -3233,7 +3350,7 @@ void ThreadState::StepNext(ShaderDebugState *state, DebugAPIWrapper *apiWrapper, { numElems = global.groupshared[resIndex].count; stride = global.groupshared[resIndex].bytestride; - data = global.groupshared[resIndex].data.data(); + gsm_base = data = global.groupshared[resIndex].data.data(); dataSize = global.groupshared[resIndex].data.size(); fmt.fmt = CompType::UInt; fmt.byteWidth = 4; @@ -3392,6 +3509,16 @@ void ThreadState::StepNext(ShaderDebugState *state, DebugAPIWrapper *apiWrapper, TypedUAVStore(fmt, data, srcOpers[srcIdx]); } + + if(gsm && state) + { + // read the variable + ShaderVariable val = srcOpers[srcIdx]; + // adjust the number of components + val.columns = fmt.numComps & 0xff; + + SetGroupsharedDst(state, resIndex, uint32_t(data - gsm_base), val); + } } } @@ -4775,10 +4902,42 @@ ShaderDebugTrace *InterpretDebugger::BeginDebug(const DXBC::DXBCContainer *dxbcC this->dxbc = dxbcContainer; this->activeLaneIndex = activeIndex; + uint32_t numthreads[3] = {0, 0, 0}; + + for(size_t i = 0; i < dxbcContainer->GetDXBCByteCode()->GetNumDeclarations(); i++) + { + const Declaration &decl = dxbcContainer->GetDXBCByteCode()->GetDeclaration(i); + + if(decl.declaration == OPCODE_DCL_THREAD_GROUP) + { + numthreads[0] = decl.groupSize[0]; + numthreads[1] = decl.groupSize[1]; + numthreads[2] = decl.groupSize[2]; + break; + } + } + int workgroupSize = dxbc->m_Type == DXBC::ShaderType::Pixel ? 4 : 1; + + if(dxbc->m_Type == DXBC::ShaderType::Compute && + dxbcContainer->GetThreadScope() == DXBC::ThreadScope::Workgroup) + { + if(D3D_Hack_EnableGroups()) + workgroupSize = numthreads[0] * numthreads[1] * numthreads[2]; + } + for(int i = 0; i < workgroupSize; i++) + { workgroup.push_back(ThreadState(i, global, dxbc)); + if(dxbc->m_Type == DXBC::ShaderType::Compute && workgroupSize > 1) + { + workgroup[i].semantics.ThreadID[0] = (i) % numthreads[0]; + workgroup[i].semantics.ThreadID[1] = (i / numthreads[0]) % numthreads[1]; + workgroup[i].semantics.ThreadID[2] = (i / numthreads[0] / numthreads[1]) % numthreads[2]; + } + } + if(dxbc->m_Type == DXBC::ShaderType::Compute) global.PopulateGroupshared(dxbc->GetDXBCByteCode()); @@ -5120,7 +5279,54 @@ void InterpretDebugger::CalcActiveMask(rdcarray &activeMask) for(bool &active : activeMask) active = true; - // only pixel shaders automatically converge workgroups, compute shaders need explicit sync + // only compute and pixel need any divergence/convergence info + if(dxbc->m_Type != DXBC::ShaderType::Pixel && dxbc->m_Type != DXBC::ShaderType::Compute) + return; + + // see if we've diverged + bool differentNext = false; + for(size_t i = 1; i < workgroup.size(); i++) + differentNext |= (workgroup[0].nextInstruction != workgroup[i].nextInstruction); + + // if we're all in lockstep, return! + if(!differentNext) + return; + + // for compute shaders, we hold up any threads that are behind if one thread is on a SYNC opcode + if(dxbc->m_Type == DXBC::ShaderType::Compute) + { + bool anySync = false; + uint32_t minInst = workgroup[0].nextInstruction, syncPoint = ~0U; + for(size_t i = 0; i < workgroup.size(); i++) + { + DXBCBytecode::OpcodeType op = + dxbc->GetDXBCByteCode()->GetInstruction(workgroup[i].nextInstruction).operation; + + if(op == DXBCBytecode::OPCODE_SYNC) + { + anySync = true; + RDCASSERT(syncPoint == ~0U || syncPoint == workgroup[i].nextInstruction); + syncPoint = workgroup[i].nextInstruction; + } + minInst = RDCMIN(minInst, workgroup[i].nextInstruction); + } + + // we've diverged, and at least one thread is on a sync. Only threads before that sync are + // active. Check that we're not about to deadlock and that some threads are behind. We should + // never be in the situation where threads are on different sync operations provided that sync + // operations only exist in uniform control flow, and similarly it should not be possible to get + // diverged ahead of the sync point + RDCASSERT(!anySync || minInst < syncPoint); + + if(anySync) + { + for(size_t i = 0; i < workgroup.size(); i++) + if(workgroup[i].nextInstruction == syncPoint) + activeMask[i] = false; + } + } + + // only pixel shaders automatically converge workgroups, compute shaders are handled above if(dxbc->m_Type != DXBC::ShaderType::Pixel) return; @@ -5147,60 +5353,52 @@ void InterpretDebugger::CalcActiveMask(rdcarray &activeMask) // all threads as active. // if we've converged, or we were never diverged, this keeps everything ticking - // see if we've diverged - bool differentNext = false; - for(size_t i = 1; i < workgroup.size(); i++) - differentNext |= (workgroup[0].nextInstruction != workgroup[i].nextInstruction); + // this isn't *perfect* but it will still eventually continue. We look for the most advanced + // thread, and check to see if it's just finished a control flow. If it has then we assume it's + // at the convergence point and wait for every other thread to catch up, pausing any threads + // that reach the convergence point before others. - if(differentNext) + // Note this might mean we don't have any threads paused even within divergent flow. This is + // fine and all we care about is pausing to make sure threads don't run ahead into code that + // should be lockstep. We don't care at all about what they do within the code that is + // divergent. + + // The reason this isn't perfect is that the most advanced thread could be on an inner loop or + // inner if, not the convergence point, and we could be pausing it fruitlessly. Worse still - it + // could be on a branch none of the other threads will take so they will never reach that exact + // instruction. + // But we know that all threads will eventually go through the convergence point, so even in + // that worst case if we didn't pick the right waiting point, another thread will overtake and + // become the new most advanced thread and the previous waiting thread will resume. So in this + // case we caused a thread to wait more than it should have but that's not a big deal as it's + // within divergent flow so they don't have to stay in lockstep. Also if all threads will + // eventually pass that point we picked, we just waited to converge even in technically + // divergent code which is also harmless. + + // Phew! + + uint32_t convergencePoint = 0; + + // find which thread is most advanced + for(size_t i = 0; i < workgroup.size(); i++) + if(workgroup[i].nextInstruction > convergencePoint) + convergencePoint = workgroup[i].nextInstruction; + + if(convergencePoint > 0) { - // this isn't *perfect* but it will still eventually continue. We look for the most advanced - // thread, and check to see if it's just finished a control flow. If it has then we assume it's - // at the convergence point and wait for every other thread to catch up, pausing any threads - // that reach the convergence point before others. + DXBCBytecode::OpcodeType op = + dxbc->GetDXBCByteCode()->GetInstruction(convergencePoint - 1).operation; - // Note this might mean we don't have any threads paused even within divergent flow. This is - // fine and all we care about is pausing to make sure threads don't run ahead into code that - // should be lockstep. We don't care at all about what they do within the code that is - // divergent. - - // The reason this isn't perfect is that the most advanced thread could be on an inner loop or - // inner if, not the convergence point, and we could be pausing it fruitlessly. Worse still - it - // could be on a branch none of the other threads will take so they will never reach that exact - // instruction. - // But we know that all threads will eventually go through the convergence point, so even in - // that worst case if we didn't pick the right waiting point, another thread will overtake and - // become the new most advanced thread and the previous waiting thread will resume. So in this - // case we caused a thread to wait more than it should have but that's not a big deal as it's - // within divergent flow so they don't have to stay in lockstep. Also if all threads will - // eventually pass that point we picked, we just waited to converge even in technically - // divergent code which is also harmless. - - // Phew! - - uint32_t convergencePoint = 0; - - // find which thread is most advanced - for(size_t i = 0; i < workgroup.size(); i++) - if(workgroup[i].nextInstruction > convergencePoint) - convergencePoint = workgroup[i].nextInstruction; - - if(convergencePoint > 0) - { - DXBCBytecode::OpcodeType op = - dxbc->GetDXBCByteCode()->GetInstruction(convergencePoint - 1).operation; - - // if the most advnaced thread hasn't just finished control flow, then all - // threads are still running, so don't converge - if(op != OPCODE_ENDIF && op != OPCODE_ENDLOOP && op != OPCODE_ENDSWITCH) - convergencePoint = 0; - } - - // pause any threads at that instruction (could be none) - for(size_t i = 0; i < workgroup.size(); i++) - if(workgroup[i].nextInstruction == convergencePoint) - activeMask[i] = false; + // if the most advnaced thread hasn't just finished control flow, then all + // threads are still running, so don't converge + if(op != OPCODE_ENDIF && op != OPCODE_ENDLOOP && op != OPCODE_ENDSWITCH) + convergencePoint = 0; } + + // pause any threads at that instruction (could be none) + for(size_t i = 0; i < workgroup.size(); i++) + if(workgroup[i].nextInstruction == convergencePoint) + activeMask[i] = false; } rdcarray InterpretDebugger::ContinueDebug(DXBCDebug::DebugAPIWrapper *apiWrapper) diff --git a/renderdoc/driver/shaders/dxbc/dxbc_debug.h b/renderdoc/driver/shaders/dxbc/dxbc_debug.h index 3a2c0f448..aa6906fb5 100644 --- a/renderdoc/driver/shaders/dxbc/dxbc_debug.h +++ b/renderdoc/driver/shaders/dxbc/dxbc_debug.h @@ -249,6 +249,8 @@ private: // file and applying any masking or swizzling void SetDst(ShaderDebugState *state, const DXBCBytecode::Operand &dstoper, const DXBCBytecode::Operation &op, const ShaderVariable &val); + void SetGroupsharedDst(ShaderDebugState *state, uint32_t gsmIndex, const uint32_t byteOffset, + ShaderVariable &val); void MarkResourceAccess(ShaderDebugState *state, DXBCBytecode::OperandType type, const BindingSlot &slot); diff --git a/renderdoc/driver/shaders/dxbc/dxbc_spdb.cpp b/renderdoc/driver/shaders/dxbc/dxbc_spdb.cpp index 1ccb5f7b8..96b3b3675 100644 --- a/renderdoc/driver/shaders/dxbc/dxbc_spdb.cpp +++ b/renderdoc/driver/shaders/dxbc/dxbc_spdb.cpp @@ -40,6 +40,25 @@ namespace DXBC { +struct TypeMember +{ + rdcstr name; + uint16_t byteOffset; + uint32_t typeIndex; +}; + +struct TypeDesc +{ + rdcstr name; + VarType baseType; + uint32_t byteSize; + uint16_t vecSize; + uint16_t matArrayStride : 15; + uint16_t colMajorMatrix : 1; + LEAF_ENUM_e leafType; + rdcarray members; +}; + bool IsPDBFile(void *data, size_t length) { FileHeaderPage *header = (FileHeaderPage *)data; @@ -173,25 +192,6 @@ SPDBChunk::SPDBChunk(byte *data, uint32_t spdblength) } } - struct TypeMember - { - rdcstr name; - uint16_t byteOffset; - uint32_t typeIndex; - }; - - struct TypeDesc - { - rdcstr name; - VarType baseType; - uint32_t byteSize; - uint16_t vecSize; - uint16_t matArrayStride : 15; - uint16_t colMajorMatrix : 1; - LEAF_ENUM_e leafType; - rdcarray members; - }; - std::map typeInfo; // prepopulate with basic types @@ -609,8 +609,15 @@ SPDBChunk::SPDBChunk(byte *data, uint32_t spdblength) stride = typeInfo[stridedArray->elemtype].byteSize & 0xffff; } + SPDBLOG( + "Type %x is a strided array of class %x indexed by type %x with original stride %u, " + "effective stride %u and length %u", + id, stridedArray->elemtype, stridedArray->idxtype, stridedArray->stride, stride, + bytelength); + typeInfo[id] = { - "", typeInfo[stridedArray->elemtype].baseType, bytelength, 1, stride, 0, type, {}, + "", typeInfo[stridedArray->elemtype].baseType, bytelength, 1, stride, 0, + type, typeInfo[stridedArray->elemtype].members, }; break; @@ -842,6 +849,65 @@ SPDBChunk::SPDBChunk(byte *data, uint32_t spdblength) RDCASSERT(compile3->flags.iLanguage == CV_CFL_HLSL && compile3->machine == CV_CFL_D3D11_SHADER); } + else if(type == S_LDATA_HLSL) + { + DATASYMHLSL *ldata = (DATASYMHLSL *)sym; + + LocalMapping mapping = {}; + + // CV_HLSLREG_e == OperandType + + mapping.regType = (DXBCBytecode::OperandType)ldata->regType; + mapping.regIndex = ldata->dataslot; + mapping.regFirstComp = 0; + + SPDBLOG( + "S_LDATA_HLSL: %s is type %x in register type %s data slot %hu data offset %hu, " + "tex/samp/uav slots %hu/%hu/%hu", + ldata->name, ldata->typind, ToStr(mapping.regType).c_str(), ldata->dataslot, + ldata->dataoff, ldata->texslot, ldata->sampslot, ldata->uavslot); + + // range valid for the whole program + mapping.range.endRange = ~0U; + + const rdcstr basename = (char *)ldata->name; + mapping.varOffset = 0; + + const TypeDesc *vartype = &typeInfo[ldata->typind]; + + uint32_t groupsharedCount = (vartype->byteSize + vartype->matArrayStride - 1) / + RDCMAX(4U, (uint32_t)vartype->matArrayStride); + + if(vartype->members.empty()) + { + // if it has a 'simple' type with no members we can do one mapping to the whole groupshared + mapping.var.name = basename; + mapping.varFirstComp = 0; + mapping.numComps = vartype->vecSize; + mapping.var.baseType = vartype->baseType; + mapping.var.rows = 1; + mapping.var.columns = uint8_t(vartype->vecSize); + mapping.var.elements = groupsharedCount; + + m_Locals.push_back(mapping); + } + else + { + mapping.numComps = 1; + // otherwise we need to explode the mappings and do it componentwise to be able to map things correctly + for(uint32_t g = 0; g < groupsharedCount; g++) + { + mapping.var.name = basename; + mapping.regSuffix = StringFormat::Fmt("[%u]", g); + mapping.var.name += mapping.regSuffix; + mapping.varOffset = g * vartype->byteSize / RDCMAX(1U, (uint32_t)vartype->matArrayStride); + + // recursively linearise the members and apply mappings + uint32_t comp = 0; + UnrollGroupsharedMappings(typeInfo, vartype->members, mapping, comp); + } + } + } else if(type == S_ENVBLOCK) { ENVBLOCKSYM *envblock = (ENVBLOCKSYM *)sym; @@ -1779,6 +1845,7 @@ void SPDBChunk::GetLocals(const DXBC::DXBCContainer *dxbc, size_t, uintptr_t off continue; range.name = dxbc->GetDXBCByteCode()->GetRegisterName(it->regType, it->regIndex); + range.name += it->regSuffix; range.component = it->regFirstComp; if(IsInput(it->regType)) @@ -1857,6 +1924,72 @@ void SPDBChunk::GetLocals(const DXBC::DXBCContainer *dxbc, size_t, uintptr_t off } } +void SPDBChunk::UnrollGroupsharedMappings(const std::map &typeInfo, + const rdcarray &members, LocalMapping mapping, + uint32_t &comp) +{ + rdcstr basename = mapping.var.name; + rdcstr basesuffix = mapping.regSuffix; + uint32_t baseoffset = mapping.varOffset; + for(uint32_t m = 0; m < members.size(); m++) + { + mapping.var.name = basename + "." + members[m].name; + mapping.varOffset = baseoffset + members[m].byteOffset; + + const TypeDesc &membertype = typeInfo.find(members[m].typeIndex)->second; + + if(membertype.members.empty()) + { + mapping.var.baseType = membertype.baseType; + mapping.var.rows = 1; + mapping.var.columns = uint8_t(AlignUp4(membertype.vecSize) / 4); + mapping.var.elements = 1; + + if(membertype.matArrayStride) + { + mapping.var.rows = uint8_t((membertype.byteSize + membertype.matArrayStride - 1) / + membertype.matArrayStride); + + // unless this is a column major matrix, in which case each vector is a column so swap the + // rows/columns (the number of ROWS is the vector size, when each vector is a column) + if(membertype.colMajorMatrix) + std::swap(mapping.var.rows, mapping.var.columns); + + if(membertype.leafType != LF_MATRIX) + { + mapping.var.elements = mapping.var.rows; + mapping.var.rows = 1; + } + } + + for(uint32_t c = 0; c < AlignUp4(membertype.byteSize) / 4; c++) + { + uint32_t element = c / membertype.vecSize; + mapping.varFirstComp = c % membertype.vecSize; + + if(membertype.matArrayStride) + { + mapping.var.name = + StringFormat::Fmt("%s.%s[%u]", basename.c_str(), members[m].name.c_str(), element); + } + else if(membertype.vecSize > 1) + { + mapping.var.name = StringFormat::Fmt("%s.%s", basename.c_str(), members[m].name.c_str()); + } + + mapping.regSuffix = StringFormat::Fmt("%s._%u", basesuffix.c_str(), comp); + comp++; + + m_Locals.push_back(mapping); + } + } + else + { + UnrollGroupsharedMappings(typeInfo, membertype.members, mapping, comp); + } + } +} + IDebugInfo *ProcessSPDBChunk(void *chunk) { uint32_t *raw = (uint32_t *)chunk; diff --git a/renderdoc/driver/shaders/dxbc/dxbc_spdb.h b/renderdoc/driver/shaders/dxbc/dxbc_spdb.h index 7a20009fc..4e50255d8 100644 --- a/renderdoc/driver/shaders/dxbc/dxbc_spdb.h +++ b/renderdoc/driver/shaders/dxbc/dxbc_spdb.h @@ -248,6 +248,7 @@ struct LocalMapping bool operator<(const LocalMapping &o) const { return range.startRange < o.range.startRange; } LocalRange range; uint8_t regFirstComp; + rdcstr regSuffix; uint32_t varFirstComp; uint32_t varOffset; uint32_t numComps; @@ -261,6 +262,9 @@ struct LocalMapping uint32_t regIndex; }; +struct TypeMember; +struct TypeDesc; + class SPDBChunk : public IDebugInfo { public: @@ -280,6 +284,10 @@ public: rdcarray &locals) const; private: + void UnrollGroupsharedMappings(const std::map &typeInfo, + const rdcarray &members, LocalMapping mapping, + uint32_t &comp); + bool m_HasDebugInfo; rdcstr m_CompilerSig;