From 94d7f4365dc22ba795056deca17f6cd09dabb935 Mon Sep 17 00:00:00 2001 From: Jake Turner Date: Thu, 14 Aug 2025 15:14:53 +0100 Subject: [PATCH] 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. --- .../driver/shaders/spirv/spirv_debug.cpp | 236 +++++--- renderdoc/driver/shaders/spirv/spirv_debug.h | 221 ++++++- .../shaders/spirv/spirv_debug_glsl450.cpp | 11 +- .../shaders/spirv/spirv_debug_setup.cpp | 560 ++++++++++++++---- renderdoc/driver/vulkan/vk_debug.cpp | 40 +- renderdoc/driver/vulkan/vk_replay.h | 7 +- renderdoc/driver/vulkan/vk_shaderdebug.cpp | 441 ++++++++------ 7 files changed, 1114 insertions(+), 402 deletions(-) diff --git a/renderdoc/driver/shaders/spirv/spirv_debug.cpp b/renderdoc/driver/shaders/spirv/spirv_debug.cpp index 7db1cc2f1..6b3f994af 100644 --- a/renderdoc/driver/shaders/spirv/spirv_debug.cpp +++ b/renderdoc/driver/shaders/spirv/spirv_debug.cpp @@ -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 &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 &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 &workgroup) return false; } -void ThreadState::StepNext(ShaderDebugState *state, const rdcarray &workgroup, - const rdcarray &activeMask) +void ThreadState::StepNext(ShaderDebugState *state, const rdcarray &workgroup) { m_State = state; @@ -2289,16 +2292,16 @@ void ThreadState::StepNext(ShaderDebugState *state, const rdcarray for(uint8_t c = 0; c < var.columns; c++) { #undef _IMPL -#define _IMPL(I, S, U) \ - if(comp(b, c) != 0) \ - { \ - comp(var, c) /= comp(b, c); \ - } \ - else \ - { \ - comp(var, c) = 0; \ - if(m_State) \ - m_State->flags |= ShaderEvents::GeneratedNanOrInf; \ +#define _IMPL(I, S, U) \ + if(comp(b, c) != 0) \ + { \ + comp(var, c) /= comp(b, c); \ + } \ + else \ + { \ + comp(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 for(uint8_t c = 0; c < var.columns; c++) { #undef _IMPL -#define _IMPL(I, S, U) \ - if(comp(b, c) != 0) \ - { \ - comp(var, c) /= comp(b, c); \ - } \ - else \ - { \ - comp(var, c) = 0; \ - if(m_State) \ - m_State->flags |= ShaderEvents::GeneratedNanOrInf; \ +#define _IMPL(I, S, U) \ + if(comp(b, c) != 0) \ + { \ + comp(var, c) /= comp(b, c); \ + } \ + else \ + { \ + comp(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 for(uint8_t c = 0; c < var.columns; c++) { #undef _IMPL -#define _IMPL(I, S, U) \ - if(comp(b, c) != 0) \ - { \ - comp(var, c) %= comp(b, c); \ - } \ - else \ - { \ - comp(var, c) = 0; \ - if(m_State) \ - m_State->flags |= ShaderEvents::GeneratedNanOrInf; \ +#define _IMPL(I, S, U) \ + if(comp(b, c) != 0) \ + { \ + comp(var, c) %= comp(b, c); \ + } \ + else \ + { \ + comp(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 for(uint8_t c = 0; c < var.columns; c++) { #undef _IMPL -#define _IMPL(I, S, U) \ - if(comp(b, c) != 0) \ - { \ - S op1 = comp(var, c); \ - S op2 = comp(b, c); \ - S tmp = op1 % op2; \ - if(opdata.op == Op::SRem) \ - comp(var, c) = op1 < 0 ? (S)-ABS(tmp) : (S)ABS(tmp); \ - else \ - comp(var, c) = op2 < 0 ? (S)-ABS(tmp) : (S)ABS(tmp); \ - } \ - else \ - { \ - comp(var, c) = 0; \ - if(m_State) \ - m_State->flags |= ShaderEvents::GeneratedNanOrInf; \ +#define _IMPL(I, S, U) \ + if(comp(b, c) != 0) \ + { \ + S op1 = comp(var, c); \ + S op2 = comp(b, c); \ + S tmp = op1 % op2; \ + if(opdata.op == Op::SRem) \ + comp(var, c) = op1 < 0 ? (S)-ABS(tmp) : (S)ABS(tmp); \ + else \ + comp(var, c) = op2 < 0 ? (S)-ABS(tmp) : (S)ABS(tmp); \ + } \ + else \ + { \ + comp(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 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 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 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 } 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 // 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 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 // 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 ¶mVars, + 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(); +}; diff --git a/renderdoc/driver/shaders/spirv/spirv_debug.h b/renderdoc/driver/shaders/spirv/spirv_debug.h index ce5c572a9..ee329b60c 100644 --- a/renderdoc/driver/shaders/spirv/spirv_debug.h +++ b/renderdoc/driver/shaders/spirv/spirv_debug.h @@ -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 ¶ms, 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 ¶ms) = 0; + virtual bool GetQueuedResults(rdcarray &mathOpResults, + rdcarray &sampleGatherResults) = 0; + virtual bool QueuedOpsHasSpace() = 0; }; typedef ShaderVariable (*ExtInstImpl)(ThreadState &, uint32_t, const rdcarray &); @@ -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 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 &workgroup, - const rdcarray &activeMask); + void StepNext(ShaderDebugState *state, const rdcarray &workgroup); enum DerivDir { @@ -205,6 +267,8 @@ struct ThreadState uint32_t currentInstruction; uint32_t nextInstruction; + rdcarray 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 ¶mVars, + 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 &arguments); void SetDst(Id id, const ShaderVariable &val); @@ -280,7 +425,21 @@ private: void ExecuteMemoryBarrier(Id semanticsId); static bool WorkgroupIsDiverged(const rdcarray &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 *ret); + void StepThread(uint32_t lane, StepThreadMode stepMode, rdcarray *ret); + void InternalStepThread(uint32_t lane, rdcarray *ret); + void ProcessQueuedDebugMessages(); + void ProcessQueuedOps(); + void ProcessQueuedGpuMathOps(); + void ProcessQueuedGpuSampleGatherOps(); + void SyncPendingGpuOps(); + void SyncPendingLanes(); mutable Threading::CriticalSection queuedDebugMessagesLock; mutable rdcarray queuedDebugMessages; + rdcarray queuedGpuMathOps; + rdcarray queuedGpuSampleGatherOps; + rdcarray *shaderChangesReturn; + + bool retireIDs = true; + ShaderDebugState activeDebugState; + rdcarray pendingLanes; + rdcarray pendingGpuMathsOpsResults; + rdcarray 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); diff --git a/renderdoc/driver/shaders/spirv/spirv_debug_glsl450.cpp b/renderdoc/driver/shaders/spirv/spirv_debug_glsl450.cpp index 17aefaf41..503377336 100644 --- a/renderdoc/driver/shaders/spirv/spirv_debug_glsl450.cpp +++ b/renderdoc/driver/shaders/spirv/spirv_debug_glsl450.cpp @@ -1228,14 +1228,21 @@ ShaderVariable NClamp(ThreadState &state, uint32_t, const rdcarray ¶ms) ShaderVariable GPUOp(ThreadState &state, uint32_t instruction, const rdcarray ¶ms) { + if(state.IsPendingResultReady()) + return state.GetPendingResult(); + rdcarray 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; } diff --git a/renderdoc/driver/shaders/spirv/spirv_debug_setup.cpp b/renderdoc/driver/shaders/spirv/spirv_debug_setup.cpp index 210be3cb3..0960bdc55 100644 --- a/renderdoc/driver/shaders/spirv/spirv_debug_setup.cpp +++ b/renderdoc/driver/shaders/spirv/spirv_debug_setup.cpp @@ -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 Debugger::ContinueDebug() ThreadState &active = GetActiveLane(); rdcarray ret; + shaderChangesReturn = NULL; // initialise the first ShaderDebugState if we haven't stepped yet if(steps == 0) @@ -2617,15 +2627,16 @@ rdcarray Debugger::ContinueDebug() if(active.Finished()) return ret; - rdcarray 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 Debugger::ContinueDebug() TangleGroup &tangles = controlFlow.GetTangles(); bool anyActiveThreads = false; + for(const Tangle &tangle : tangles) + { + if(!tangle.IsAliveActive()) + continue; + + rdcarray 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 &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 &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 &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 Debugger::ContinueDebug() RDCASSERTNOTEQUAL(newConvergeInstruction, INVALID_EXECUTION_POINT); } if(newConvergeInstruction == threadConvergeInstruction) - ++countIdentialConvergePointThreads; + ++countIdenticalConvergePointThreads; } uint32_t threadFunctionReturnPoint = thread.GetFunctionReturnPoint(); @@ -2816,14 +2798,17 @@ rdcarray 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 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 *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 *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 *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) diff --git a/renderdoc/driver/vulkan/vk_debug.cpp b/renderdoc/driver/vulkan/vk_debug.cpp index 0186b6fe7..3efdc9f91 100644 --- a/renderdoc/driver/vulkan/vk_debug.cpp +++ b/renderdoc/driver/vulkan/vk_debug.cpp @@ -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); } diff --git a/renderdoc/driver/vulkan/vk_replay.h b/renderdoc/driver/vulkan/vk_replay.h index 2f2406984..d2637949a 100644 --- a/renderdoc/driver/vulkan/vk_replay.h +++ b/renderdoc/driver/vulkan/vk_replay.h @@ -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] = {}; diff --git a/renderdoc/driver/vulkan/vk_shaderdebug.cpp b/renderdoc/driver/vulkan/vk_shaderdebug.cpp index 276718a84..7e5e6986e 100644 --- a/renderdoc/driver/vulkan/vk_shaderdebug.cpp +++ b/renderdoc/driver/vulkan/vk_shaderdebug.cpp @@ -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(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, ®ion); - - 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, ®ion); + hasResult = false; return true; } - virtual bool CalculateMathOp(rdcspv::ThreadState &lane, rdcspv::GLSLstd450 op, - const rdcarray ¶ms, ShaderVariable &output) override + virtual bool QueueCalculateMathOp(rdcspv::GLSLstd450 op, + const rdcarray ¶ms) 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 &mathOpResults, + rdcarray &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 global_builtins; @@ -1634,6 +1716,15 @@ private: std::map 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 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 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 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]); }