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]); }