Simulate and propagate gsm data in DXBC

This commit is contained in:
baldurk
2025-02-21 13:46:58 +00:00
parent fc5282398f
commit 5e3bfb29b2
10 changed files with 499 additions and 82 deletions
+12 -1
View File
@@ -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();
+13 -1
View File
@@ -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();
@@ -664,6 +664,46 @@ void Program::SetupRegisterFile(rdcarray<ShaderVariable> &registers) 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)
@@ -1174,6 +1174,8 @@ protected:
uint32_t m_NumTemps = 0;
rdcarray<uint32_t> m_IndexTempSizes;
// each one is declared with byteStride, elementCount
rdcarray<rdcpair<uint32_t, uint32_t>> m_GroupsharedTempSizes;
// most regular outputs, including system value outputs like primitive ID are given a register
// number
@@ -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)
{
@@ -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)
{
+253 -55
View File
@@ -26,12 +26,16 @@
#include "dxbc_debug.h"
#include <algorithm>
#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<ThreadState> &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<bool> &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<bool> &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<ShaderDebugState> InterpretDebugger::ContinueDebug(DXBCDebug::DebugAPIWrapper *apiWrapper)
@@ -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);
+153 -20
View File
@@ -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<TypeMember> 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<TypeMember> members;
};
std::map<uint32_t, TypeDesc> 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<uint32_t, TypeDesc> &typeInfo,
const rdcarray<TypeMember> &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;
@@ -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<SourceVariableMapping> &locals) const;
private:
void UnrollGroupsharedMappings(const std::map<uint32_t, TypeDesc> &typeInfo,
const rdcarray<TypeMember> &members, LocalMapping mapping,
uint32_t &comp);
bool m_HasDebugInfo;
rdcstr m_CompilerSig;