diff --git a/renderdoc/driver/d3d12/d3d12_shaderdebug.cpp b/renderdoc/driver/d3d12/d3d12_shaderdebug.cpp index 17bbb937f..e9d67da04 100644 --- a/renderdoc/driver/d3d12/d3d12_shaderdebug.cpp +++ b/renderdoc/driver/d3d12/d3d12_shaderdebug.cpp @@ -2357,6 +2357,7 @@ ShaderDebugTrace *D3D12Replay::DebugVertex(uint32_t eventId, uint32_t vertid, ui const rdcarray &dxilInputs = debugger->GetDXILEntryPointInputs(); + globalState.subgroupSize = buf->subgroupSize; for(uint32_t t = 0; t < buf->subgroupSize; t++) { DXDebug::VSLaneData *lane = (DXDebug::VSLaneData *)(initialData.data() + laneDataOffset + @@ -2369,6 +2370,7 @@ ShaderDebugTrace *D3D12Replay::DebugVertex(uint32_t eventId, uint32_t vertid, ui if(lane->active) RDCASSERTEQUAL(lane->laneIndex, t); workgroupProperties[t][DXILDebug::ThreadProperty::Active] = lane->active; + workgroupProperties[t][DXILDebug::ThreadProperty::SubgroupIdx] = t; rdcarray inputDatas; for(int i = 0; i < fetcher.inputs.count(); i++) @@ -2404,6 +2406,8 @@ ShaderDebugTrace *D3D12Replay::DebugVertex(uint32_t eventId, uint32_t vertid, ui data += inputElement.numwords * sizeof(uint32_t); } + state.m_Builtins[ShaderBuiltin::IndexInSubgroup] = ShaderVariable(rdcstr(), t, 0U, 0U, 0U); + for(const DXILDebug::InputData &input : inputDatas) { int32_t *rawout = NULL; @@ -3159,6 +3163,7 @@ ShaderDebugTrace *D3D12Replay::DebugPixel(uint32_t eventId, uint32_t x, uint32_t DXILDebug::FetchConstantBufferData(m_pDevice, dxbc->GetDXILByteCode(), rs.graphics, refl, globalState, ret->sourceVars); + globalState.subgroupSize = hit->subgroupSize; for(uint32_t q = 0; q < hit->subgroupSize; q++) { DXDebug::PSLaneData *lane = (DXDebug::PSLaneData *)data; @@ -3170,6 +3175,7 @@ ShaderDebugTrace *D3D12Replay::DebugPixel(uint32_t eventId, uint32_t x, uint32_t workgroupProperties[q][DXILDebug::ThreadProperty::Helper] = lane->isHelper; workgroupProperties[q][DXILDebug::ThreadProperty::QuadLane] = lane->quadLane; workgroupProperties[q][DXILDebug::ThreadProperty::QuadId] = lane->quadId; + workgroupProperties[q][DXILDebug::ThreadProperty::SubgroupIdx] = q; data += sizeof(DXDebug::PSLaneData); @@ -3212,6 +3218,7 @@ ShaderDebugTrace *D3D12Replay::DebugPixel(uint32_t eventId, uint32_t x, uint32_t data += inputElement.numwords * sizeof(uint32_t); } + state.m_Builtins[ShaderBuiltin::IndexInSubgroup] = ShaderVariable(rdcstr(), q, 0U, 0U, 0U); state.m_Builtins[ShaderBuiltin::PrimitiveIndex] = ShaderVariable(rdcstr(), lane->primitive, 0U, 0U, 0U); state.m_Builtins[ShaderBuiltin::MSAACoverage] = @@ -3433,6 +3440,7 @@ ShaderDebugTrace *D3D12Replay::DebugThread(uint32_t eventId, }; uint32_t numThreads = 1; + uint32_t subgroupSize = 1; uint32_t activeLaneIndex = 0; rdcflatmap globalBuiltins; @@ -3594,6 +3602,7 @@ ShaderDebugTrace *D3D12Replay::DebugThread(uint32_t eventId, else activeLaneIndex = buf->laneIndex; + subgroupSize = buf->subgroupSize; for(uint32_t t = 0; t < buf->subgroupSize; t++) { DXDebug::CSLaneData *value = (DXDebug::CSLaneData *)(initialData.data() + laneDataOffset + @@ -3616,6 +3625,7 @@ ShaderDebugTrace *D3D12Replay::DebugThread(uint32_t eventId, activeLaneIndex = lane; workgroupProperties[lane][DXILDebug::ThreadProperty::Active] = value->active; + workgroupProperties[lane][DXILDebug::ThreadProperty::SubgroupIdx] = t; RDCASSERT(value->active); threadBuiltins[lane][ShaderBuiltin::DispatchThreadIndex] = @@ -3629,6 +3639,8 @@ ShaderDebugTrace *D3D12Replay::DebugThread(uint32_t eventId, value->threadid[2] * threadDim[0] * threadDim[1] + value->threadid[1] * threadDim[0] + value->threadid[0], 0U, 0U, 0U); + threadBuiltins[lane][ShaderBuiltin::IndexInSubgroup] = + ShaderVariable(rdcstr(), value->laneIndex, 0U, 0U, 0U); } if(activeLaneIndex == ~0U) @@ -3658,6 +3670,9 @@ ShaderDebugTrace *D3D12Replay::DebugThread(uint32_t eventId, groupid[1] * threadDim[1] + ty); RDCASSERTEQUAL(thread_builtins[ShaderBuiltin::DispatchThreadIndex].value.u32v[2], groupid[2] * threadDim[2] + tz); + + RDCASSERTEQUAL(thread_builtins[ShaderBuiltin::IndexInSubgroup].value.u32v[0], + i % buf->subgroupSize); } else { @@ -3668,7 +3683,12 @@ ShaderDebugTrace *D3D12Replay::DebugThread(uint32_t eventId, ShaderVariable(rdcstr(), tx, ty, tz, 0U); thread_builtins[ShaderBuiltin::GroupFlatIndex] = ShaderVariable( rdcstr(), tz * threadDim[0] * threadDim[1] + ty * threadDim[0] + tx, 0U, 0U, 0U); + // tightly wrap subgroups, this is likely not how the GPU actually assigns them + thread_builtins[ShaderBuiltin::IndexInSubgroup] = + ShaderVariable(rdcstr(), i % buf->subgroupSize, 0U, 0U, 0U); workgroupProperties[i][DXILDebug::ThreadProperty::Active] = 1; + workgroupProperties[i][DXILDebug::ThreadProperty::SubgroupIdx] = + i % buf->subgroupSize; } i++; @@ -3751,6 +3771,7 @@ ShaderDebugTrace *D3D12Replay::DebugThread(uint32_t eventId, DXILDebug::GlobalState &globalState = debugger->GetGlobalState(); globalState.builtins.swap(globalBuiltins); + globalState.subgroupSize = subgroupSize; for(uint32_t i = 0; i < threadBuiltins.size(); i++) debugger->GetLane(i).m_Builtins.swap(threadBuiltins[i]); diff --git a/renderdoc/driver/shaders/dxil/dxil_bytecode.h b/renderdoc/driver/shaders/dxil/dxil_bytecode.h index ddd64f21c..459b88f95 100644 --- a/renderdoc/driver/shaders/dxil/dxil_bytecode.h +++ b/renderdoc/driver/shaders/dxil/dxil_bytecode.h @@ -620,6 +620,33 @@ enum class AtomicBinOpCode : uint32_t Invalid // Must be last. }; +// WaveOp / WavePrefixOp +enum class WaveOpCode : uint32_t +{ + Sum = 0, + Product = 1, + Min = 2, + Max = 3, +}; + +// WaveBitOp +enum class WaveBitOpCode : uint32_t +{ + And = 0, + Or = 1, + Xor = 2, +}; + +// WaveMultiPrefixOp +enum class WaveMultiPrefixOpCode : uint32_t +{ + Sum = 0, + And = 1, + Or = 2, + Xor = 3, + Product = 4, +}; + enum class QuadOpKind : uint32_t { ReadAcrossX = 0, // returns the value from the other lane in the quad in the diff --git a/renderdoc/driver/shaders/dxil/dxil_debug.cpp b/renderdoc/driver/shaders/dxil/dxil_debug.cpp index d2a8db5d9..e4a0e8149 100644 --- a/renderdoc/driver/shaders/dxil/dxil_debug.cpp +++ b/renderdoc/driver/shaders/dxil/dxil_debug.cpp @@ -1655,7 +1655,8 @@ bool IsNopInstruction(const Instruction &inst) } bool ThreadState::ExecuteInstruction(DebugAPIWrapper *apiWrapper, - const rdcarray &workgroup) + const rdcarray &workgroup, + const rdcarray &activeMask) { m_CurrentInstruction = m_FunctionInfo->function->instructions[m_FunctionInstructionIdx]; const Instruction &inst = *m_CurrentInstruction; @@ -3529,6 +3530,92 @@ bool ThreadState::ExecuteInstruction(DebugAPIWrapper *apiWrapper, result.value.u64v[0] = a.value.u64v[0]; break; } + // Wave/Subgroup Operations + case DXOp::WaveGetLaneIndex: + { + // SV_PrimitiveID + result.value.u32v[0] = m_SubgroupIdx; + break; + } + case DXOp::WaveActiveOp: + { + // WaveActiveOp(value,op,sop) + + ShaderVariable arg; + RDCASSERT(GetShaderVariable(inst.args[2], opCode, dxOpCode, arg)); + WaveOpCode waveOpCode = (WaveOpCode)arg.value.u32v[0]; + + RDCASSERT(GetShaderVariable(inst.args[3], opCode, dxOpCode, arg)); + bool isUnsigned = (arg.value.u32v[0] != 0); + + // determine active lane indices in our subgroup + rdcarray activeLanes; + + const uint32_t firstLaneInSub = m_WorkgroupIndex - m_SubgroupIdx; + for(uint32_t lane = firstLaneInSub; lane < firstLaneInSub + m_GlobalState.subgroupSize; + lane++) + { + // wave operations exclude helpers + if(activeMask[lane]) + { + if(!m_GlobalState.waveOpsIncludeHelpers && workgroup[lane - firstLaneInSub].m_Helper) + continue; + activeLanes.push_back(lane - firstLaneInSub); + } + } + + ShaderVariable accum; + RDCASSERT(GetShaderVariable(inst.args[1], opCode, dxOpCode, accum)); + + // set the identity + switch(waveOpCode) + { + default: + RDCERR("Unhandled wave opcode"); + accum.value = {}; + break; + case WaveOpCode::Sum: accum.value = {}; break; + } + + for(uint32_t lane : activeLanes) + { + ShaderVariable x; + RDCASSERT(workgroup[lane].GetShaderVariable(inst.args[1], opCode, dxOpCode, x)); + + switch(waveOpCode) + { + default: RDCERR("Unhandled wave opcode"); break; + case WaveOpCode::Sum: + { + for(uint8_t c = 0; c < x.columns; c++) + { + if(isUnsigned) + { +#undef _IMPL +#define _IMPL(I, S, U) comp(accum, c) = comp(accum, c) + comp(x, c) + IMPL_FOR_INT_TYPES_FOR_TYPE(_IMPL, x.type); + } + else + { +#undef _IMPL +#define _IMPL(I, S, U) comp(accum, c) = comp(accum, c) + comp(x, c) + IMPL_FOR_INT_TYPES_FOR_TYPE(_IMPL, x.type); + +#undef _IMPL +#define _IMPL(T) comp(accum, c) = comp(accum, c) + comp(x, c) + + IMPL_FOR_FLOAT_TYPES_FOR_TYPE(_IMPL, x.type); + } + } + break; + } + } + } + + result = accum; + + break; + } // Quad Operations case DXOp::QuadReadLaneAt: case DXOp::QuadOp: @@ -3818,7 +3905,6 @@ bool ThreadState::ExecuteInstruction(DebugAPIWrapper *apiWrapper, // Wave/Subgroup Operations case DXOp::WaveIsFirstLane: - case DXOp::WaveGetLaneIndex: case DXOp::WaveGetLaneCount: case DXOp::WaveAnyTrue: case DXOp::WaveAllTrue: @@ -3826,7 +3912,6 @@ bool ThreadState::ExecuteInstruction(DebugAPIWrapper *apiWrapper, case DXOp::WaveActiveBallot: case DXOp::WaveReadLaneAt: case DXOp::WaveReadLaneFirst: - case DXOp::WaveActiveOp: case DXOp::WaveActiveBit: case DXOp::WavePrefixOp: case DXOp::WaveAllBitCount: @@ -5321,7 +5406,7 @@ void ThreadState::StepOverNopInstructions() } void ThreadState::StepNext(ShaderDebugState *state, DebugAPIWrapper *apiWrapper, - const rdcarray &workgroup) + const rdcarray &workgroup, const rdcarray &activeMask) { m_State = state; @@ -5359,7 +5444,7 @@ void ThreadState::StepNext(ShaderDebugState *state, DebugAPIWrapper *apiWrapper, } } } - ExecuteInstruction(apiWrapper, workgroup); + ExecuteInstruction(apiWrapper, workgroup, activeMask); m_State = NULL; } @@ -8360,6 +8445,7 @@ void Debugger::InitialiseWorkgroup(const rdcarray &workgroupPr } lane.m_Dead = workgroupProperties[i][ThreadProperty::Active] == 0; + lane.m_SubgroupIdx = workgroupProperties[i][ThreadProperty::SubgroupIdx]; } // find quad neighbours @@ -8486,12 +8572,12 @@ rdcarray Debugger::ContinueDebug(DebugAPIWrapper *apiWrapper) { hasDebugState = true; state.stepIndex = m_Steps; - thread.StepNext(&state, apiWrapper, m_Workgroup); + thread.StepNext(&state, apiWrapper, m_Workgroup, activeMask); m_Steps++; } else { - thread.StepNext(NULL, apiWrapper, m_Workgroup); + thread.StepNext(NULL, apiWrapper, m_Workgroup, activeMask); } } } diff --git a/renderdoc/driver/shaders/dxil/dxil_debug.h b/renderdoc/driver/shaders/dxil/dxil_debug.h index a0a6396fd..265785c8c 100644 --- a/renderdoc/driver/shaders/dxil/dxil_debug.h +++ b/renderdoc/driver/shaders/dxil/dxil_debug.h @@ -222,13 +222,14 @@ struct ThreadState void EnterFunction(const DXIL::Function *function, const rdcarray &args); void EnterEntryPoint(const DXIL::Function *function, ShaderDebugState *state); void StepNext(ShaderDebugState *state, DebugAPIWrapper *apiWrapper, - const rdcarray &workgroup); + const rdcarray &workgroup, const rdcarray &activeMask); void StepOverNopInstructions(); bool Finished() const; bool InUniformBlock() const; - bool ExecuteInstruction(DebugAPIWrapper *apiWrapper, const rdcarray &workgroup); + bool ExecuteInstruction(DebugAPIWrapper *apiWrapper, const rdcarray &workgroup, + const rdcarray &activeMask); void MarkResourceAccess(const rdcstr &name, const ResourceReferenceInfo &resRefInfo, bool directAccess, const ShaderDirectAccess &access, @@ -347,6 +348,8 @@ struct ThreadState rdcfixedarray m_QuadNeighbours = {~0U, ~0U, ~0U, ~0U}; // index in the workgroup uint32_t m_WorkgroupIndex = ~0U; + // index in the subgroup + uint32_t m_SubgroupIdx = ~0U; bool m_Dead = false; bool m_Ended = false; bool m_Helper = false; @@ -357,6 +360,8 @@ struct GlobalState GlobalState() = default; ~GlobalState(); BuiltinInputs builtins; + uint32_t subgroupSize = 1; + bool waveOpsIncludeHelpers = false; struct ViewFmt { @@ -518,6 +523,7 @@ enum class ThreadProperty : uint32_t QuadId, QuadLane, Active, + SubgroupIdx, Count, }; diff --git a/renderdoc/driver/shaders/dxil/dxil_reflect.cpp b/renderdoc/driver/shaders/dxil/dxil_reflect.cpp index d42d93420..5b6828001 100644 --- a/renderdoc/driver/shaders/dxil/dxil_reflect.cpp +++ b/renderdoc/driver/shaders/dxil/dxil_reflect.cpp @@ -28,6 +28,8 @@ #include "dxil_bytecode.h" #include "dxil_common.h" +RDOC_EXTERN_CONFIG(bool, D3D_Hack_EnableGroups); + namespace DXIL { enum class ResourcesTag @@ -1868,6 +1870,12 @@ rdcstr Program::GetDebugStatus() "Only supported when debugging pixel shaders dx.op call `%s` %s", callFunc->name.c_str(), ToStr(dxOpCode).c_str()); continue; + case DXOp::WaveGetLaneIndex: + case DXOp::WaveActiveOp: + if(!D3D_Hack_EnableGroups()) + return StringFormat::Fmt("Unsupported dx.op call `%s` %s", callFunc->name.c_str(), + ToStr(dxOpCode).c_str()); + continue; case DXOp::TempRegLoad: case DXOp::TempRegStore: case DXOp::MinPrecXRegLoad: @@ -1889,7 +1897,6 @@ rdcstr Program::GetDebugStatus() case DXOp::OutputControlPointID: case DXOp::CycleCounterLegacy: case DXOp::WaveIsFirstLane: - case DXOp::WaveGetLaneIndex: case DXOp::WaveGetLaneCount: case DXOp::WaveAnyTrue: case DXOp::WaveAllTrue: @@ -1897,7 +1904,6 @@ rdcstr Program::GetDebugStatus() case DXOp::WaveActiveBallot: case DXOp::WaveReadLaneAt: case DXOp::WaveReadLaneFirst: - case DXOp::WaveActiveOp: case DXOp::WaveActiveBit: case DXOp::WavePrefixOp: case DXOp::WaveAllBitCount: diff --git a/renderdoc/driver/vulkan/vk_shaderdebug.cpp b/renderdoc/driver/vulkan/vk_shaderdebug.cpp index 505aa1149..48357c3f4 100644 --- a/renderdoc/driver/vulkan/vk_shaderdebug.cpp +++ b/renderdoc/driver/vulkan/vk_shaderdebug.cpp @@ -6154,6 +6154,11 @@ ShaderDebugTrace *VulkanReplay::DebugComputeCommon(ShaderStage stage, uint32_t e groupid[1] * threadDim[1] + ty); RDCASSERTEQUAL(thread_builtins[ShaderBuiltin::DispatchThreadIndex].value.u32v[2], groupid[2] * threadDim[2] + tz); + + RDCASSERTEQUAL(thread_builtins[ShaderBuiltin::IndexInSubgroup].value.u32v[0], + i % winner->subgroupSize); + RDCASSERTEQUAL(thread_builtins[ShaderBuiltin::SubgroupIndexInWorkgroup].value.u32v[0], + i / winner->subgroupSize); } else { @@ -6165,8 +6170,10 @@ ShaderDebugTrace *VulkanReplay::DebugComputeCommon(ShaderStage stage, uint32_t e thread_builtins[ShaderBuiltin::GroupFlatIndex] = ShaderVariable( rdcstr(), tz * threadDim[0] * threadDim[1] + ty * threadDim[0] + tx, 0U, 0U, 0U); // tightly wrap subgroups, this is likely not how the GPU actually assigns them - thread_builtins[ShaderBuiltin::SubgroupIndexInWorkgroup] = + thread_builtins[ShaderBuiltin::IndexInSubgroup] = ShaderVariable(rdcstr(), i % winner->subgroupSize, 0U, 0U, 0U); + thread_builtins[ShaderBuiltin::SubgroupIndexInWorkgroup] = + ShaderVariable(rdcstr(), i / winner->subgroupSize, 0U, 0U, 0U); apiWrapper->thread_props[i][(size_t)rdcspv::ThreadProperty::Active] = 1; }