mirror of
https://github.com/baldurk/renderdoc.git
synced 2026-08-12 01:30:57 +00:00
Simulate and propagate gsm data in DXBC
This commit is contained in:
@@ -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();
|
||||
|
||||
|
||||
@@ -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> ®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)
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user