Batch Spirv debugger MathOp and SampleGatherOp GPU operations

Thread Simulation queues MathOp, SampleGatherOp requests and then the Thread is paused.
The Debugger loop processes the MathOp queue, batches MathOps together into a single command list, Submit, Sync.
The Debugger loop processes the SampleGatherOp queue, batches GPU SampleGatherOps together into a single command list, Submit, Sync. Some SampleGatherOps are run on the CPU and do not use the GPU.

When the queued operations are completed, the results are written back into the Thread Simulation and the paused Threads are rerun.
This commit is contained in:
Jake Turner committed 2025-09-24 14:22:04 +01:00
1 parent b9d86315ff
commit 94d7f4365d
7 files changed
+1114 -402

No files matched your search

+160 -76
View File
@@ -336,7 +336,7 @@ void ThreadState::WritePointerValue(Id pointer, const ShaderVariable &val)
ShaderVariable &var = ids[pointer];
if(ContainsNaNInf(val))
m_State->flags |= ShaderEvents::GeneratedNanOrInf;
pendingDebugState.flags |= ShaderEvents::GeneratedNanOrInf;
// if var is a pointer we update the underlying storage and generate at least one change,
// plus any additional ones for other pointers.
@@ -357,7 +357,7 @@ void ThreadState::WritePointerValue(Id pointer, const ShaderVariable &val)
// if this is a write to a SSBO pointer, don't record any alias changes, just record a no-op
// change to this pointer
basechange.after = basechange.before = debugger.GetPointerValue(ids[pointer]);
m_State->changes.push_back(basechange);
pendingDebugState.changes.push_back(basechange);
debugger.WriteThroughPointer(var, val);
return;
}
@@ -395,7 +395,7 @@ void ThreadState::WritePointerValue(Id pointer, const ShaderVariable &val)
{
if(pointer != ptrid)
{
m_State->changes.push_back(changes[ptrIdx]);
pendingDebugState.changes.push_back(changes[ptrIdx]);
changes.erase(ptrIdx);
}
}
@@ -405,7 +405,7 @@ void ThreadState::WritePointerValue(Id pointer, const ShaderVariable &val)
// we're assigning the same value) but that false negative is not a concern.
changes.removeIf([](const ShaderVariableChange &c) { return c.before == c.after; });
m_State->changes.append(changes);
pendingDebugState.changes.append(changes);
// always add a change for the base storage variable written itself, even if that's a no-op.
// This one is not included in any of the pointers lists above
@@ -416,7 +416,7 @@ void ThreadState::WritePointerValue(Id pointer, const ShaderVariable &val)
if(firstLocalWrite)
basechange.before = {};
m_State->changes.push_back(basechange);
pendingDebugState.changes.push_back(basechange);
if(ptrIdx == -1)
pointers.push_back(pointer);
@@ -446,8 +446,12 @@ void ThreadState::DebugBreak()
void ThreadState::SetDst(Id id, const ShaderVariable &val)
{
// Waiting for result i.e. from the GPU or replay thread
if(IsPendingResultPending())
return;
if(m_State && ContainsNaNInf(val))
m_State->flags |= ShaderEvents::GeneratedNanOrInf;
pendingDebugState.flags |= ShaderEvents::GeneratedNanOrInf;
ShaderVariable prev = ids[id];
@@ -486,7 +490,7 @@ void ThreadState::SetDst(Id id, const ShaderVariable &val)
if(wasLive)
change.before = debugger.GetPointerValue(prev);
change.after = debugger.GetPointerValue(ids[id]);
m_State->changes.push_back(change);
pendingDebugState.changes.push_back(change);
}
}
@@ -506,7 +510,7 @@ void ThreadState::ProcessScopeChange(const rdcarray<Id> &oldLive, const rdcarray
if(liveGlobals.contains(id))
continue;
m_State->changes.push_back({debugger.GetPointerValue(ids[id])});
pendingDebugState.changes.push_back({debugger.GetPointerValue(ids[id])});
if(ids[id].type == VarType::GPUPointer && !debugger.IsOpaquePointer(ids[id]) &&
!debugger.IsPhysicalPointer(ids[id]))
@@ -522,7 +526,7 @@ void ThreadState::ProcessScopeChange(const rdcarray<Id> &oldLive, const rdcarray
if(liveGlobals.contains(id))
continue;
m_State->changes.push_back({ShaderVariable(), debugger.GetPointerValue(ids[id])});
pendingDebugState.changes.push_back({ShaderVariable(), debugger.GetPointerValue(ids[id])});
}
}
@@ -790,8 +794,7 @@ bool ThreadState::WorkgroupIsDiverged(const rdcarray<ThreadState> &workgroup)
return false;
}
void ThreadState::StepNext(ShaderDebugState *state, const rdcarray<ThreadState> &workgroup,
const rdcarray<bool> &activeMask)
void ThreadState::StepNext(ShaderDebugState *state, const rdcarray<ThreadState> &workgroup)
{
m_State = state;
@@ -2289,16 +2292,16 @@ void ThreadState::StepNext(ShaderDebugState *state, const rdcarray<ThreadState>
for(uint8_t c = 0; c < var.columns; c++)
{
#undef _IMPL
#define _IMPL(I, S, U) \
if(comp<S>(b, c) != 0) \
{ \
comp<S>(var, c) /= comp<S>(b, c); \
} \
else \
{ \
comp<U>(var, c) = 0; \
if(m_State) \
m_State->flags |= ShaderEvents::GeneratedNanOrInf; \
#define _IMPL(I, S, U) \
if(comp<S>(b, c) != 0) \
{ \
comp<S>(var, c) /= comp<S>(b, c); \
} \
else \
{ \
comp<U>(var, c) = 0; \
if(m_State) \
pendingDebugState.flags |= ShaderEvents::GeneratedNanOrInf; \
}
IMPL_FOR_INT_TYPES(_IMPL);
@@ -2309,16 +2312,16 @@ void ThreadState::StepNext(ShaderDebugState *state, const rdcarray<ThreadState>
for(uint8_t c = 0; c < var.columns; c++)
{
#undef _IMPL
#define _IMPL(I, S, U) \
if(comp<U>(b, c) != 0) \
{ \
comp<U>(var, c) /= comp<U>(b, c); \
} \
else \
{ \
comp<U>(var, c) = 0; \
if(m_State) \
m_State->flags |= ShaderEvents::GeneratedNanOrInf; \
#define _IMPL(I, S, U) \
if(comp<U>(b, c) != 0) \
{ \
comp<U>(var, c) /= comp<U>(b, c); \
} \
else \
{ \
comp<U>(var, c) = 0; \
if(m_State) \
pendingDebugState.flags |= ShaderEvents::GeneratedNanOrInf; \
}
IMPL_FOR_INT_TYPES(_IMPL);
@@ -2329,16 +2332,16 @@ void ThreadState::StepNext(ShaderDebugState *state, const rdcarray<ThreadState>
for(uint8_t c = 0; c < var.columns; c++)
{
#undef _IMPL
#define _IMPL(I, S, U) \
if(comp<U>(b, c) != 0) \
{ \
comp<U>(var, c) %= comp<U>(b, c); \
} \
else \
{ \
comp<U>(var, c) = 0; \
if(m_State) \
m_State->flags |= ShaderEvents::GeneratedNanOrInf; \
#define _IMPL(I, S, U) \
if(comp<U>(b, c) != 0) \
{ \
comp<U>(var, c) %= comp<U>(b, c); \
} \
else \
{ \
comp<U>(var, c) = 0; \
if(m_State) \
pendingDebugState.flags |= ShaderEvents::GeneratedNanOrInf; \
}
IMPL_FOR_INT_TYPES(_IMPL);
@@ -2360,22 +2363,22 @@ void ThreadState::StepNext(ShaderDebugState *state, const rdcarray<ThreadState>
for(uint8_t c = 0; c < var.columns; c++)
{
#undef _IMPL
#define _IMPL(I, S, U) \
if(comp<S>(b, c) != 0) \
{ \
S op1 = comp<S>(var, c); \
S op2 = comp<S>(b, c); \
S tmp = op1 % op2; \
if(opdata.op == Op::SRem) \
comp<S>(var, c) = op1 < 0 ? (S)-ABS(tmp) : (S)ABS(tmp); \
else \
comp<S>(var, c) = op2 < 0 ? (S)-ABS(tmp) : (S)ABS(tmp); \
} \
else \
{ \
comp<S>(var, c) = 0; \
if(m_State) \
m_State->flags |= ShaderEvents::GeneratedNanOrInf; \
#define _IMPL(I, S, U) \
if(comp<S>(b, c) != 0) \
{ \
S op1 = comp<S>(var, c); \
S op2 = comp<S>(b, c); \
S tmp = op1 % op2; \
if(opdata.op == Op::SRem) \
comp<S>(var, c) = op1 < 0 ? (S)-ABS(tmp) : (S)ABS(tmp); \
else \
comp<S>(var, c) = op2 < 0 ? (S)-ABS(tmp) : (S)ABS(tmp); \
} \
else \
{ \
comp<S>(var, c) = 0; \
if(m_State) \
pendingDebugState.flags |= ShaderEvents::GeneratedNanOrInf; \
}
IMPL_FOR_INT_TYPES(_IMPL);
@@ -3571,6 +3574,18 @@ void ThreadState::StepNext(ShaderDebugState *state, const rdcarray<ThreadState>
case Op::ImageSampleProjDrefExplicitLod:
case Op::ImageSampleProjDrefImplicitLod:
{
const DataType &resultType = debugger.GetType(opdata.resultType);
if(IsPendingResultReady())
{
ShaderVariable result = GetPendingResult();
result.rows = 1;
result.columns = RDCMAX(1U, resultType.vector().count) & 0xff;
SetDst(opdata.result, result);
break;
}
ShaderVariable img;
ShaderVariable sampler;
ShaderVariable uv;
@@ -3724,8 +3739,6 @@ void ThreadState::StepNext(ShaderDebugState *state, const rdcarray<ThreadState>
sampler = sampler.members[1];
}
const DataType &resultType = debugger.GetType(opdata.resultType);
RDCASSERT(img.type == VarType::ReadOnlyResource || img.type == VarType::ReadWriteResource);
RDCASSERT(sampler.type == VarType::Unknown || sampler.type == VarType::ReadOnlyResource ||
sampler.type == VarType::Sampler);
@@ -3744,13 +3757,8 @@ void ThreadState::StepNext(ShaderDebugState *state, const rdcarray<ThreadState>
if(sampler.type == VarType::Sampler || sampler.type == VarType::ReadOnlyResource)
samplerIndex = sampler.GetBindIndex();
if(!debugger.GetAPIWrapper()->CalculateSampleGather(
*this, opdata.op, texType, img.GetBindIndex(), samplerIndex, uv, ddxCalc, ddyCalc,
compare, gather, operands, result))
{
// sample failed. Pretend we got 0 columns back
set0001(result);
}
QueueSampleGather(opdata.op, texType, img.GetBindIndex(), samplerIndex, uv, ddxCalc, ddyCalc,
compare, gather, operands, result);
result.rows = 1;
result.columns = RDCMAX(1U, resultType.vector().count) & 0xff;
@@ -3760,6 +3768,13 @@ void ThreadState::StepNext(ShaderDebugState *state, const rdcarray<ThreadState>
}
case Op::ImageRead:
{
if(IsPendingResultReady())
{
ShaderVariable result = GetPendingResult();
SetDst(opdata.result, result);
break;
}
OpImageRead read(it);
ShaderVariable img = GetSrc(read.image);
@@ -3791,14 +3806,9 @@ void ThreadState::StepNext(ShaderDebugState *state, const rdcarray<ThreadState>
// do it with samplegather as ImageFetch rather than a Read which caches the whole texture
// on the CPU for no reason (since we can't write to it)
if(!debugger.GetAPIWrapper()->CalculateSampleGather(
*this, Op::ImageFetch, texType, img.GetBindIndex(), ShaderBindIndex(), coord,
ShaderVariable(), ShaderVariable(), ShaderVariable(), GatherChannel::Red,
ImageOperandsAndParamDatas(), result))
{
// sample failed. Pretend we got 0 columns back
set0001(result);
}
QueueSampleGather(Op::ImageFetch, texType, img.GetBindIndex(), ShaderBindIndex(), coord,
ShaderVariable(), ShaderVariable(), ShaderVariable(), GatherChannel::Red,
ImageOperandsAndParamDatas(), result);
}
else
{
@@ -4984,6 +4994,18 @@ void ThreadState::StepNext(ShaderDebugState *state, const rdcarray<ThreadState>
case Op::Max: RDCWARN("Unhandled SPIR-V operation %s", ToStr(opdata.op).c_str()); break;
}
// if instruction result is pending i.e. waiting for GPU then stay on the current instruction (early return)
if(IsPendingResultPending())
{
nextInstruction--;
// This instruction is being deferred clear the pending debug state
if(m_State)
ClearPendingDebugState();
m_State = NULL;
return;
}
SetPendingResultUnknown();
// skip over any degenerate branches
while(!debugger.HasDebugInfo())
{
@@ -5012,7 +5034,7 @@ void ThreadState::StepNext(ShaderDebugState *state, const rdcarray<ThreadState>
// set the state's next instruction (if we have one) to ours, bounded by how many
// instructions there are
if(m_State)
m_State->nextInstruction = RDCMIN(nextInstruction, debugger.GetNumInstructions() - 1);
pendingDebugState.nextInstruction = RDCMIN(nextInstruction, debugger.GetNumInstructions() - 1);
m_State = NULL;
}
@@ -5044,7 +5066,7 @@ void ThreadState::ExecuteMemoryBarrier(Id semanticsId)
AssignValue(privates[localIndex], globalData);
change.after = privates[localIndex];
if(!(change.after == change.before))
m_State->changes.push_back(change);
pendingDebugState.changes.push_back(change);
}
else
{
@@ -5057,4 +5079,66 @@ void ThreadState::ExecuteMemoryBarrier(Id semanticsId)
}
}
}
void ThreadState::QueueMathOp(GLSLstd450 op, const rdcarray<ShaderVariable> &paramVars,
const ShaderVariable &result)
{
RDCASSERT(!IsPendingResultPending());
pendingResultData = result;
queuedGpuMathOp.workgroupIndex = workgroupIndex;
queuedGpuMathOp.op = op;
queuedGpuMathOp.paramVars = paramVars;
queuedGpuMathOp.result = &pendingResultData;
SetStepNeedsGpuMathOp();
}
void ThreadState::QueueSampleGather(Op opcode, DebugAPIWrapper::TextureType texType,
const ShaderBindIndex &imageBind,
const ShaderBindIndex &samplerBind, const ShaderVariable &uv,
const ShaderVariable &ddxCalc, const ShaderVariable &ddyCalc,
const ShaderVariable &compare, GatherChannel gatherChannel,
const ImageOperandsAndParamDatas &operands,
const ShaderVariable &result)
{
RDCASSERT(!IsPendingResultPending());
pendingResultData = result;
queuedGpuSampleGatherOp.workgroupIndex = workgroupIndex;
queuedGpuSampleGatherOp.opcode = opcode;
queuedGpuSampleGatherOp.texType = texType;
queuedGpuSampleGatherOp.imageBind = imageBind;
queuedGpuSampleGatherOp.samplerBind = samplerBind;
queuedGpuSampleGatherOp.uv = uv;
queuedGpuSampleGatherOp.ddxCalc = ddxCalc;
queuedGpuSampleGatherOp.ddyCalc = ddyCalc;
queuedGpuSampleGatherOp.compare = compare;
queuedGpuSampleGatherOp.gatherChannel = gatherChannel;
queuedGpuSampleGatherOp.operands = operands;
queuedGpuSampleGatherOp.result = &pendingResultData;
SetStepNeedsGpuSampleGatherOp();
}
}; // namespace rdcspv
template <>
rdcstr DoStringise(const rdcspv::ThreadState::PendingResultStatus &el)
{
BEGIN_ENUM_STRINGISE(rdcspv::ThreadState::PendingResultStatus)
{
STRINGISE_ENUM_CLASS(Unknown)
STRINGISE_ENUM_CLASS(Pending)
STRINGISE_ENUM_CLASS(Ready)
}
END_ENUM_STRINGISE();
};
template <>
rdcstr DoStringise(const rdcspv::StepThreadMode &el)
{
BEGIN_ENUM_STRINGISE(rdcspv::StepThreadMode)
{
STRINGISE_ENUM_CLASS(RUN_SINGLE_STEP)
STRINGISE_ENUM_CLASS(RUN_MULTIPLE_STEPS)
STRINGISE_ENUM_CLASS(QUEUE_MULTIPLE_STEPS)
}
END_ENUM_STRINGISE();
};
+209 -12
View File
@@ -58,6 +58,25 @@ enum class ThreadProperty : uint32_t
ITERABLE_OPERATORS(ThreadProperty);
inline void AtomicStore(int32_t *var, int32_t newVal)
{
int32_t oldVal = *var;
while(Atomic::CmpExch32(var, oldVal, newVal) != oldVal)
{
oldVal = *var;
};
}
inline int32_t AtomicLoad(int32_t *var)
{
return Atomic::CmpExch32(var, 0, 0);
}
inline int32_t AtomicLoad(const int32_t *var)
{
return Atomic::CmpExch32((int32_t *)var, 0, 0);
}
struct ThreadState;
class DebugAPIWrapper
@@ -99,16 +118,17 @@ public:
Subpass_Texture = 0x20,
};
virtual bool CalculateSampleGather(ThreadState &lane, Op opcode, TextureType texType,
ShaderBindIndex imageBind, ShaderBindIndex samplerBind,
const ShaderVariable &uv, const ShaderVariable &ddxCalc,
const ShaderVariable &ddyCalc, const ShaderVariable &compare,
GatherChannel gatherChannel,
const ImageOperandsAndParamDatas &operands,
ShaderVariable &output) = 0;
virtual bool CalculateMathOp(ThreadState &lane, GLSLstd450 op,
const rdcarray<ShaderVariable> &params, ShaderVariable &output) = 0;
virtual bool QueueSampleGather(ThreadState &lane, Op opcode, TextureType texType,
const ShaderBindIndex &imageBind,
const ShaderBindIndex &samplerBind, const ShaderVariable &uv,
const ShaderVariable &ddxCalc, const ShaderVariable &ddyCalc,
const ShaderVariable &compare, GatherChannel gatherChannel,
const rdcspv::ImageOperandsAndParamDatas &operands,
ShaderVariable &output, bool &hasResult) = 0;
virtual bool QueueCalculateMathOp(GLSLstd450 op, const rdcarray<ShaderVariable> &params) = 0;
virtual bool GetQueuedResults(rdcarray<ShaderVariable *> &mathOpResults,
rdcarray<ShaderVariable *> &sampleGatherResults) = 0;
virtual bool QueuedOpsHasSpace() = 0;
};
typedef ShaderVariable (*ExtInstImpl)(ThreadState &, uint32_t, const rdcarray<Id> &);
@@ -173,6 +193,49 @@ private:
StackFrame &operator=(const StackFrame &o) = delete;
};
struct GpuMathOperation
{
void Clear()
{
workgroupIndex = 0;
op = GLSLstd450::Invalid;
paramVars.clear();
result = NULL;
}
uint32_t workgroupIndex;
GLSLstd450 op;
rdcarray<ShaderVariable> paramVars;
ShaderVariable *result;
};
struct GpuSampleGatherOperation
{
void Clear()
{
workgroupIndex = 0;
opcode = Op::Max;
texType = DebugAPIWrapper::TextureType::Float_Texture;
imageBind = ShaderBindIndex();
samplerBind = ShaderBindIndex();
uv = ddxCalc = ddyCalc = compare = ShaderVariable();
gatherChannel = GatherChannel::Red;
operands.setNone();
result = NULL;
}
uint32_t workgroupIndex;
Op opcode;
DebugAPIWrapper::TextureType texType;
ShaderBindIndex imageBind;
ShaderBindIndex samplerBind;
ShaderVariable uv;
ShaderVariable ddxCalc;
ShaderVariable ddyCalc;
ShaderVariable compare;
GatherChannel gatherChannel;
ImageOperandsAndParamDatas operands;
ShaderVariable *result = NULL;
};
class Debugger;
struct ThreadState
@@ -181,8 +244,7 @@ struct ThreadState
~ThreadState();
void EnterEntryPoint(ShaderDebugState *state);
void StepNext(ShaderDebugState *state, const rdcarray<ThreadState> &workgroup,
const rdcarray<bool> &activeMask);
void StepNext(ShaderDebugState *state, const rdcarray<ThreadState> &workgroup);
enum DerivDir
{
@@ -205,6 +267,8 @@ struct ThreadState
uint32_t currentInstruction;
uint32_t nextInstruction;
rdcarray<bool> activeMask;
const GlobalState &global;
Debugger &debugger;
@@ -267,6 +331,87 @@ struct ThreadState
void DebugBreak();
enum class PendingResultStatus : int32_t
{
Unknown,
Pending,
Ready,
Stepped,
};
void QueueMathOp(GLSLstd450 op, const rdcarray<ShaderVariable> &paramVars,
const ShaderVariable &result);
void QueueSampleGather(Op opcode, DebugAPIWrapper::TextureType texType,
const ShaderBindIndex &imageBind, const ShaderBindIndex &samplerBind,
const ShaderVariable &uv, const ShaderVariable &ddxCalc,
const ShaderVariable &ddyCalc, const ShaderVariable &compare,
GatherChannel gatherChannel, const ImageOperandsAndParamDatas &operands,
const ShaderVariable &result);
bool IsPendingResultPending() const
{
return GetPendingResultStatus() == PendingResultStatus::Pending;
}
bool IsPendingResultReady() const
{
return GetPendingResultStatus() == PendingResultStatus::Ready;
}
void SetPendingResultUnknown() { SetPendingResultStatus(PendingResultStatus::Unknown); }
void SetPendingResultReady()
{
RDCASSERTEQUAL(GetPendingResultStatus(), PendingResultStatus::Pending);
SetPendingResultStatus(PendingResultStatus::Ready);
}
const ShaderVariable &GetPendingResult() const
{
RDCASSERTEQUAL(GetPendingResultStatus(), PendingResultStatus::Ready);
return pendingResultData;
}
void SetStepQueued()
{
AtomicStore(&atomic_isSimulationStepActive, 1);
AtomicStore(&atomic_stepNeedsGpuSampleGatherOp, 0);
AtomicStore(&atomic_stepNeedsGpuMathOp, 0);
}
void SetStepNeedsGpuSampleGatherOp()
{
AtomicStore(&atomic_stepNeedsGpuSampleGatherOp, 1);
SetPendingResultStatus(PendingResultStatus::Pending);
}
bool StepNeedsGpuSampleGatherOp() const
{
return (AtomicLoad(&atomic_stepNeedsGpuSampleGatherOp) == 1);
}
void SetStepNeedsGpuMathOp()
{
AtomicStore(&atomic_stepNeedsGpuMathOp, 1);
SetPendingResultStatus(PendingResultStatus::Pending);
}
bool StepNeedsGpuMathOp() const { return (AtomicLoad(&atomic_stepNeedsGpuMathOp) == 1); }
const GpuMathOperation &GetQueuedGpuMathOp() const
{
RDCASSERT(AtomicLoad(&atomic_stepNeedsGpuMathOp));
RDCASSERT(IsPendingResultPending());
return queuedGpuMathOp;
}
const GpuSampleGatherOperation &GetQueuedGpuSampleGatherOp() const
{
RDCASSERT(AtomicLoad(&atomic_stepNeedsGpuSampleGatherOp));
RDCASSERT(IsPendingResultPending());
return queuedGpuSampleGatherOp;
}
void SetSimulationStepCompleted() { AtomicStore(&atomic_isSimulationStepActive, 0); }
bool IsSimulationStepActive() const { return (AtomicLoad(&atomic_isSimulationStepActive) == 1); }
void ClearPendingDebugState()
{
pendingDebugState.changes.clear();
pendingDebugState.flags = ShaderEvents::NoEvent;
pendingDebugState.nextInstruction = 0;
}
const ShaderDebugState &GetPendingDebugState() const { return pendingDebugState; }
private:
void EnterFunction(const rdcarray<Id> &arguments);
void SetDst(Id id, const ShaderVariable &val);
@@ -280,7 +425,21 @@ private:
void ExecuteMemoryBarrier(Id semanticsId);
static bool WorkgroupIsDiverged(const rdcarray<ThreadState> &workgroup);
PendingResultStatus GetPendingResultStatus() const
{
return (PendingResultStatus)AtomicLoad(&atomic_pendingResultStatus);
}
void SetPendingResultStatus(PendingResultStatus status)
{
AtomicStore(&atomic_pendingResultStatus, (int32_t)status);
}
ShaderDebugState pendingDebugState;
ShaderDebugState *m_State = NULL;
ShaderVariable pendingResultData;
GpuMathOperation queuedGpuMathOp;
GpuSampleGatherOperation queuedGpuSampleGatherOp;
// Control Flow state variables
// true if executed an operation which could trigger divergence
@@ -291,6 +450,12 @@ private:
uint32_t convergenceInstruction;
// the instruction after a function call is defined to be a convergence point
uint32_t functionReturnPoint;
// These need to be accessed using atomics
int32_t atomic_pendingResultStatus = (int32_t)PendingResultStatus::Unknown;
int32_t atomic_stepNeedsGpuSampleGatherOp = 0;
int32_t atomic_stepNeedsGpuMathOp = 0;
int32_t atomic_isSimulationStepActive = 0;
};
enum class DebugScope
@@ -408,6 +573,13 @@ struct DebugMessage
rdcstr desc;
};
enum class StepThreadMode
{
RUN_SINGLE_STEP,
RUN_MULTIPLE_STEPS,
QUEUE_MULTIPLE_STEPS
};
class Debugger : public Processor, public ShaderDebugger
{
public:
@@ -459,6 +631,10 @@ public:
ThreadState &GetActiveLane() { return workgroup[activeLaneIndex]; }
const ThreadState &GetActiveLane() const { return workgroup[activeLaneIndex]; }
uint32_t GetSubgroupSize() const { return subgroupSize; }
void QueueGpuMathOp(uint32_t lane);
void QueueGpuSampleGatherOp(uint32_t lane);
uint64_t GetDeviceThreadID() const { return deviceThreadID; }
bool IsDeviceThread() const { return Threading::GetCurrentID() == GetDeviceThreadID(); }
void AddDebugMessage(MessageCategory cat, MessageSeverity sev, MessageSource src, rdcstr desc) const;
@@ -581,10 +757,28 @@ private:
void ClampScalars(const ShaderVariable &var, uint8_t &scalar0) const;
void ClampScalars(const ShaderVariable &var, uint8_t &scalar0, uint8_t &scalar1) const;
void QueueJob(uint32_t lane, rdcarray<ShaderDebugState> *ret);
void StepThread(uint32_t lane, StepThreadMode stepMode, rdcarray<ShaderDebugState> *ret);
void InternalStepThread(uint32_t lane, rdcarray<ShaderDebugState> *ret);
void ProcessQueuedDebugMessages();
void ProcessQueuedOps();
void ProcessQueuedGpuMathOps();
void ProcessQueuedGpuSampleGatherOps();
void SyncPendingGpuOps();
void SyncPendingLanes();
mutable Threading::CriticalSection queuedDebugMessagesLock;
mutable rdcarray<DebugMessage> queuedDebugMessages;
rdcarray<bool> queuedGpuMathOps;
rdcarray<bool> queuedGpuSampleGatherOps;
rdcarray<ShaderDebugState> *shaderChangesReturn;
bool retireIDs = true;
ShaderDebugState activeDebugState;
rdcarray<bool> pendingLanes;
rdcarray<ShaderVariable *> pendingGpuMathsOpsResults;
rdcarray<ShaderVariable *> pendingGpuSampleGatherOpsResults;
uint64_t deviceThreadID;
};
@@ -595,3 +789,6 @@ private:
void AssignValue(ShaderVariable &dst, const ShaderVariable &src);
}; // namespace rdcspv
DECLARE_REFLECTION_ENUM(rdcspv::ThreadState::PendingResultStatus);
DECLARE_REFLECTION_ENUM(rdcspv::StepThreadMode);
@@ -1228,14 +1228,21 @@ ShaderVariable NClamp(ThreadState &state, uint32_t, const rdcarray<Id> &params)
ShaderVariable GPUOp(ThreadState &state, uint32_t instruction, const rdcarray<Id> &params)
{
if(state.IsPendingResultReady())
return state.GetPendingResult();
rdcarray<ShaderVariable> paramVars;
for(Id id : params)
paramVars.push_back(state.GetSrc(id));
ShaderVariable ret = paramVars[0];
if(!state.debugger.GetAPIWrapper()->CalculateMathOp(state, (GLSLstd450)instruction, paramVars, ret))
memset(&ret.value, 0, sizeof(ret.value));
// these two operations change the type of the output
rdcspv::GLSLstd450 op = (GLSLstd450)instruction;
if(op == rdcspv::GLSLstd450::Length || op == rdcspv::GLSLstd450::Distance)
ret.columns = 1;
state.QueueMathOp(op, paramVars, ret);
return ret;
}
@@ -1021,8 +1021,16 @@ ShaderDebugTrace *Debugger::BeginDebug(DebugAPIWrapper *api, const ShaderStage s
stage = shaderStage;
apiWrapper = api;
queuedGpuMathOps.resize(threadsInWorkgroup);
queuedGpuSampleGatherOps.resize(threadsInWorkgroup);
pendingLanes.resize(threadsInWorkgroup);
for(uint32_t i = 0; i < threadsInWorkgroup; i++)
{
workgroup.push_back(ThreadState(*this, global));
queuedGpuMathOps[i] = false;
queuedGpuSampleGatherOps[i] = false;
pendingLanes[i] = false;
}
ThreadState &active = GetActiveLane();
@@ -1643,6 +1651,7 @@ ShaderDebugTrace *Debugger::BeginDebug(DebugAPIWrapper *api, const ShaderStage s
bool isActiveLane = (i == activeLaneIndex);
ThreadState &lane = workgroup[i];
lane.workgroupIndex = i;
lane.activeMask.resize(threadsInWorkgroup);
if(!isActiveLane)
{
lane.nextInstruction = active.nextInstruction;
@@ -2555,6 +2564,7 @@ rdcarray<ShaderDebugState> Debugger::ContinueDebug()
ThreadState &active = GetActiveLane();
rdcarray<ShaderDebugState> ret;
shaderChangesReturn = NULL;
// initialise the first ShaderDebugState if we haven't stepped yet
if(steps == 0)
@@ -2617,15 +2627,16 @@ rdcarray<ShaderDebugState> Debugger::ContinueDebug()
if(active.Finished())
return ret;
rdcarray<bool> activeMask;
bool allStepsCompleted = true;
shaderChangesReturn = &ret;
// continue stepping until we have 1000000 target steps completed in a chunk. This may involve
// doing more steps if our target thread is inactive
for(int stepEnd = steps + 1000000; steps < stepEnd;)
{
global.clock++;
if(active.Finished())
allStepsCompleted = true;
if(active.Finished() && !active.IsSimulationStepActive())
break;
// Execute the threads in each active tangle
@@ -2633,152 +2644,123 @@ rdcarray<ShaderDebugState> Debugger::ContinueDebug()
TangleGroup &tangles = controlFlow.GetTangles();
bool anyActiveThreads = false;
for(const Tangle &tangle : tangles)
{
if(!tangle.IsAliveActive())
continue;
rdcarray<bool> activeMask;
// one bool per workgroup thread
activeMask.resize(workgroup.size());
// calculate the current active thread mask from the threads in the tangle
for(size_t i = 0; i < workgroup.size(); i++)
activeMask[i] = false;
const rdcarray<ThreadReference> &threadRefs = tangle.GetThreadRefs();
for(const ThreadReference &ref : threadRefs)
{
uint32_t lane = ref.id;
RDCASSERT(lane < workgroup.size(), lane, workgroup.size());
ThreadState &thread = workgroup[lane];
RDCASSERT(!thread.Finished());
activeMask[lane] = true;
anyActiveThreads = true;
}
// step all threads in the tangle
for(const ThreadReference &ref : threadRefs)
{
const uint32_t threadId = ref.id;
const uint32_t lane = threadId;
ThreadState &thread = workgroup[lane];
if(thread.nextInstruction >= instructionOffsets.size())
{
if(lane == activeLaneIndex)
ret.emplace_back();
continue;
}
RDCASSERTEQUAL(thread.activeMask.size(), activeMask.size());
memcpy(thread.activeMask.data(), activeMask.data(), activeMask.size() * sizeof(bool));
QueueJob(lane, &ret);
}
}
do
{
ProcessQueuedDebugMessages();
// Convert the simulation threads queued operations into pending operations i.e. GPU commands
ProcessQueuedOps();
// Sync any pending GPU operations and set the results to the pending threads
SyncPendingLanes();
allStepsCompleted = true;
for(const Tangle &tangle : tangles)
{
if(!tangle.IsAliveActive())
continue;
bool tangleStepsCompleted = true;
const rdcarray<ThreadReference> &threadRefs = tangle.GetThreadRefs();
for(const ThreadReference &ref : threadRefs)
{
const uint32_t threadId = ref.id;
const uint32_t lane = threadId;
ThreadState &thread = workgroup[lane];
if(thread.IsSimulationStepActive())
{
tangleStepsCompleted = false;
break;
}
}
if(!tangleStepsCompleted)
{
allStepsCompleted = false;
break;
}
}
} while(!allStepsCompleted);
for(Tangle &tangle : tangles)
{
if(!tangle.IsAliveActive())
continue;
const rdcarray<ThreadReference> &threadRefs = tangle.GetThreadRefs();
// calculate the current active thread mask from the threads in the tangle
#if !defined(RELEASE)
for(const ThreadReference &ref : threadRefs)
{
// one bool per workgroup thread
activeMask.resize(workgroup.size());
// start with all threads as inactive
for(size_t i = 0; i < workgroup.size(); i++)
activeMask[i] = false;
// activate the threads in the tangle
for(const ThreadReference &ref : threadRefs)
{
uint32_t idx = ref.id;
RDCASSERT(idx < workgroup.size(), idx, workgroup.size());
RDCASSERT(!workgroup[idx].Finished());
activeMask[idx] = true;
anyActiveThreads = true;
}
const uint32_t threadId = ref.id;
const uint32_t lane = threadId;
ThreadState &thread = workgroup[lane];
RDCASSERT(!thread.IsSimulationStepActive());
}
#endif // #if !defined(RELEASE)
ExecutionPoint newConvergeInstruction = INVALID_EXECUTION_POINT;
ExecutionPoint newFunctionReturnPoint = INVALID_EXECUTION_POINT;
uint32_t countActiveThreads = 0;
uint32_t countDivergedThreads = 0;
uint32_t countIdentialConvergePointThreads = 0;
uint32_t countIdenticalConvergePointThreads = 0;
uint32_t countFunctionReturnThreads = 0;
// step all active members of the workgroup
for(size_t lane = 0; lane < workgroup.size(); lane++)
// Update the control flow state
for(const ThreadReference &ref : threadRefs)
{
if(!activeMask[lane])
continue;
const uint32_t threadId = ref.id;
const uint32_t lane = threadId;
ThreadState &thread = workgroup[lane];
++countActiveThreads;
ThreadState &thread = workgroup[lane];
const uint32_t threadId = lane;
if(thread.nextInstruction >= instructionOffsets.size())
{
if(lane == activeLaneIndex)
ret.emplace_back();
tangle.SetThreadDead(threadId);
continue;
}
if(lane == activeLaneIndex)
{
ShaderDebugState state;
size_t instOffs = instructionOffsets[thread.nextInstruction];
// see if we're retiring any IDs at this state
for(size_t l = 0; l < thread.live.size();)
{
Id id = thread.live[l];
if(idLiveRange[id].second < instOffs)
{
thread.live.erase(l);
ShaderVariableChange change;
change.before = GetPointerValue(thread.ids[id]);
state.changes.push_back(change);
continue;
}
l++;
}
uint32_t funcRet = ~0U;
size_t prevStackSize = thread.callstack.size();
if(!thread.callstack.empty())
funcRet = thread.callstack.back()->funcCallInstruction;
state.stepIndex = steps;
thread.StepNext(&state, workgroup, activeMask);
if(thread.callstack.size() > prevStackSize)
instOffs =
instructionOffsets[GetInstructionForFunction(thread.callstack.back()->function)];
else if(thread.callstack.size() < prevStackSize && funcRet != ~0U)
instOffs = instructionOffsets[funcRet];
FillCallstack(thread, state);
if(m_DebugInfo.valid)
{
size_t endOffs = instructionOffsets[thread.nextInstruction - 1];
// append any inlined functions to the top of the stack
InlineData *inlined = m_DebugInfo.lineInline[endOffs];
size_t insertPoint = state.callstack.size();
// start with the current scope, it refers to the *inlined* function
if(inlined)
{
const ScopeData *scope = GetScope(endOffs);
// find the function parent of the current scope
while(scope && scope->parent && scope->type == DebugScope::Block)
scope = scope->parent;
state.callstack.insert(insertPoint, scope->name);
}
// if this instruction has no scope, don't give it a callstack
if(GetScope(endOffs) == NULL)
{
state.callstack.clear();
}
// move to the next inline up on our inline stack. If we reach an actual function
// call, this parent will be NULL as there was no more inlining - the final scope will
// refer to the real function which is already on our stack
while(inlined && inlined->parent)
{
const ScopeData *scope = inlined->scope;
// find the function parent of the current scope
while(scope && scope->parent && scope->type == DebugScope::Block)
scope = scope->parent;
state.callstack.insert(insertPoint, scope->name);
inlined = inlined->parent;
}
}
ret.push_back(std::move(state));
steps++;
}
else
{
thread.StepNext(NULL, workgroup, activeMask);
}
threadExecutionStates[threadId] = thread.GetEnteredPoints();
ProcessQueuedDebugMessages();
uint32_t threadConvergeInstruction = thread.GetConvergenceInstruction();
tangle.SetThreadMergePoint(threadId, threadConvergeInstruction);
// the thread activated a new convergence point
@@ -2790,7 +2772,7 @@ rdcarray<ShaderDebugState> Debugger::ContinueDebug()
RDCASSERTNOTEQUAL(newConvergeInstruction, INVALID_EXECUTION_POINT);
}
if(newConvergeInstruction == threadConvergeInstruction)
++countIdentialConvergePointThreads;
++countIdenticalConvergePointThreads;
}
uint32_t threadFunctionReturnPoint = thread.GetFunctionReturnPoint();
@@ -2816,14 +2798,17 @@ rdcarray<ShaderDebugState> Debugger::ContinueDebug()
if(thread.IsDiverged())
++countDivergedThreads;
}
for(size_t lane = 0; lane < workgroup.size(); lane++)
for(const ThreadReference &ref : threadRefs)
{
if(activeMask[lane])
workgroup[lane].currentInstruction = workgroup[lane].nextInstruction;
const uint32_t threadId = ref.id;
const uint32_t lane = threadId;
workgroup[lane].currentInstruction = workgroup[lane].nextInstruction;
}
// If the tangle has a common merge point set it here (this will clear the thread merge point)
// otherwise the convergence point will come from the threads during control flow divergence porcessing
if(countIdentialConvergePointThreads == countActiveThreads)
// otherwise the convergence point will come from the threads during control flow divergence processing
if(countIdenticalConvergePointThreads == countActiveThreads)
tangle.AddMergePoint(newConvergeInstruction);
if(countFunctionReturnThreads)
@@ -2848,6 +2833,8 @@ rdcarray<ShaderDebugState> Debugger::ContinueDebug()
controlFlow.UpdateState(threadExecutionStates);
}
RDCASSERT(allStepsCompleted);
shaderChangesReturn = NULL;
return ret;
}
@@ -4522,6 +4509,133 @@ void Debugger::RegisterOp(Iter it)
}
}
// Can be called from any thread
void Debugger::QueueGpuMathOp(uint32_t lane)
{
ThreadState &thread = workgroup[lane];
RDCASSERT(thread.IsSimulationStepActive());
RDCASSERT(!queuedGpuMathOps[lane]);
queuedGpuMathOps[lane] = true;
}
// Can be called from any thread
void Debugger::QueueGpuSampleGatherOp(uint32_t lane)
{
ThreadState &thread = workgroup[lane];
RDCASSERT(thread.IsSimulationStepActive());
RDCASSERT(!queuedGpuSampleGatherOps[lane]);
queuedGpuSampleGatherOps[lane] = true;
}
// Must be called from the replay manager thread (the debugger thread)
void Debugger::ProcessQueuedOps()
{
CHECK_DEBUGGER_THREAD();
ProcessQueuedGpuMathOps();
ProcessQueuedGpuSampleGatherOps();
SyncPendingGpuOps();
}
// Must be called from the replay manager thread (the debugger thread)
void Debugger::SyncPendingLanes()
{
CHECK_DEBUGGER_THREAD();
for(uint32_t lane = 0; lane < pendingLanes.size(); ++lane)
{
if(pendingLanes[lane])
{
pendingLanes[lane] = false;
ThreadState &thread = workgroup[lane];
thread.SetPendingResultReady();
QueueJob(lane, shaderChangesReturn);
}
}
}
// Must be called from the replay manager thread (the debugger thread)
void Debugger::ProcessQueuedGpuMathOps()
{
CHECK_DEBUGGER_THREAD();
for(uint32_t lane = 0; lane < queuedGpuMathOps.size(); ++lane)
{
if(queuedGpuMathOps[lane])
{
if(!apiWrapper->QueuedOpsHasSpace())
SyncPendingGpuOps();
queuedGpuMathOps[lane] = false;
const GpuMathOperation &mathOp = workgroup[lane].GetQueuedGpuMathOp();
uint32_t workgroupIndex = mathOp.workgroupIndex;
if(apiWrapper->QueueCalculateMathOp(mathOp.op, mathOp.paramVars))
{
pendingGpuMathsOpsResults.push_back(mathOp.result);
}
else
{
ShaderVariable &result = *mathOp.result;
memset(&result.value, 0, sizeof(result.value));
}
RDCASSERT(!pendingLanes[workgroupIndex]);
pendingLanes[workgroupIndex] = true;
}
}
}
// Must be called from the replay manager thread (the debugger thread)
void Debugger::ProcessQueuedGpuSampleGatherOps()
{
CHECK_DEBUGGER_THREAD();
for(uint32_t lane = 0; lane < queuedGpuSampleGatherOps.size(); ++lane)
{
if(queuedGpuSampleGatherOps[lane])
{
if(!apiWrapper->QueuedOpsHasSpace())
SyncPendingGpuOps();
queuedGpuSampleGatherOps[lane] = false;
const GpuSampleGatherOperation &sampleGatherOp = workgroup[lane].GetQueuedGpuSampleGatherOp();
uint32_t workgroupIndex = sampleGatherOp.workgroupIndex;
ThreadState &thread = workgroup[workgroupIndex];
ShaderVariable &result = *sampleGatherOp.result;
bool hasResult = false;
if(!(apiWrapper->QueueSampleGather(
thread, sampleGatherOp.opcode, sampleGatherOp.texType, sampleGatherOp.imageBind,
sampleGatherOp.samplerBind, sampleGatherOp.uv, sampleGatherOp.ddxCalc,
sampleGatherOp.ddyCalc, sampleGatherOp.compare, sampleGatherOp.gatherChannel,
sampleGatherOp.operands, result, hasResult)))
{
// sample failed. Pretend we got 0 columns back
set0001(result);
hasResult = true;
}
if(!hasResult)
pendingGpuSampleGatherOpsResults.push_back(sampleGatherOp.result);
RDCASSERT(!pendingLanes[workgroupIndex]);
pendingLanes[workgroupIndex] = true;
}
}
}
// Must be called from the replay manager thread (the debugger thread)
void Debugger::SyncPendingGpuOps()
{
CHECK_DEBUGGER_THREAD();
if(pendingGpuMathsOpsResults.empty() && pendingGpuSampleGatherOpsResults.empty())
return;
if(!(apiWrapper->GetQueuedResults(pendingGpuMathsOpsResults, pendingGpuSampleGatherOpsResults)))
{
RDCERR("GetQueuedResults failed");
return;
}
pendingGpuMathsOpsResults.clear();
pendingGpuSampleGatherOpsResults.clear();
}
// Must be called from the replay manager thread (the debugger thread)
DebugAPIWrapper *Debugger::GetAPIWrapper() const
{
@@ -4529,6 +4643,193 @@ DebugAPIWrapper *Debugger::GetAPIWrapper() const
return apiWrapper;
}
// Called from any thread
void Debugger::StepThread(uint32_t lane, StepThreadMode stepMode, rdcarray<ShaderDebugState> *ret)
{
ThreadState &thread = workgroup[lane];
bool isActiveThread = lane == activeLaneIndex;
bool simulateStep = true;
RDCASSERT(thread.IsSimulationStepActive());
int curActiveSteps = isActiveThread ? steps : 0;
while(simulateStep)
{
simulateStep = false;
{
thread.ClearPendingDebugState();
if(isActiveThread)
activeDebugState.stepIndex = curActiveSteps;
InternalStepThread(lane, ret);
thread.ClearPendingDebugState();
}
if(thread.StepNeedsGpuSampleGatherOp())
break;
else if(thread.StepNeedsGpuMathOp())
break;
if(isActiveThread)
curActiveSteps++;
if(stepMode == StepThreadMode::RUN_SINGLE_STEP)
break;
};
// Update the number of simulation steps
if(isActiveThread)
steps = curActiveSteps;
RDCASSERT(thread.IsSimulationStepActive());
// The queueing has to be when the thread is not being simulated
if(thread.StepNeedsGpuSampleGatherOp())
{
RDCASSERT(!simulateStep);
QueueGpuSampleGatherOp(lane);
return;
}
if(thread.StepNeedsGpuMathOp())
{
RDCASSERT(!simulateStep);
QueueGpuMathOp(lane);
return;
}
RDCASSERT(!thread.IsPendingResultPending());
thread.SetSimulationStepCompleted();
}
// Called from any thread
void Debugger::InternalStepThread(uint32_t lane, rdcarray<ShaderDebugState> *ret)
{
ThreadState &thread = workgroup[lane];
if(lane == activeLaneIndex)
{
size_t instOffs = instructionOffsets[thread.nextInstruction];
// see if we're retiring any IDs at this state
if(retireIDs)
{
{
RDCASSERT(activeDebugState.callstack.empty());
RDCASSERT(activeDebugState.changes.empty());
RDCASSERT(activeDebugState.flags == ShaderEvents::NoEvent);
RDCASSERT(activeDebugState.nextInstruction == 0);
}
for(size_t l = 0; l < thread.live.size();)
{
Id id = thread.live[l];
if(idLiveRange[id].second < instOffs)
{
thread.live.erase(l);
ShaderVariableChange change;
change.before = GetPointerValue(thread.ids[id]);
activeDebugState.changes.push_back(change);
continue;
}
l++;
}
retireIDs = false;
}
uint32_t funcRet = ~0U;
size_t prevStackSize = thread.callstack.size();
if(!thread.callstack.empty())
funcRet = thread.callstack.back()->funcCallInstruction;
thread.StepNext(&activeDebugState, workgroup);
if(thread.StepNeedsGpuSampleGatherOp())
return;
if(thread.StepNeedsGpuMathOp())
return;
if(!thread.IsPendingResultPending())
{
const ShaderDebugState &pendingDebugState = thread.GetPendingDebugState();
activeDebugState.nextInstruction = pendingDebugState.nextInstruction;
activeDebugState.flags = pendingDebugState.flags;
activeDebugState.changes.append(pendingDebugState.changes);
thread.ClearPendingDebugState();
if(thread.callstack.size() > prevStackSize)
instOffs = instructionOffsets[GetInstructionForFunction(thread.callstack.back()->function)];
else if(thread.callstack.size() < prevStackSize && funcRet != ~0U)
instOffs = instructionOffsets[funcRet];
FillCallstack(thread, activeDebugState);
if(m_DebugInfo.valid)
{
size_t endOffs = instructionOffsets[thread.nextInstruction - 1];
// append any inlined functions to the top of the stack
InlineData *inlined = m_DebugInfo.lineInline[endOffs];
size_t insertPoint = activeDebugState.callstack.size();
// start with the current scope, it refers to the *inlined* function
if(inlined)
{
const ScopeData *scope = GetScope(endOffs);
// find the function parent of the current scope
while(scope && scope->parent && scope->type == DebugScope::Block)
scope = scope->parent;
activeDebugState.callstack.insert(insertPoint, scope->name);
}
// if this instruction has no scope, don't give it a callstack
if(GetScope(endOffs) == NULL)
{
activeDebugState.callstack.clear();
}
// move to the next inline up on our inline stack. If we reach an actual function
// call, this parent will be NULL as there was no more inlining - the final scope will
// refer to the real function which is already on our stack
while(inlined && inlined->parent)
{
const ScopeData *scope = inlined->scope;
// find the function parent of the current scope
while(scope && scope->parent && scope->type == DebugScope::Block)
scope = scope->parent;
activeDebugState.callstack.insert(insertPoint, scope->name);
inlined = inlined->parent;
}
}
ret->push_back(activeDebugState);
{
activeDebugState.callstack.clear();
activeDebugState.changes.clear();
activeDebugState.flags = ShaderEvents::NoEvent;
activeDebugState.stepIndex = 0;
activeDebugState.nextInstruction = 0;
retireIDs = true;
}
}
}
else
{
thread.StepNext(NULL, workgroup);
if(thread.StepNeedsGpuSampleGatherOp())
return;
if(thread.StepNeedsGpuMathOp())
return;
}
}
// Must be called from the replay manager thread (the debugger thread)
void Debugger::QueueJob(uint32_t lane, rdcarray<ShaderDebugState> *ret)
{
CHECK_DEBUGGER_THREAD();
ThreadState &thread = workgroup[lane];
thread.SetStepQueued();
StepThread(lane, StepThreadMode::RUN_SINGLE_STEP, ret);
}
// Must be called from the replay manager thread (the debugger thread)
void Debugger::ProcessQueuedDebugMessages()
{
@@ -4547,6 +4848,7 @@ void Debugger::AddDebugMessage(MessageCategory c, MessageSeverity sv, MessageSou
SCOPED_LOCK(queuedDebugMessagesLock);
queuedDebugMessages.push_back({c, sv, src, d});
}
}; // namespace rdcspv
#if ENABLED(ENABLE_UNIT_TESTS)
+33 -7
View File
@@ -5554,6 +5554,28 @@ void VulkanReplay::Feedback::Destroy(WrappedVulkan *driver)
void ShaderDebugData::Init(WrappedVulkan *driver, VkDescriptorPool descriptorPool)
{
VkResult vkr = VK_SUCCESS;
VkDescriptorPoolSize descPoolTypes[] = {
{VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, 5 * MAX_QUEUED_OPS},
{VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, MAX_QUEUED_OPS},
{VK_DESCRIPTOR_TYPE_SAMPLER, MAX_QUEUED_OPS},
{VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, MAX_QUEUED_OPS},
{VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, MAX_QUEUED_OPS},
};
VkDescriptorPoolCreateInfo descPoolInfo = {
VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
NULL,
0,
MAX_QUEUED_OPS,
ARRAY_COUNT(descPoolTypes),
&descPoolTypes[0],
};
// create descriptor pool
vkr = driver->vkCreateDescriptorPool(driver->GetDev(), &descPoolInfo, NULL, &DescPool);
CHECK_VKR(driver, vkr);
// should match the enum ShaderDebugBind
CREATE_OBJECT(
DescSetLayout,
@@ -5573,7 +5595,7 @@ void ShaderDebugData::Init(WrappedVulkan *driver, VkDescriptorPool descriptorPoo
// ShaderDebugBind::Sampler
{7, VK_DESCRIPTOR_TYPE_SAMPLER, 1, VK_SHADER_STAGE_FRAGMENT_BIT, NULL},
// ShaderDebugBind::Constants
{8, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_FRAGMENT_BIT, NULL},
{8, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1, VK_SHADER_STAGE_FRAGMENT_BIT, NULL},
// ShaderDebugBind::MathResult
{9, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1,
VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_COMPUTE_BIT, NULL},
@@ -5581,9 +5603,8 @@ void ShaderDebugData::Init(WrappedVulkan *driver, VkDescriptorPool descriptorPoo
CREATE_OBJECT(PipeLayout, DescSetLayout, sizeof(Vec4f) * 6 + sizeof(uint32_t));
CREATE_OBJECT(DescSet, descriptorPool, DescSetLayout);
VkResult vkr = VK_SUCCESS;
for(uint32_t i = 0; i < MAX_QUEUED_OPS; ++i)
CREATE_OBJECT(DescSets[i], DescPool, DescSetLayout);
VkImageCreateInfo imInfo = {
VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
@@ -5704,13 +5725,15 @@ void ShaderDebugData::Init(WrappedVulkan *driver, VkDescriptorPool descriptorPoo
vkr = driver->vkCreateFramebuffer(driver->GetDev(), &fbinfo, NULL, &Framebuffer);
CHECK_VKR(driver, vkr);
MathResult.Create(driver, driver->GetDev(), sizeof(Vec4f) * 4, 1,
VkDeviceSize resultMaxElementSize = sizeof(Vec4f) * 4;
MathResult.Create(driver, driver->GetDev(), resultMaxElementSize, 1,
GPUBuffer::eGPUBufferGPULocal | GPUBuffer::eGPUBufferSSBO);
// don't need to ring this, as we hard-sync for readback anyway
ReadbackBuffer.Create(driver, driver->GetDev(), sizeof(Vec4f) * 4, 1,
uint32_t maxQueuedResults = 128;
ReadbackBuffer.Create(driver, driver->GetDev(), resultMaxElementSize * maxQueuedResults, 1,
GPUBuffer::eGPUBufferReadback);
ConstantsBuffer.Create(driver, driver->GetDev(), 1024, 1, 0);
ConstantsBuffer.Create(driver, driver->GetDev(), 1024, maxQueuedResults, 0);
MathResult.Name("MathResult");
ReadbackBuffer.Name("ShaderReadbackBuffer");
ConstantsBuffer.Name("ShaderConstantsBuffer");
@@ -5740,4 +5763,7 @@ void ShaderDebugData::Destroy(WrappedVulkan *driver)
for(auto it = m_Pipelines.begin(); it != m_Pipelines.end(); it++)
driver->vkDestroyPipeline(driver->GetDev(), it->second, NULL);
if(DescPool != VK_NULL_HANDLE)
driver->vkDestroyDescriptorPool(driver->GetDev(), DescPool, NULL);
}
+6 -1
View File
@@ -277,12 +277,17 @@ enum TexDisplayFlags
struct ShaderDebugData
{
enum
{
MAX_QUEUED_OPS = 128
};
void Init(WrappedVulkan *driver, VkDescriptorPool descriptorPool);
void Destroy(WrappedVulkan *driver);
VkDescriptorPool DescPool = VK_NULL_HANDLE;
VkDescriptorSetLayout DescSetLayout = VK_NULL_HANDLE;
VkPipelineLayout PipeLayout = VK_NULL_HANDLE;
VkDescriptorSet DescSet = VK_NULL_HANDLE;
VkDescriptorSet DescSets[MAX_QUEUED_OPS];
VkPipeline MathPipe[3] = {};
+266 -175
View File
@@ -254,6 +254,11 @@ public:
}
}
}
RDCASSERT(ShaderDebugData::MAX_QUEUED_OPS * mathOpResultByteSize <
m_DebugData.ReadbackBuffer.TotalSize());
RDCASSERT(sampleGatherOpResultsStart +
ShaderDebugData::MAX_QUEUED_OPS * sampleGatherOpResultByteSize <
m_DebugData.ReadbackBuffer.TotalSize());
}
~VulkanAPIWrapper()
@@ -627,13 +632,13 @@ public:
return thread_props[threadIndex][(size_t)prop];
}
bool CalculateSampleGather(rdcspv::ThreadState &lane, rdcspv::Op opcode,
DebugAPIWrapper::TextureType texType, ShaderBindIndex imageBind,
ShaderBindIndex samplerBind, const ShaderVariable &uv,
const ShaderVariable &ddxCalc, const ShaderVariable &ddyCalc,
const ShaderVariable &compare, rdcspv::GatherChannel gatherChannel,
const rdcspv::ImageOperandsAndParamDatas &operands,
ShaderVariable &output) override
bool QueueSampleGather(rdcspv::ThreadState &lane, rdcspv::Op opcode,
DebugAPIWrapper::TextureType texType, const ShaderBindIndex &imageBind,
const ShaderBindIndex &samplerBind, const ShaderVariable &uv,
const ShaderVariable &ddxCalc, const ShaderVariable &ddyCalc,
const ShaderVariable &compare, rdcspv::GatherChannel gatherChannel,
const rdcspv::ImageOperandsAndParamDatas &operands, ShaderVariable &output,
bool &hasResult) override
{
CHECK_DEVICE_THREAD();
ShaderConstParameters constParams = {};
@@ -660,7 +665,7 @@ public:
if(!valid)
return false;
VkMarkerRegion markerRegion("CalculateSampleGather");
VkMarkerRegion markerRegion("QueueSampleGather");
VkBufferView bufferView =
m_pDriver->GetResourceManager()->GetLiveHandle<VkBufferView>(bufferViewDescriptor.view);
@@ -747,11 +752,13 @@ public:
output.value.u32v[0] = viewProps.range.levelCount;
if(viewProps.range.levelCount == VK_REMAINING_MIP_LEVELS)
output.value.u32v[0] = imageProps.mipLevels - viewProps.range.baseMipLevel;
hasResult = true;
return true;
}
case rdcspv::Op::ImageQuerySamples:
{
output.value.u32v[0] = (uint32_t)imageProps.samples;
hasResult = true;
return true;
}
case rdcspv::Op::ImageQuerySize:
@@ -808,6 +815,7 @@ public:
setUintComp(output, 0, uint32_t(size / GetByteSize(1, 1, 1, format, 0)));
}
hasResult = true;
return true;
}
default: break;
@@ -1229,22 +1237,39 @@ public:
return false;
}
VkCommandBuffer cmd = queuedOpCmdBuffer;
if(cmd == VK_NULL_HANDLE)
{
if(StartQueuedOps())
cmd = queuedOpCmdBuffer;
}
if(cmd == VK_NULL_HANDLE)
return false;
if(queueIndex >= ShaderDebugData::MAX_QUEUED_OPS)
{
m_pDriver->AddDebugMessage(MessageCategory::Execution, MessageSeverity::High,
MessageSource::RuntimeWarning, "Too many GPU queued operations");
return false;
}
VkDescriptorImageInfo samplerWriteInfo = {Unwrap(sampler), VK_NULL_HANDLE,
VK_IMAGE_LAYOUT_UNDEFINED};
VkDescriptorImageInfo imageWriteInfo = {VK_NULL_HANDLE, Unwrap(sampleView), layout};
VkDescriptorBufferInfo uniformWriteInfo = {};
m_DebugData.ConstantsBuffer.FillDescriptor(uniformWriteInfo);
const VkDescriptorSet realDescSet = Unwrap(m_DebugData.DescSets[queueIndex++]);
VkWriteDescriptorSet writeSets[] = {
{
VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
NULL,
Unwrap(m_DebugData.DescSet),
realDescSet,
(uint32_t)ShaderDebugBind::Constants,
0,
1,
VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC,
NULL,
&uniformWriteInfo,
NULL,
@@ -1252,7 +1277,7 @@ public:
{
VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
NULL,
Unwrap(m_DebugData.DescSet),
realDescSet,
(uint32_t)constParams.dim,
0,
1,
@@ -1264,7 +1289,7 @@ public:
{
VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
NULL,
Unwrap(m_DebugData.DescSet),
realDescSet,
(uint32_t)ShaderDebugBind::Sampler,
0,
1,
@@ -1319,7 +1344,10 @@ public:
{
// not all textures may be supported for depth, so only update those that are valid
if(m_DebugData.DummyWrites[resetIndex][i].descriptorCount != 0)
{
m_DebugData.DummyWrites[resetIndex][i].dstSet = realDescSet;
writes.push_back(m_DebugData.DummyWrites[resetIndex][i]);
}
}
ObjDisp(dev)->UpdateDescriptorSets(Unwrap(dev), (uint32_t)writes.count(), writes.data(), 0,
@@ -1333,6 +1361,11 @@ public:
else if(sintTex)
resetIndex = 2;
for(size_t i = 0; i < ARRAY_COUNT(m_DebugData.DummyWrites[resetIndex]); i++)
{
if(m_DebugData.DummyWrites[resetIndex][i].descriptorCount != 0)
m_DebugData.DummyWrites[resetIndex][i].dstSet = realDescSet;
}
ObjDisp(dev)->UpdateDescriptorSets(Unwrap(dev),
ARRAY_COUNT(m_DebugData.DummyWrites[resetIndex]),
m_DebugData.DummyWrites[resetIndex], 0, NULL);
@@ -1342,7 +1375,8 @@ public:
ObjDisp(dev)->UpdateDescriptorSets(Unwrap(dev), sampler != VK_NULL_HANDLE ? 3 : 2, writeSets, 0,
NULL);
void *constants = m_DebugData.ConstantsBuffer.Map(NULL, 0);
uint32_t constsOffset = 0;
void *constants = m_DebugData.ConstantsBuffer.Map(&constsOffset, 0);
if(!constants)
return false;
@@ -1350,102 +1384,55 @@ public:
m_DebugData.ConstantsBuffer.Unmap();
{
VkCommandBuffer cmd = m_pDriver->GetNextCmd();
VkClearValue clear = {};
if(cmd == VK_NULL_HANDLE)
return false;
VkRenderPassBeginInfo rpbegin = {
VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
NULL,
Unwrap(m_DebugData.RenderPass),
Unwrap(m_DebugData.Framebuffer),
{{0, 0}, {1, 1}},
1,
&clear,
};
ObjDisp(cmd)->CmdBeginRenderPass(Unwrap(cmd), &rpbegin, VK_SUBPASS_CONTENTS_INLINE);
VkCommandBufferBeginInfo beginInfo = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, NULL,
VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT};
ObjDisp(cmd)->CmdBindPipeline(Unwrap(cmd), VK_PIPELINE_BIND_POINT_GRAPHICS, Unwrap(pipe));
ObjDisp(cmd)->CmdBindDescriptorSets(Unwrap(cmd), VK_PIPELINE_BIND_POINT_GRAPHICS,
Unwrap(m_DebugData.PipeLayout), 0, 1, &realDescSet, 1,
&constsOffset);
VkResult vkr = ObjDisp(cmd)->BeginCommandBuffer(Unwrap(cmd), &beginInfo);
CHECK_VKR(m_pDriver, vkr);
// push uvw/ddx/ddy for the vertex shader
ObjDisp(cmd)->CmdPushConstants(Unwrap(cmd), Unwrap(m_DebugData.PipeLayout), VK_SHADER_STAGE_ALL,
sizeof(Vec4f) * 0, sizeof(Vec4f), &uniformParams.uvwa);
ObjDisp(cmd)->CmdPushConstants(Unwrap(cmd), Unwrap(m_DebugData.PipeLayout), VK_SHADER_STAGE_ALL,
sizeof(Vec4f) * 1, sizeof(Vec3f), &uniformParams.ddx);
ObjDisp(cmd)->CmdPushConstants(Unwrap(cmd), Unwrap(m_DebugData.PipeLayout), VK_SHADER_STAGE_ALL,
sizeof(Vec4f) * 2, sizeof(Vec3f), &uniformParams.ddy);
VkClearValue clear = {};
ObjDisp(cmd)->CmdDraw(Unwrap(cmd), 3, 1, 0, 0);
VkRenderPassBeginInfo rpbegin = {
VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
NULL,
Unwrap(m_DebugData.RenderPass),
Unwrap(m_DebugData.Framebuffer),
{{0, 0}, {1, 1}},
1,
&clear,
};
ObjDisp(cmd)->CmdBeginRenderPass(Unwrap(cmd), &rpbegin, VK_SUBPASS_CONTENTS_INLINE);
ObjDisp(cmd)->CmdEndRenderPass(Unwrap(cmd));
ObjDisp(cmd)->CmdBindPipeline(Unwrap(cmd), VK_PIPELINE_BIND_POINT_GRAPHICS, Unwrap(pipe));
ObjDisp(cmd)->CmdBindDescriptorSets(Unwrap(cmd), VK_PIPELINE_BIND_POINT_GRAPHICS,
Unwrap(m_DebugData.PipeLayout), 0, 1,
UnwrapPtr(m_DebugData.DescSet), 0, NULL);
// push uvw/ddx/ddy for the vertex shader
ObjDisp(cmd)->CmdPushConstants(Unwrap(cmd), Unwrap(m_DebugData.PipeLayout), VK_SHADER_STAGE_ALL,
sizeof(Vec4f) * 0, sizeof(Vec4f), &uniformParams.uvwa);
ObjDisp(cmd)->CmdPushConstants(Unwrap(cmd), Unwrap(m_DebugData.PipeLayout), VK_SHADER_STAGE_ALL,
sizeof(Vec4f) * 1, sizeof(Vec3f), &uniformParams.ddx);
ObjDisp(cmd)->CmdPushConstants(Unwrap(cmd), Unwrap(m_DebugData.PipeLayout), VK_SHADER_STAGE_ALL,
sizeof(Vec4f) * 2, sizeof(Vec3f), &uniformParams.ddy);
ObjDisp(cmd)->CmdDraw(Unwrap(cmd), 3, 1, 0, 0);
ObjDisp(cmd)->CmdEndRenderPass(Unwrap(cmd));
VkBufferImageCopy region = {
0, sizeof(Vec4f), 1, {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}, {0, 0, 0}, {1, 1, 1},
};
ObjDisp(cmd)->CmdCopyImageToBuffer(Unwrap(cmd), Unwrap(m_DebugData.Image),
VK_IMAGE_LAYOUT_GENERAL,
m_DebugData.ReadbackBuffer.UnwrappedBuffer(), 1, &region);
VkBufferMemoryBarrier bufBarrier = {
VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
NULL,
VK_ACCESS_TRANSFER_WRITE_BIT,
VK_ACCESS_HOST_READ_BIT,
VK_QUEUE_FAMILY_IGNORED,
VK_QUEUE_FAMILY_IGNORED,
m_DebugData.ReadbackBuffer.UnwrappedBuffer(),
0,
VK_WHOLE_SIZE,
};
// wait for copy to finish before reading back to host
DoPipelineBarrier(cmd, 1, &bufBarrier);
vkr = ObjDisp(cmd)->EndCommandBuffer(Unwrap(cmd));
CHECK_VKR(m_pDriver, vkr);
m_pDriver->SubmitCmds();
m_pDriver->FlushQ();
}
float *retf = (float *)m_DebugData.ReadbackBuffer.Map(NULL, 0);
if(!retf)
return false;
uint32_t *retu = (uint32_t *)retf;
int32_t *reti = (int32_t *)retf;
// convert full precision results, we did all sampling at 32-bit precision
for(uint8_t c = 0; c < 4; c++)
{
if(VarTypeCompType(output.type) == CompType::Float)
setFloatComp(output, c, retf[c]);
else if(VarTypeCompType(output.type) == CompType::SInt)
setIntComp(output, c, reti[c]);
else
setUintComp(output, c, retu[c]);
}
m_DebugData.ReadbackBuffer.Unmap();
RDCASSERT(sampleGatherOpResultOffset + sampleGatherOpResultByteSize <=
m_DebugData.ReadbackBuffer.TotalSize(),
sampleGatherOpResultOffset, sampleGatherOpResultByteSize,
m_DebugData.ReadbackBuffer.TotalSize());
VkBufferImageCopy region = {
sampleGatherOpResultOffset, sizeof(Vec4f), 1,
{VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}, {0, 0, 0}, {1, 1, 1},
};
sampleGatherOpResultOffset += sampleGatherOpResultByteSize;
ObjDisp(cmd)->CmdCopyImageToBuffer(Unwrap(cmd), Unwrap(m_DebugData.Image),
VK_IMAGE_LAYOUT_GENERAL,
m_DebugData.ReadbackBuffer.UnwrappedBuffer(), 1, &region);
hasResult = false;
return true;
}
virtual bool CalculateMathOp(rdcspv::ThreadState &lane, rdcspv::GLSLstd450 op,
const rdcarray<ShaderVariable> &params, ShaderVariable &output) override
virtual bool QueueCalculateMathOp(rdcspv::GLSLstd450 op,
const rdcarray<ShaderVariable> &params) override
{
CHECK_DEVICE_THREAD();
RDCASSERT(params.size() <= 3, params.size());
@@ -1472,14 +1459,31 @@ public:
}
}
VkCommandBuffer cmd = queuedOpCmdBuffer;
if(cmd == VK_NULL_HANDLE)
{
if(StartQueuedOps())
cmd = queuedOpCmdBuffer;
}
if(cmd == VK_NULL_HANDLE)
return false;
if(queueIndex >= ShaderDebugData::MAX_QUEUED_OPS)
{
m_pDriver->AddDebugMessage(MessageCategory::Execution, MessageSeverity::High,
MessageSource::RuntimeWarning, "Too many GPU queued operations");
return false;
}
VkDescriptorBufferInfo storageWriteInfo = {};
m_DebugData.MathResult.FillDescriptor(storageWriteInfo);
const VkDescriptorSet realDescSet = Unwrap(m_DebugData.DescSets[queueIndex++]);
VkWriteDescriptorSet writeSets[] = {
{
VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
NULL,
Unwrap(m_DebugData.DescSet),
realDescSet,
(uint32_t)ShaderDebugBind::MathResult,
0,
1,
@@ -1494,89 +1498,167 @@ public:
ObjDisp(dev)->UpdateDescriptorSets(Unwrap(dev), 1, writeSets, 0, NULL);
VkMarkerRegion markerRegion("QueueCalculateMathOp");
ObjDisp(cmd)->CmdBindPipeline(Unwrap(cmd), VK_PIPELINE_BIND_POINT_COMPUTE,
Unwrap(m_DebugData.MathPipe[floatSizeIdx]));
uint32_t constsOffset = 0;
ObjDisp(cmd)->CmdBindDescriptorSets(Unwrap(cmd), VK_PIPELINE_BIND_POINT_COMPUTE,
Unwrap(m_DebugData.PipeLayout), 0, 1, &realDescSet, 1,
&constsOffset);
// push the parameters
for(size_t i = 0; i < params.size(); i++)
{
VkCommandBuffer cmd = m_pDriver->GetNextCmd();
if(cmd == VK_NULL_HANDLE)
return false;
VkCommandBufferBeginInfo beginInfo = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, NULL,
VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT};
VkResult vkr = ObjDisp(cmd)->BeginCommandBuffer(Unwrap(cmd), &beginInfo);
CHECK_VKR(m_pDriver, vkr);
ObjDisp(cmd)->CmdBindPipeline(Unwrap(cmd), VK_PIPELINE_BIND_POINT_COMPUTE,
Unwrap(m_DebugData.MathPipe[floatSizeIdx]));
ObjDisp(cmd)->CmdBindDescriptorSets(Unwrap(cmd), VK_PIPELINE_BIND_POINT_COMPUTE,
Unwrap(m_DebugData.PipeLayout), 0, 1,
UnwrapPtr(m_DebugData.DescSet), 0, NULL);
// push the parameters
for(size_t i = 0; i < params.size(); i++)
{
RDCASSERTEQUAL(params[i].type, params[0].type);
double p[4] = {};
memcpy(p, params[i].value.f32v.data(), VarTypeByteSize(params[i].type) * params[i].columns);
ObjDisp(cmd)->CmdPushConstants(Unwrap(cmd), Unwrap(m_DebugData.PipeLayout),
VK_SHADER_STAGE_ALL, uint32_t(sizeof(p) * i), sizeof(p), p);
}
// push the operation afterwards
RDCASSERTEQUAL(params[i].type, params[0].type);
double p[4] = {};
memcpy(p, params[i].value.f32v.data(), VarTypeByteSize(params[i].type) * params[i].columns);
ObjDisp(cmd)->CmdPushConstants(Unwrap(cmd), Unwrap(m_DebugData.PipeLayout),
VK_SHADER_STAGE_ALL, sizeof(Vec4f) * 6, sizeof(uint32_t), &op);
ObjDisp(cmd)->CmdDispatch(Unwrap(cmd), 1, 1, 1);
VkBufferMemoryBarrier bufBarrier = {
VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
NULL,
VK_ACCESS_SHADER_WRITE_BIT,
VK_ACCESS_TRANSFER_READ_BIT,
VK_QUEUE_FAMILY_IGNORED,
VK_QUEUE_FAMILY_IGNORED,
m_DebugData.MathResult.UnwrappedBuffer(),
0,
VK_WHOLE_SIZE,
};
DoPipelineBarrier(cmd, 1, &bufBarrier);
VkBufferCopy bufCopy = {0, 0, 0};
bufCopy.size = sizeof(Vec4f) * 2;
ObjDisp(cmd)->CmdCopyBuffer(Unwrap(cmd), m_DebugData.MathResult.UnwrappedBuffer(),
m_DebugData.ReadbackBuffer.UnwrappedBuffer(), 1, &bufCopy);
bufBarrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
bufBarrier.dstAccessMask = VK_ACCESS_HOST_READ_BIT;
bufBarrier.buffer = m_DebugData.ReadbackBuffer.UnwrappedBuffer();
// wait for copy to finish before reading back to host
DoPipelineBarrier(cmd, 1, &bufBarrier);
vkr = ObjDisp(cmd)->EndCommandBuffer(Unwrap(cmd));
CHECK_VKR(m_pDriver, vkr);
m_pDriver->SubmitCmds();
m_pDriver->FlushQ();
VK_SHADER_STAGE_ALL, uint32_t(sizeof(p) * i), sizeof(p), p);
}
byte *ret = (byte *)m_DebugData.ReadbackBuffer.Map(NULL, 0);
if(!ret)
return false;
// push the operation afterwards
ObjDisp(cmd)->CmdPushConstants(Unwrap(cmd), Unwrap(m_DebugData.PipeLayout), VK_SHADER_STAGE_ALL,
sizeof(Vec4f) * 6, sizeof(uint32_t), &op);
// these two operations change the type of the output
if(op == rdcspv::GLSLstd450::Length || op == rdcspv::GLSLstd450::Distance)
output.columns = 1;
ObjDisp(cmd)->CmdDispatch(Unwrap(cmd), 1, 1, 1);
memcpy(output.value.u32v.data(), ret, VarTypeByteSize(output.type) * output.columns);
VkBufferMemoryBarrier bufBarrier = {
VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
NULL,
VK_ACCESS_SHADER_WRITE_BIT,
VK_ACCESS_TRANSFER_READ_BIT,
VK_QUEUE_FAMILY_IGNORED,
VK_QUEUE_FAMILY_IGNORED,
m_DebugData.MathResult.UnwrappedBuffer(),
0,
VK_WHOLE_SIZE,
};
m_DebugData.ReadbackBuffer.Unmap();
DoPipelineBarrier(cmd, 1, &bufBarrier);
RDCASSERT(mathOpResultOffset + mathOpResultByteSize <= m_DebugData.ReadbackBuffer.TotalSize(),
mathOpResultOffset, mathOpResultByteSize, m_DebugData.ReadbackBuffer.TotalSize());
VkBufferCopy bufCopy = {0, mathOpResultOffset, mathOpResultByteSize};
mathOpResultOffset += mathOpResultByteSize;
ObjDisp(cmd)->CmdCopyBuffer(Unwrap(cmd), m_DebugData.MathResult.UnwrappedBuffer(),
m_DebugData.ReadbackBuffer.UnwrappedBuffer(), 1, &bufCopy);
return true;
}
bool StartQueuedOps()
{
CHECK_DEVICE_THREAD();
RDCASSERTEQUAL(queueIndex, 0);
RDCASSERTEQUAL(queuedOpCmdBuffer, VK_NULL_HANDLE);
RDCASSERTEQUAL(mathOpResultOffset, 0);
RDCASSERTEQUAL(sampleGatherOpResultOffset, 0);
if(queuedOpCmdBuffer != VK_NULL_HANDLE)
return false;
queuedOpCmdBuffer = m_pDriver->GetNextCmd();
if(queuedOpCmdBuffer == VK_NULL_HANDLE)
return false;
VkCommandBuffer cmd = queuedOpCmdBuffer;
VkCommandBufferBeginInfo beginInfo = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, NULL,
VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT};
VkResult vkr = ObjDisp(cmd)->BeginCommandBuffer(Unwrap(cmd), &beginInfo);
CHECK_VKR(m_pDriver, vkr);
sampleGatherOpResultOffset = sampleGatherOpResultsStart;
return true;
}
virtual bool GetQueuedResults(rdcarray<ShaderVariable *> &mathOpResults,
rdcarray<ShaderVariable *> &sampleGatherResults) override
{
CHECK_DEVICE_THREAD();
if(queuedOpCmdBuffer == VK_NULL_HANDLE)
return false;
VkBufferMemoryBarrier bufBarrier = {
VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
NULL,
VK_ACCESS_TRANSFER_WRITE_BIT,
VK_ACCESS_HOST_READ_BIT,
VK_QUEUE_FAMILY_IGNORED,
VK_QUEUE_FAMILY_IGNORED,
m_DebugData.ReadbackBuffer.UnwrappedBuffer(),
0,
VK_WHOLE_SIZE,
};
VkCommandBuffer cmd = queuedOpCmdBuffer;
if(cmd == VK_NULL_HANDLE)
return false;
// wait for copy to finish before reading back to host
DoPipelineBarrier(cmd, 1, &bufBarrier);
VkResult vkr = ObjDisp(cmd)->EndCommandBuffer(Unwrap(cmd));
CHECK_VKR(m_pDriver, vkr);
m_pDriver->SubmitCmds();
m_pDriver->FlushQ();
queueIndex = 0;
queuedOpCmdBuffer = VK_NULL_HANDLE;
mathOpResultOffset = 0;
sampleGatherOpResultOffset = 0;
byte *gpuResults = (byte *)m_DebugData.ReadbackBuffer.Map(NULL, 0);
if(!gpuResults)
return false;
uintptr_t bufferEnd = (uintptr_t)(gpuResults + m_DebugData.ReadbackBuffer.TotalSize());
byte *gpuMathOpResults = gpuResults;
for(ShaderVariable *result : mathOpResults)
{
size_t countBytes = VarTypeByteSize(result->type) * result->columns;
RDCASSERT((uintptr_t)gpuMathOpResults + countBytes <= bufferEnd, (uintptr_t)gpuMathOpResults,
countBytes, bufferEnd);
RDCASSERT(countBytes <= mathOpResultByteSize, countBytes, mathOpResultByteSize);
memcpy(result->value.u32v.data(), gpuMathOpResults, countBytes);
gpuMathOpResults += mathOpResultByteSize;
}
byte *gpuSampleGatherOpResults = gpuResults + sampleGatherOpResultsStart;
for(ShaderVariable *result : sampleGatherResults)
{
float *retf = (float *)gpuSampleGatherOpResults;
uint32_t *retu = (uint32_t *)gpuSampleGatherOpResults;
int32_t *reti = (int32_t *)gpuSampleGatherOpResults;
size_t countBytes = 16;
RDCASSERT((uintptr_t)gpuSampleGatherOpResults + countBytes <= bufferEnd,
(uintptr_t)gpuSampleGatherOpResults, countBytes, bufferEnd);
RDCASSERT(countBytes <= sampleGatherOpResultByteSize, countBytes, sampleGatherOpResultByteSize);
// convert full precision results, we did all sampling at 32-bit precision
ShaderVariable &output = *result;
for(uint8_t c = 0; c < 4; c++)
{
if(VarTypeCompType(output.type) == CompType::Float)
setFloatComp(output, c, retf[c]);
else if(VarTypeCompType(output.type) == CompType::SInt)
setIntComp(output, c, reti[c]);
else
setUintComp(output, c, retu[c]);
}
gpuSampleGatherOpResults += sampleGatherOpResultByteSize;
}
m_DebugData.ReadbackBuffer.Unmap();
return true;
}
virtual bool QueuedOpsHasSpace() override { return queueIndex < ShaderDebugData::MAX_QUEUED_OPS; }
// global over all threads
std::unordered_map<ShaderBuiltin, ShaderVariable> global_builtins;
@@ -1634,6 +1716,15 @@ private:
std::map<ShaderBindIndex, bytebuf> bufferCache;
VkCommandBuffer queuedOpCmdBuffer = VK_NULL_HANDLE;
VkDeviceSize mathOpResultOffset = 0;
VkDeviceSize sampleGatherOpResultOffset = 0;
uint32_t queueIndex = 0;
const VkDeviceSize mathOpResultByteSize = sizeof(Vec4f) * 2;
const VkDeviceSize sampleGatherOpResultByteSize = sizeof(Vec4f);
const VkDeviceSize sampleGatherOpResultsStart =
ShaderDebugData::MAX_QUEUED_OPS * mathOpResultByteSize;
struct ImageData
{
uint32_t width = 0, height = 0, depth = 0;
@@ -6537,7 +6628,7 @@ rdcarray<ShaderDebugState> VulkanReplay::ContinueDebug(ShaderDebugger *debugger)
m_ShaderDebugData.DummyWrites[fmt][dim].descriptorCount = 1;
m_ShaderDebugData.DummyWrites[fmt][dim].descriptorType = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE;
m_ShaderDebugData.DummyWrites[fmt][dim].dstBinding = uint32_t(dim + 1);
m_ShaderDebugData.DummyWrites[fmt][dim].dstSet = Unwrap(m_ShaderDebugData.DescSet);
m_ShaderDebugData.DummyWrites[fmt][dim].dstSet = VK_NULL_HANDLE;
m_ShaderDebugData.DummyWrites[fmt][dim].pImageInfo =
&m_ShaderDebugData.DummyImageInfos[fmt][dim];
}
@@ -6548,7 +6639,7 @@ rdcarray<ShaderDebugState> VulkanReplay::ContinueDebug(ShaderDebugger *debugger)
m_ShaderDebugData.DummyWrites[fmt][5].descriptorCount = 1;
m_ShaderDebugData.DummyWrites[fmt][5].descriptorType = VK_DESCRIPTOR_TYPE_SAMPLER;
m_ShaderDebugData.DummyWrites[fmt][5].dstBinding = (uint32_t)ShaderDebugBind::Sampler;
m_ShaderDebugData.DummyWrites[fmt][5].dstSet = Unwrap(m_ShaderDebugData.DescSet);
m_ShaderDebugData.DummyWrites[fmt][5].dstSet = VK_NULL_HANDLE;
m_ShaderDebugData.DummyWrites[fmt][5].pImageInfo = &m_ShaderDebugData.DummyImageInfos[fmt][5];
if(m_TexRender.DummyBufferView[fmt] != VK_NULL_HANDLE)
@@ -6557,7 +6648,7 @@ rdcarray<ShaderDebugState> VulkanReplay::ContinueDebug(ShaderDebugger *debugger)
m_ShaderDebugData.DummyWrites[fmt][6].descriptorCount = 1;
m_ShaderDebugData.DummyWrites[fmt][6].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER;
m_ShaderDebugData.DummyWrites[fmt][6].dstBinding = (uint32_t)ShaderDebugBind::Buffer;
m_ShaderDebugData.DummyWrites[fmt][6].dstSet = Unwrap(m_ShaderDebugData.DescSet);
m_ShaderDebugData.DummyWrites[fmt][6].dstSet = VK_NULL_HANDLE;
m_ShaderDebugData.DummyWrites[fmt][6].pTexelBufferView =
UnwrapPtr(m_TexRender.DummyBufferView[fmt]);
}