diff --git a/renderdoc/driver/d3d12/d3d12_debug.h b/renderdoc/driver/d3d12/d3d12_debug.h index 5ad9d0ebf..25e9a2397 100644 --- a/renderdoc/driver/d3d12/d3d12_debug.h +++ b/renderdoc/driver/d3d12/d3d12_debug.h @@ -66,6 +66,8 @@ enum CBVUAVSRVSlot PICK_RESULT_UAV, PICK_RESULT_CLEAR_UAV, + SHADER_DEBUG_UAV, + TMP_UAV, MSAA_SRV2x, diff --git a/renderdoc/driver/d3d12/d3d12_shaderdebug.cpp b/renderdoc/driver/d3d12/d3d12_shaderdebug.cpp index 244ed50e2..3b7254367 100644 --- a/renderdoc/driver/d3d12/d3d12_shaderdebug.cpp +++ b/renderdoc/driver/d3d12/d3d12_shaderdebug.cpp @@ -22,10 +22,27 @@ * THE SOFTWARE. ******************************************************************************/ +#include "driver/dx/official/d3dcompiler.h" #include "driver/shaders/dxbc/dxbc_debug.h" #include "d3d12_command_queue.h" #include "d3d12_debug.h" #include "d3d12_resources.h" +#include "d3d12_shader_cache.h" + +#define D3D12SHADERDEBUG_PIXEL 0 + +struct DebugHit +{ + uint32_t numHits; + float posx; + float posy; + float depth; + uint32_t primitive; + uint32_t isFrontFace; + uint32_t sample; + uint32_t coverage; + uint32_t rawdata; // arbitrary, depending on shader +}; class D3D12DebugAPIWrapper : public ShaderDebug::DebugAPIWrapper { @@ -417,6 +434,8 @@ ShaderDebugTrace D3D12Replay::DebugVertex(uint32_t eventId, uint32_t vertid, uin return ret; } +#if D3D12SHADERDEBUG_PIXEL == 0 + ShaderDebugTrace D3D12Replay::DebugPixel(uint32_t eventId, uint32_t x, uint32_t y, uint32_t sample, uint32_t primitive) { @@ -425,6 +444,675 @@ ShaderDebugTrace D3D12Replay::DebugPixel(uint32_t eventId, uint32_t x, uint32_t return ret; } +#else + +ShaderDebugTrace D3D12Replay::DebugPixel(uint32_t eventId, uint32_t x, uint32_t y, uint32_t sample, + uint32_t primitive) +{ + using namespace DXBC; + using namespace DXBCBytecode; + using namespace ShaderDebug; + + D3D12MarkerRegion debugpixRegion( + m_pDevice->GetQueue()->GetReal(), + StringFormat::Fmt("DebugPixel @ %u of (%u,%u) %u / %u", eventId, x, y, sample, primitive)); + + const D3D12Pipe::State *pipelineState = GetD3D12PipelineState(); + + ShaderDebugTrace empty; + + // Fetch the disassembly info from the pixel shader + const D3D12Pipe::Shader &pixelShader = pipelineState->pixelShader; + WrappedID3D12Shader *ps = + m_pDevice->GetResourceManager()->GetCurrentAs(pixelShader.resourceId); + if(!ps) + return empty; + + DXBCContainer *dxbc = ps->GetDXBC(); + const ShaderReflection &refl = ps->GetDetails(); + + if(!dxbc) + return empty; + + dxbc->GetDisassembly(); + + // Fetch the previous stage's disassembly, to match outputs to PS inputs + DXBCContainer *prevDxbc = NULL; + // Check for geometry shader first + { + const D3D12Pipe::Shader &geometryShader = pipelineState->geometryShader; + WrappedID3D12Shader *gs = + m_pDevice->GetResourceManager()->GetCurrentAs(geometryShader.resourceId); + if(gs) + prevDxbc = gs->GetDXBC(); + } + // Check for domain shader next + if(prevDxbc == NULL) + { + const D3D12Pipe::Shader &domainShader = pipelineState->domainShader; + WrappedID3D12Shader *ds = + m_pDevice->GetResourceManager()->GetCurrentAs(domainShader.resourceId); + if(ds) + prevDxbc = ds->GetDXBC(); + } + // Check for vertex shader last + if(prevDxbc == NULL) + { + const D3D12Pipe::Shader &vertexShader = pipelineState->vertexShader; + WrappedID3D12Shader *vs = + m_pDevice->GetResourceManager()->GetCurrentAs(vertexShader.resourceId); + if(vs) + prevDxbc = vs->GetDXBC(); + } + + std::vector initialValues; + std::vector floatInputs; + std::vector inputVarNames; + std::string extractHlsl; + int structureStride = 0; + + ShaderDebug::GatherPSInputDataForInitialValues(*dxbc->GetReflection(), *prevDxbc->GetReflection(), + initialValues, floatInputs, inputVarNames, + extractHlsl, structureStride); + + uint32_t overdrawLevels = 100; // maximum number of overdraw levels + + // get the multisample count + uint32_t outputSampleCount = RDCMAX(1U, pipelineState->outputMerger.multiSampleCount); + + // if we're not rendering at MSAA, no need to fill the cache because evaluates will all return the + // plain input anyway. + if(outputSampleCount > 1) + { + RDCUNIMPLEMENTED("MSAA debugging not yet implemented for D3D12"); + return empty; + } + + extractHlsl += R"( +struct PSInitialData +{ + // metadata we need ourselves + uint hit; + float3 pos; + uint prim; + uint fface; + uint sample; + uint covge; + float derivValid; + + // input values + PSInput IN; + PSInput INddx; + PSInput INddy; + PSInput INddxfine; + PSInput INddyfine; +}; + +)"; + + extractHlsl += "RWStructuredBuffer PSInitialBuffer : register(u0);\n\n"; + + extractHlsl += R"( +void ExtractInputsPS(PSInput IN, float4 debug_pixelPos : SV_Position, uint prim : SV_PrimitiveID, + uint sample : SV_SampleIndex, uint covge : SV_Coverage, + bool fface : SV_IsFrontFace) +{ +)"; + + extractHlsl += " uint idx = " + ToStr(overdrawLevels) + ";\n"; + extractHlsl += StringFormat::Fmt( + " if(abs(debug_pixelPos.x - %u.5) < 0.5f && abs(debug_pixelPos.y - %u.5) < 0.5f)\n", x, y); + extractHlsl += " InterlockedAdd(PSInitialBuffer[0].hit, 1, idx);\n\n"; + extractHlsl += " idx = min(idx, " + ToStr(overdrawLevels) + ");\n\n"; + extractHlsl += " PSInitialBuffer[idx].pos = debug_pixelPos.xyz;\n"; + extractHlsl += " PSInitialBuffer[idx].prim = prim;\n"; + extractHlsl += " PSInitialBuffer[idx].fface = fface;\n"; + extractHlsl += " PSInitialBuffer[idx].covge = covge;\n"; + extractHlsl += " PSInitialBuffer[idx].sample = sample;\n"; + extractHlsl += " PSInitialBuffer[idx].IN = IN;\n"; + extractHlsl += " PSInitialBuffer[idx].derivValid = ddx(debug_pixelPos.x);\n"; + extractHlsl += " PSInitialBuffer[idx].INddx = (PSInput)0;\n"; + extractHlsl += " PSInitialBuffer[idx].INddy = (PSInput)0;\n"; + extractHlsl += " PSInitialBuffer[idx].INddxfine = (PSInput)0;\n"; + extractHlsl += " PSInitialBuffer[idx].INddyfine = (PSInput)0;\n"; + + for(size_t i = 0; i < floatInputs.size(); i++) + { + const std::string &name = floatInputs[i]; + extractHlsl += " PSInitialBuffer[idx].INddx." + name + " = ddx(IN." + name + ");\n"; + extractHlsl += " PSInitialBuffer[idx].INddy." + name + " = ddy(IN." + name + ");\n"; + extractHlsl += " PSInitialBuffer[idx].INddxfine." + name + " = ddx_fine(IN." + name + ");\n"; + extractHlsl += " PSInitialBuffer[idx].INddyfine." + name + " = ddy_fine(IN." + name + ");\n"; + } + extractHlsl += "\n}"; + + // Create pixel shader to get initial values from previous stage output + ID3DBlob *psBlob = NULL; + UINT flags = D3DCOMPILE_DEBUG | D3DCOMPILE_WARNINGS_ARE_ERRORS | D3DCOMPILE_DEBUG_NAME_FOR_SOURCE; + if(m_pDevice->GetShaderCache()->GetShaderBlob(extractHlsl.c_str(), "ExtractInputsPS", flags, + "ps_5_0", &psBlob) != "") + { + RDCERR("Failed to create shader to extract inputs"); + return empty; + } + + uint32_t structStride = sizeof(uint32_t) // uint hit; + + sizeof(float) * 3 // float3 pos; + + sizeof(uint32_t) // uint prim; + + sizeof(uint32_t) // uint fface; + + sizeof(uint32_t) // uint sample; + + sizeof(uint32_t) // uint covge; + + sizeof(float) // float derivValid; + + + structureStride * 5; // PSInput IN, INddx, INddy, INddxfine, INddyfine; + + HRESULT hr = S_OK; + + // Create buffer to store initial values captured in pixel shader + D3D12_RESOURCE_DESC rdesc; + ZeroMemory(&rdesc, sizeof(D3D12_RESOURCE_DESC)); + rdesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; + rdesc.Width = structStride * (overdrawLevels + 1); + rdesc.Height = 1; + rdesc.DepthOrArraySize = 1; + rdesc.MipLevels = 1; + rdesc.Format = DXGI_FORMAT_UNKNOWN; + rdesc.Flags = D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS; + rdesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; + rdesc.SampleDesc.Count = 1; // TODO: Support MSAA + rdesc.SampleDesc.Quality = 0; + + D3D12_HEAP_PROPERTIES heapProps; + heapProps.Type = D3D12_HEAP_TYPE_DEFAULT; + heapProps.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN; + heapProps.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN; + heapProps.CreationNodeMask = 1; + heapProps.VisibleNodeMask = 1; + + ID3D12Resource *pInitialValuesBuffer = NULL; + D3D12_RESOURCE_STATES resourceState = D3D12_RESOURCE_STATE_UNORDERED_ACCESS; + hr = m_pDevice->CreateCommittedResource(&heapProps, D3D12_HEAP_FLAG_NONE, &rdesc, resourceState, + NULL, __uuidof(ID3D12Resource), + (void **)&pInitialValuesBuffer); + if(FAILED(hr)) + { + RDCERR("Failed to create buffer for pixel shader debugging HRESULT: %s", ToStr(hr).c_str()); + SAFE_RELEASE(psBlob); + return empty; + } + + // Create UAV of initial values buffer + D3D12_UNORDERED_ACCESS_VIEW_DESC uavDesc; + ZeroMemory(&uavDesc, sizeof(D3D12_UNORDERED_ACCESS_VIEW_DESC)); + uavDesc.Format = DXGI_FORMAT_UNKNOWN; + uavDesc.ViewDimension = D3D12_UAV_DIMENSION_BUFFER; + uavDesc.Buffer.NumElements = overdrawLevels + 1; + uavDesc.Buffer.StructureByteStride = structStride; + + D3D12_CPU_DESCRIPTOR_HANDLE uav = m_pDevice->GetDebugManager()->GetCPUHandle(SHADER_DEBUG_UAV); + m_pDevice->CreateUnorderedAccessView(pInitialValuesBuffer, NULL, &uavDesc, uav); + + uavDesc.Format = DXGI_FORMAT_R32_UINT; + uavDesc.Buffer.FirstElement = 0; + uavDesc.Buffer.NumElements = structStride * (overdrawLevels + 1) / sizeof(uint32_t); + uavDesc.Buffer.StructureByteStride = 0; + D3D12_CPU_DESCRIPTOR_HANDLE clearUav = + m_pDevice->GetDebugManager()->GetUAVClearHandle(SHADER_DEBUG_UAV); + m_pDevice->CreateUnorderedAccessView(pInitialValuesBuffer, NULL, &uavDesc, clearUav); + + // Store a copy of the event's render state to restore later + D3D12RenderState &rs = m_pDevice->GetQueue()->GetCommandData()->m_RenderState; + D3D12RenderState prevState = rs; + + WrappedID3D12RootSignature *sig = + m_pDevice->GetResourceManager()->GetCurrentAs(rs.graphics.rootsig); + + // Need to be able to add a descriptor table with our UAV without hitting the 64 DWORD limit + RDCASSERT(sig->sig.dwordLength < 64); + D3D12RootSignature modsig = sig->sig; + + UINT regSpace = modsig.maxSpaceIndex + 1; + MoveRootSignatureElementsToRegisterSpace(modsig, regSpace, D3D12DescriptorType::UAV, + D3D12_SHADER_VISIBILITY_PIXEL); + + // Create the descriptor table for our UAV + D3D12_DESCRIPTOR_RANGE1 descRange; + descRange.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_UAV; + descRange.NumDescriptors = 1; + descRange.BaseShaderRegister = 0; + descRange.RegisterSpace = 0; + descRange.Flags = D3D12_DESCRIPTOR_RANGE_FLAG_NONE; + descRange.OffsetInDescriptorsFromTableStart = 0; + + modsig.Parameters.push_back(D3D12RootSignatureParameter()); + D3D12RootSignatureParameter ¶m = modsig.Parameters.back(); + param.ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE; + param.ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL; + param.DescriptorTable.NumDescriptorRanges = 1; + param.DescriptorTable.pDescriptorRanges = &descRange; + + uint32_t sigElem = uint32_t(modsig.Parameters.size() - 1); + + // Create the root signature for gathering initial pixel shader values + ID3DBlob *root = m_pDevice->GetShaderCache()->MakeRootSig(modsig); + ID3D12RootSignature *pRootSignature = NULL; + hr = m_pDevice->CreateRootSignature(0, root->GetBufferPointer(), root->GetBufferSize(), + __uuidof(ID3D12RootSignature), (void **)&pRootSignature); + if(FAILED(hr)) + { + RDCERR("Failed to create root signature for pixel shader debugging HRESULT: %s", + ToStr(hr).c_str()); + SAFE_RELEASE(root); + SAFE_RELEASE(psBlob); + SAFE_RELEASE(pInitialValuesBuffer); + return empty; + } + SAFE_RELEASE(root); + + WrappedID3D12PipelineState *origPSO = + m_pDevice->GetResourceManager()->GetCurrentAs(rs.pipe); + + RDCASSERT(origPSO->IsGraphics()); + + D3D12_EXPANDED_PIPELINE_STATE_STREAM_DESC pipeDesc; + origPSO->Fill(pipeDesc); + + // All PSO state is the same as the event's, except for the pixel shader and root signature + pipeDesc.PS.BytecodeLength = psBlob->GetBufferSize(); + pipeDesc.PS.pShaderBytecode = psBlob->GetBufferPointer(); + pipeDesc.pRootSignature = pRootSignature; + + ID3D12PipelineState *initialPso = NULL; + hr = m_pDevice->CreatePipeState(pipeDesc, &initialPso); + if(FAILED(hr)) + { + RDCERR("Failed to create PSO for pixel shader debugging HRESULT: %s", ToStr(hr).c_str()); + SAFE_RELEASE(psBlob); + SAFE_RELEASE(pInitialValuesBuffer); + SAFE_RELEASE(pRootSignature); + return empty; + } + + // Add the descriptor for our UAV, then clear it + std::set copiedHeaps; + PortableHandle shaderDebugUav = ToPortableHandle(GetDebugManager()->GetCPUHandle(SHADER_DEBUG_UAV)); + AddDebugDescriptorToRenderState(m_pDevice, rs, shaderDebugUav, + D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, sigElem, copiedHeaps); + + ID3D12GraphicsCommandListX *cmdList = m_pDevice->GetDebugManager()->ResetDebugList(); + rs.ApplyDescriptorHeaps(cmdList); + D3D12_GPU_DESCRIPTOR_HANDLE gpuUav = m_pDevice->GetDebugManager()->GetGPUHandle(SHADER_DEBUG_UAV); + UINT zero[4] = {0, 0, 0, 0}; + cmdList->ClearUnorderedAccessViewUint(gpuUav, clearUav, pInitialValuesBuffer, zero, 0, NULL); + + // Execute the command to ensure that UAV clear and resource creation occur before replay + hr = cmdList->Close(); + if(FAILED(hr)) + { + RDCERR("Failed to close command list HRESULT: %s", ToStr(hr).c_str()); + SAFE_RELEASE(psBlob); + SAFE_RELEASE(pInitialValuesBuffer); + SAFE_RELEASE(pRootSignature); + SAFE_RELEASE(initialPso); + return empty; + } + + { + ID3D12CommandList *l = cmdList; + m_pDevice->GetQueue()->ExecuteCommandLists(1, &l); + m_pDevice->GPUSync(); + } + + { + D3D12MarkerRegion initState(m_pDevice->GetQueue()->GetReal(), + "Replaying event for initial states"); + + // Set the PSO and root signature + rs.pipe = GetResID(initialPso); + rs.graphics.rootsig = GetResID(pRootSignature); + + // Replay the event with our modified state + m_pDevice->ReplayLog(0, eventId, eReplay_OnlyDraw); + + // Restore D3D12 state to what the event uses + rs = prevState; + } + + bytebuf initialData; + m_pDevice->GetDebugManager()->GetBufferData(pInitialValuesBuffer, 0, 0, initialData); + + // Replaying the event has finished, and the data has been copied out. + // Free all the resources that were created. + SAFE_RELEASE(psBlob); + SAFE_RELEASE(pRootSignature); + SAFE_RELEASE(pInitialValuesBuffer); + SAFE_RELEASE(initialPso); + + DebugHit *buf = (DebugHit *)initialData.data(); + + D3D12MarkerRegion::Set(m_pDevice->GetQueue()->GetReal(), + StringFormat::Fmt("Got %u hits", buf[0].numHits)); + if(buf[0].numHits == 0) + { + RDCLOG("No hit for this event"); + return empty; + } + + // if we encounter multiple hits at our destination pixel co-ord (or any other) we + // check to see if a specific primitive was requested (via primitive parameter not + // being set to ~0U). If it was, debug that pixel, otherwise do a best-estimate + // of which fragment was the last to successfully depth test and debug that, just by + // checking if the depth test is ordered and picking the final fragment in the series + + // our debugging quad. Order is TL, TR, BL, BR + State quad[4]; + + // figure out the TL pixel's coords. Assume even top left (towards 0,0) + // this isn't spec'd but is a reasonable assumption. + int xTL = x & (~1); + int yTL = y & (~1); + + // get the index of our desired pixel + int destIdx = (x - xTL) + 2 * (y - yTL); + + // Fetch constant buffer data from root signature + bytebuf cbufData[D3D12_COMMONSHADER_CONSTANT_BUFFER_API_SLOT_COUNT]; + GatherConstantBuffers(m_pDevice, dxbc->m_Type, rs.graphics, cbufData); + + // Get depth func and determine "winner" pixel + D3D12_COMPARISON_FUNC depthFunc = pipeDesc.DepthStencilState.DepthFunc; + DebugHit *pWinnerHit = NULL; + + if(sample == ~0U) + sample = 0; + + if(primitive != ~0U) + { + for(size_t i = 0; i < buf[0].numHits && i < overdrawLevels; i++) + { + DebugHit *pHit = (DebugHit *)(initialData.data() + i * structStride); + + if(pHit->primitive == primitive && pHit->sample == sample) + { + pWinnerHit = pHit; + } + } + } + + if(pWinnerHit == NULL) + { + for(size_t i = 0; i < buf[0].numHits && i < overdrawLevels; i++) + { + DebugHit *pHit = (DebugHit *)(initialData.data() + i * structStride); + + if(pWinnerHit == NULL || (pWinnerHit->sample != sample && pHit->sample == sample) || + depthFunc == D3D12_COMPARISON_FUNC_ALWAYS || depthFunc == D3D12_COMPARISON_FUNC_NEVER || + depthFunc == D3D12_COMPARISON_FUNC_NOT_EQUAL || depthFunc == D3D12_COMPARISON_FUNC_EQUAL) + { + pWinnerHit = pHit; + continue; + } + + if((depthFunc == D3D12_COMPARISON_FUNC_LESS && pHit->depth < pWinnerHit->depth) || + (depthFunc == D3D12_COMPARISON_FUNC_LESS_EQUAL && pHit->depth <= pWinnerHit->depth) || + (depthFunc == D3D12_COMPARISON_FUNC_GREATER && pHit->depth > pWinnerHit->depth) || + (depthFunc == D3D12_COMPARISON_FUNC_GREATER_EQUAL && pHit->depth >= pWinnerHit->depth)) + { + if(pHit->sample == sample) + { + pWinnerHit = pHit; + } + } + } + } + + if(pWinnerHit == NULL) + { + RDCLOG("Couldn't find any pixels that passed depth test at target coordinates"); + return empty; + } + + ShaderDebugTrace traces[4]; + + GlobalState global; + GetDebugManager()->CreateShaderGlobalState(global, dxbc); + + { + DebugHit *pHit = pWinnerHit; + State initialState; + CreateShaderDebugStateAndTrace(initialState, traces[destIdx], destIdx, dxbc, refl, cbufData); + + rdcarray &ins = traces[destIdx].inputs; + if(!ins.empty() && ins.back().name == "vCoverage") + ins.back().value.u.x = pHit->coverage; + + initialState.semantics.coverage = pHit->coverage; + initialState.semantics.primID = pHit->primitive; + initialState.semantics.isFrontFace = pHit->isFrontFace; + + uint32_t *data = &pHit->rawdata; + + float *pos_ddx = (float *)data; + + // ddx(SV_Position.x) MUST be 1.0 + if(*pos_ddx != 1.0f) + { + RDCERR("Derivatives invalid"); + return empty; + } + + data++; + + for(size_t i = 0; i < initialValues.size(); i++) + { + int32_t *rawout = NULL; + + if(initialValues[i].reg >= 0) + { + ShaderVariable &invar = traces[destIdx].inputs[initialValues[i].reg]; + + if(initialValues[i].sysattribute == ShaderBuiltin::PrimitiveIndex) + { + invar.value.u.x = pHit->primitive; + } + else if(initialValues[i].sysattribute == ShaderBuiltin::MSAASampleIndex) + { + invar.value.u.x = pHit->sample; + } + else if(initialValues[i].sysattribute == ShaderBuiltin::MSAACoverage) + { + invar.value.u.x = pHit->coverage; + } + else if(initialValues[i].sysattribute == ShaderBuiltin::IsFrontFace) + { + invar.value.u.x = pHit->isFrontFace ? ~0U : 0; + } + else + { + rawout = &invar.value.iv[initialValues[i].elem]; + + memcpy(rawout, data, initialValues[i].numwords * 4); + } + } + + if(initialValues[i].included) + data += initialValues[i].numwords; + } + + for(int i = 0; i < 4; i++) + { + if(i != destIdx) + traces[i] = traces[destIdx]; + quad[i] = initialState; + quad[i].SetTrace(i, &traces[i]); + if(i != destIdx) + quad[i].SetHelper(); + } + + // TODO: Handle inputs that were evaluated at sample granularity (MSAA) + + ApplyAllDerivatives(global, traces, destIdx, initialValues, (float *)data); + } + + std::vector states; + + if(dxbc->GetDebugInfo()) + dxbc->GetDebugInfo()->GetLocals(0, dxbc->GetDXBCByteCode()->GetInstruction(0).offset, + quad[destIdx].locals); + + states.push_back(quad[destIdx]); + + // ping pong between so that we can have 'current' quad to update into new one + State quad2[4]; + + State *curquad = quad; + State *newquad = quad2; + + // marks any threads stalled waiting for others to catch up + bool activeMask[4] = {true, true, true, true}; + + int cycleCounter = 0; + + D3D12MarkerRegion simloop(m_pDevice->GetQueue()->GetReal(), "Simulation Loop"); + + D3D12DebugAPIWrapper apiWrapper(m_pDevice, dxbc, global); + + // simulate lockstep until all threads are finished + bool finished = true; + do + { + for(size_t i = 0; i < 4; i++) + { + if(activeMask[i]) + newquad[i] = curquad[i].GetNext(global, &apiWrapper, curquad); + else + newquad[i] = curquad[i]; + } + + State *a = curquad; + curquad = newquad; + newquad = a; + + // if our destination quad is paused don't record multiple identical states. + if(activeMask[destIdx]) + { + State &s = curquad[destIdx]; + + if(dxbc->GetDebugInfo()) + { + size_t inst = + RDCMIN((size_t)s.nextInstruction, dxbc->GetDXBCByteCode()->GetNumInstructions() - 1); + const Operation &op = dxbc->GetDXBCByteCode()->GetInstruction(inst); + dxbc->GetDebugInfo()->GetLocals(s.nextInstruction, op.offset, s.locals); + } + + states.push_back(s); + } + + // we need to make sure that control flow which converges stays in lockstep so that + // derivatives are still valid. While diverged, we don't have to keep threads in lockstep + // since using derivatives is invalid. + + // Threads diverge either in ifs, loops, or switches. Due to the nature of the bytecode, + // all threads *must* pass through the same exit instruction for each, there's no jumping + // around with gotos. Note also for the same reason, the only time threads are on earlier + // instructions is if they are still catching up to a thread that has exited the control + // flow. + + // So the scheme is as follows: + // * If all threads have the same nextInstruction, just continue we are still in lockstep. + // * If threads are out of lockstep, find any thread which has nextInstruction pointing + // immediately *after* an ENDIF, ENDLOOP or ENDSWITCH. Pointing directly at one is not + // an indication the thread is done, as the next step for an ENDLOOP will jump back to + // the matching LOOP and continue iterating. + // * Pause any thread matching the above until all threads are pointing to the same + // instruction. By the assumption above, all threads will eventually pass through this + // terminating instruction so we just pause any other threads and don't do anything + // until the control flow has converged and we can continue stepping in lockstep. + + // mark all threads as active again. + // if we've converged, or we were never diverged, this keeps everything ticking + activeMask[0] = activeMask[1] = activeMask[2] = activeMask[3] = true; + + if(curquad[0].nextInstruction != curquad[1].nextInstruction || + curquad[0].nextInstruction != curquad[2].nextInstruction || + curquad[0].nextInstruction != curquad[3].nextInstruction) + { + // this isn't *perfect* but it will still eventually continue. We look for the most + // advanced thread, and check to see if it's just finished a control flow. If it has + // then we assume it's at the convergence point and wait for every other thread to + // catch up, pausing any threads that reach the convergence point before others. + + // Note this might mean we don't have any threads paused even within divergent flow. + // This is fine and all we care about is pausing to make sure threads don't run ahead + // into code that should be lockstep. We don't care at all about what they do within + // the code that is divergent. + + // The reason this isn't perfect is that the most advanced thread could be on an + // inner loop or inner if, not the convergence point, and we could be pausing it + // fruitlessly. Worse still - it could be on a branch none of the other threads will + // take so they will never reach that exact instruction. + // But we know that all threads will eventually go through the convergence point, so + // even in that worst case if we didn't pick the right waiting point, another thread + // will overtake and become the new most advanced thread and the previous waiting + // thread will resume. So in this case we caused a thread to wait more than it should + // have but that's not a big deal as it's within divergent flow so they don't have to + // stay in lockstep. Also if all threads will eventually pass that point we picked, + // we just waited to converge even in technically divergent code which is also + // harmless. + + // Phew! + + uint32_t convergencePoint = 0; + + // find which thread is most advanced + for(size_t i = 0; i < 4; i++) + if(curquad[i].nextInstruction > convergencePoint) + convergencePoint = curquad[i].nextInstruction; + + if(convergencePoint > 0) + { + OpcodeType op = dxbc->GetDXBCByteCode()->GetInstruction(convergencePoint - 1).operation; + + // if the most advnaced thread hasn't just finished control flow, then all + // threads are still running, so don't converge + if(op != OPCODE_ENDIF && op != OPCODE_ENDLOOP && op != OPCODE_ENDSWITCH) + convergencePoint = 0; + } + + // pause any threads at that instruction (could be none) + for(size_t i = 0; i < 4; i++) + if(curquad[i].nextInstruction == convergencePoint) + activeMask[i] = false; + } + + finished = curquad[destIdx].Finished(); + + cycleCounter++; + + if(cycleCounter == SHADER_DEBUG_WARN_THRESHOLD) + { + if(PromptDebugTimeout(cycleCounter)) + break; + } + } while(!finished); + + traces[destIdx].states = states; + + traces[destIdx].hasLocals = dxbc->GetDebugInfo() && dxbc->GetDebugInfo()->HasLocals(); + + traces[destIdx].lineInfo.resize(dxbc->GetDXBCByteCode()->GetNumInstructions()); + for(size_t i = 0; dxbc->GetDebugInfo() && i < dxbc->GetDXBCByteCode()->GetNumInstructions(); i++) + { + const Operation &op = dxbc->GetDXBCByteCode()->GetInstruction(i); + dxbc->GetDebugInfo()->GetLineInfo(i, op.offset, traces[destIdx].lineInfo[i]); + } + + return traces[destIdx]; +} + +#endif // D3D12SHADERDEBUG_PIXEL + ShaderDebugTrace D3D12Replay::DebugThread(uint32_t eventId, const uint32_t groupid[3], const uint32_t threadid[3]) { diff --git a/renderdoc/driver/d3d12/d3d12_state.cpp b/renderdoc/driver/d3d12/d3d12_state.cpp index 7c6f5f459..da261e0b2 100644 --- a/renderdoc/driver/d3d12/d3d12_state.cpp +++ b/renderdoc/driver/d3d12/d3d12_state.cpp @@ -157,14 +157,7 @@ void D3D12RenderState::ApplyState(WrappedID3D12Device *dev, ID3D12GraphicsComman } } - std::vector descHeaps; - descHeaps.resize(heaps.size()); - - for(size_t i = 0; i < heaps.size(); i++) - descHeaps[i] = GetResourceManager()->GetCurrentAs(heaps[i]); - - if(!descHeaps.empty()) - cmd->SetDescriptorHeaps((UINT)descHeaps.size(), &descHeaps[0]); + ApplyDescriptorHeaps(cmd); if(graphics.rootsig != ResourceId()) { @@ -183,6 +176,18 @@ void D3D12RenderState::ApplyState(WrappedID3D12Device *dev, ID3D12GraphicsComman } } +void D3D12RenderState::ApplyDescriptorHeaps(ID3D12GraphicsCommandListX *cmd) const +{ + std::vector descHeaps; + descHeaps.resize(heaps.size()); + + for(size_t i = 0; i < heaps.size(); i++) + descHeaps[i] = GetResourceManager()->GetCurrentAs(heaps[i]); + + if(!descHeaps.empty()) + cmd->SetDescriptorHeaps((UINT)descHeaps.size(), &descHeaps[0]); +} + void D3D12RenderState::ApplyComputeRootElements(ID3D12GraphicsCommandListX *cmd) const { for(size_t i = 0; i < compute.sigelems.size(); i++) diff --git a/renderdoc/driver/d3d12/d3d12_state.h b/renderdoc/driver/d3d12/d3d12_state.h index 5ef45e8f6..bbd4eef96 100644 --- a/renderdoc/driver/d3d12/d3d12_state.h +++ b/renderdoc/driver/d3d12/d3d12_state.h @@ -47,6 +47,7 @@ struct D3D12RenderState D3D12RenderState &operator=(const D3D12RenderState &o); void ApplyState(WrappedID3D12Device *dev, ID3D12GraphicsCommandListX *list) const; + void ApplyDescriptorHeaps(ID3D12GraphicsCommandListX *list) const; void ApplyComputeRootElements(ID3D12GraphicsCommandListX *cmd) const; void ApplyGraphicsRootElements(ID3D12GraphicsCommandListX *cmd) const;