mirror of
https://github.com/baldurk/renderdoc.git
synced 2026-09-13 17:25:54 +00:00
Implement D3D12Replay::DebugPixel
This enables the core loop of capturing PS inputs, building shader debug state, and walking through a shader's instructions.
This commit is contained in:
committed by
Baldur Karlsson
parent
c63baeb609
commit
a88deaeba0
@@ -66,6 +66,8 @@ enum CBVUAVSRVSlot
|
||||
PICK_RESULT_UAV,
|
||||
PICK_RESULT_CLEAR_UAV,
|
||||
|
||||
SHADER_DEBUG_UAV,
|
||||
|
||||
TMP_UAV,
|
||||
|
||||
MSAA_SRV2x,
|
||||
|
||||
@@ -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<WrappedID3D12Shader>(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<WrappedID3D12Shader>(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<WrappedID3D12Shader>(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<WrappedID3D12Shader>(vertexShader.resourceId);
|
||||
if(vs)
|
||||
prevDxbc = vs->GetDXBC();
|
||||
}
|
||||
|
||||
std::vector<PSInputElement> initialValues;
|
||||
std::vector<std::string> floatInputs;
|
||||
std::vector<std::string> 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<PSInitialData> 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<WrappedID3D12RootSignature>(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<WrappedID3D12PipelineState>(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<ResourceId> 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<ShaderVariable> &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<ShaderDebugState> 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])
|
||||
{
|
||||
|
||||
@@ -157,14 +157,7 @@ void D3D12RenderState::ApplyState(WrappedID3D12Device *dev, ID3D12GraphicsComman
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<ID3D12DescriptorHeap *> descHeaps;
|
||||
descHeaps.resize(heaps.size());
|
||||
|
||||
for(size_t i = 0; i < heaps.size(); i++)
|
||||
descHeaps[i] = GetResourceManager()->GetCurrentAs<ID3D12DescriptorHeap>(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<ID3D12DescriptorHeap *> descHeaps;
|
||||
descHeaps.resize(heaps.size());
|
||||
|
||||
for(size_t i = 0; i < heaps.size(); i++)
|
||||
descHeaps[i] = GetResourceManager()->GetCurrentAs<ID3D12DescriptorHeap>(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++)
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user