Skip to content

Commit

Permalink
Add D3D12 per fragment callback for pixel history
Browse files Browse the repository at this point in the history
This callback gets info for each fragment of a draw during pixel
history, to determine which ones were discarded and why.
  • Loading branch information
skarolewics authored and Zorro666 committed Nov 13, 2023
1 parent f2c1435 commit caef1b9
Showing 1 changed file with 380 additions and 0 deletions.
380 changes: 380 additions & 0 deletions renderdoc/driver/d3d12/d3d12_pixelhistory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,15 @@ struct D3D12EventInfo
uint8_t padding1[8];
};

struct D3D12PerFragmentInfo
{
// primitive ID is copied from a R32G32B32A32 texture.
int32_t primitiveID;
uint32_t padding[3];
D3D12PixelHistoryValue shaderOut;
D3D12PixelHistoryValue postMod;
};

struct D3D12PipelineReplacements
{
ID3D12PipelineState *fixedShaderStencil;
Expand Down Expand Up @@ -1744,6 +1753,377 @@ void D3D12UpdateTestsFailed(const D3D12TestsFailedCallback *tfCb, uint32_t event
}
}

// Callback used to get information for each fragment that touched a pixel
struct D3D12PixelHistoryPerFragmentCallback : D3D12PixelHistoryCallback
{
D3D12PixelHistoryPerFragmentCallback(WrappedID3D12Device *device,
D3D12PixelHistoryShaderCache *shaderCache,
const D3D12PixelHistoryCallbackInfo &callbackInfo,
const std::map<uint32_t, uint32_t> &eventFragments,
const std::map<uint32_t, ModificationValue> &eventPremods)
: D3D12PixelHistoryCallback(device, shaderCache, callbackInfo, NULL),
m_EventFragments(eventFragments),
m_EventPremods(eventPremods)
{
}

~D3D12PixelHistoryPerFragmentCallback()
{
for(ID3D12PipelineState *pso : m_PSOsToDestroy)
SAFE_RELEASE(pso);
}

struct PerFragmentPipelines
{
// Disable all tests, use the new render pass to render into a separate
// attachment, and use fragment shader that outputs primitive ID.
ID3D12PipelineState *primitiveIdPipe;
// Turn off blending.
ID3D12PipelineState *shaderOutPipe;
// Enable blending to get post event values.
ID3D12PipelineState *postModPipe;
};

void PreDraw(uint32_t eid, ID3D12GraphicsCommandListX *cmd)
{
if(m_EventFragments.find(eid) == m_EventFragments.end())
return;

D3D12RenderState prevState = m_pDevice->GetQueue()->GetCommandData()->GetCurRenderState();
D3D12RenderState &state = m_pDevice->GetQueue()->GetCommandData()->GetCurRenderState();

uint32_t numFragmentsInEvent = m_EventFragments[eid];

// TODO: Do we need to test for stencil format here too, or does this check catch planar depth/stencil?
uint32_t renderTargetIndex = 0;
if(IsDepthFormat(m_CallbackInfo.targetDesc.Format))
{
// Going to add another render target
renderTargetIndex = (uint32_t)state.rts.size();
}
else
{
for(uint32_t i = 0; i < state.rts.size(); ++i)
{
ResourceId img = state.rts[i].GetResResourceId();
if(img == GetResID(m_CallbackInfo.targetImage))
{
renderTargetIndex = i;
break;
}
}
}

WrappedID3D12PipelineState *origPSO =
m_pDevice->GetResourceManager()->GetCurrentAs<WrappedID3D12PipelineState>(state.pipe);
if(origPSO == NULL)
{
RDCERR("Failed to retrieve original PSO for pixel history.");
return;
}

D3D12_EXPANDED_PIPELINE_STATE_STREAM_DESC origPipeDesc;
origPSO->Fill(origPipeDesc);

PerFragmentPipelines pipes = CreatePerFragmentPipelines(state, eid, 0, renderTargetIndex);

for(uint32_t i = 0; i < state.views.size(); i++)
{
ScissorToPixel(state.views[i], state.scissors[i]);

// Set scissor to the whole pixel quad
state.scissors[i].left &= ~0x1;
state.scissors[i].top &= ~0x1;
state.scissors[i].right = state.scissors[i].left + 2;
state.scissors[i].bottom = state.scissors[i].top + 2;
}

ID3D12PipelineState *psosIter[2];
psosIter[0] = pipes.primitiveIdPipe;
psosIter[1] = pipes.shaderOutPipe;

D3D12CopyPixelParams colorCopyParams = {};
colorCopyParams.srcImage = m_CallbackInfo.colorImage;
colorCopyParams.srcImageFormat = DXGI_FORMAT_R32G32B32A32_FLOAT;
colorCopyParams.copyFormat = DXGI_FORMAT_R32G32B32A32_FLOAT;
colorCopyParams.srcImageState = D3D12_RESOURCE_STATE_RENDER_TARGET;
colorCopyParams.multisampled = m_CallbackInfo.targetDesc.SampleDesc.Count > 1;
colorCopyParams.x = m_CallbackInfo.x;
colorCopyParams.y = m_CallbackInfo.y;
colorCopyParams.sample = m_CallbackInfo.targetSubresource.sample;
colorCopyParams.mip = m_CallbackInfo.targetSubresource.mip;
colorCopyParams.arraySlice = m_CallbackInfo.targetSubresource.slice;
colorCopyParams.scratchBuffer = true;

bool depthEnabled = origPipeDesc.DepthStencilState.DepthEnable != FALSE;

D3D12MarkerRegion::Set(
cmd, StringFormat::Fmt("Event %u has %u fragments", eid, numFragmentsInEvent));

// Get primitive ID and shader output value for each fragment.
for(uint32_t f = 0; f < numFragmentsInEvent; f++)
{
for(uint32_t i = 0; i < 2; i++)
{
uint32_t storeOffset = (fragsProcessed + f) * sizeof(D3D12PerFragmentInfo);

D3D12MarkerRegion region(
cmd,
StringFormat::Fmt("Getting %s for %u", i == 0 ? "primitive ID" : "shader output", eid));

if(psosIter[i] == NULL)
{
// Without one of the pipelines (e.g. if there was a geometry shader in use and we can't
// read primitive ID in the fragment shader) we can't continue.
// Technically we can if the geometry shader outs a primitive ID, but that is unlikely.
D3D12MarkerRegion::Set(cmd, "Can't get primitive ID with geometry shader in use");

D3D12_WRITEBUFFERIMMEDIATE_PARAMETER param = {};
param.Dest = m_CallbackInfo.dstBuffer->GetGPUVirtualAddress() + storeOffset;
param.Value = ~0U;
cmd->WriteBufferImmediate(1, &param, NULL);
continue;
}

// TODO: Are there any barriers needed here before/after clearing DSV?

cmd->ClearDepthStencilView(m_pDevice->GetDebugManager()->GetCPUHandle(PIXEL_HISTORY_DSV),
D3D12_CLEAR_FLAG_DEPTH | D3D12_CLEAR_FLAG_STENCIL, 0.0f, 0, 1,
&state.scissors[0]);

state.rts[0] = *m_CallbackInfo.colorDescriptor;
state.dsv = *m_CallbackInfo.dsDescriptor;
state.pipe = GetResID(psosIter[i]);

// Update stencil reference to the current fragment index, so that we
// get values for a single fragment only.
state.stencilRefFront = f;
state.stencilRefBack = f;
state.ApplyState(m_pDevice, cmd);

const ActionDescription *action = m_pDevice->GetAction(eid);
::ReplayDraw(cmd, *action);

if(i == 1)
{
storeOffset += offsetof(struct D3D12PerFragmentInfo, shaderOut);
if(depthEnabled)
{
D3D12CopyPixelParams depthCopyParams = colorCopyParams;
depthCopyParams.srcImage = m_CallbackInfo.dsImage;
depthCopyParams.srcImageFormat =
GetDepthSRVFormat(m_CallbackInfo.dsImage->GetDesc().Format, 0);
depthCopyParams.copyFormat = DXGI_FORMAT_R32_TYPELESS;
depthCopyParams.srcImageState = D3D12_RESOURCE_STATE_DEPTH_WRITE;
depthCopyParams.planeSlice = 0;
depthCopyParams.depthcopy = true;
CopyImagePixel(cmd, depthCopyParams,
storeOffset + offsetof(struct D3D12PixelHistoryValue, depth));
}
}
CopyImagePixel(cmd, colorCopyParams, storeOffset);
}
}

const ModificationValue &premod = m_EventPremods[eid];
// For every fragment except the last one, retrieve post-modification value.
for(uint32_t f = 0; f < numFragmentsInEvent - 1; ++f)
{
D3D12MarkerRegion region(cmd,
StringFormat::Fmt("Getting postmod for fragment %u in %u", f, eid));

// Get post-modification value, use the original framebuffer attachment.
state.pipe = GetResID(pipes.postModPipe);

// Have to reset stencil
D3D12_CLEAR_FLAGS clearFlags =
(f == 0 ? D3D12_CLEAR_FLAG_DEPTH | D3D12_CLEAR_FLAG_STENCIL : D3D12_CLEAR_FLAG_STENCIL);
cmd->ClearDepthStencilView(m_pDevice->GetDebugManager()->GetCPUHandle(PIXEL_HISTORY_DSV),
clearFlags, premod.depth, 0, 1, &state.scissors[0]);

if(f == 0)
{
// Before starting the draw, initialize the pixel to the premodification value
// for this event, for both color and depth. Depth was handled above already.
cmd->ClearRenderTargetView(m_pDevice->GetDebugManager()->GetCPUHandle(PIXEL_HISTORY_RTV),
premod.col.floatValue.data(), 1, &state.scissors[0]);

// TODO: Does anything different need to happen here if the target resource is depth/stencil?
}

state.stencilRefFront = f;
state.stencilRefBack = f;
state.ApplyState(m_pDevice, cmd);

const ActionDescription *action = m_pDevice->GetAction(eid);
::ReplayDraw(cmd, *action);

CopyImagePixel(cmd, colorCopyParams,
(fragsProcessed + f) * sizeof(D3D12PerFragmentInfo) +
offsetof(struct D3D12PerFragmentInfo, postMod));

if(prevState.dsv.GetResResourceId() != ResourceId())
{
D3D12CopyPixelParams depthCopyParams = colorCopyParams;
depthCopyParams.srcImage = m_CallbackInfo.dsImage;
depthCopyParams.srcImageFormat =
GetDepthSRVFormat(m_CallbackInfo.dsImage->GetDesc().Format, 0);
depthCopyParams.copyFormat = DXGI_FORMAT_R32_TYPELESS;
depthCopyParams.srcImageState = D3D12_RESOURCE_STATE_DEPTH_WRITE;
depthCopyParams.depthcopy = true;
CopyImagePixel(cmd, depthCopyParams,
(fragsProcessed + f) * sizeof(D3D12PerFragmentInfo) +
offsetof(struct D3D12PerFragmentInfo, postMod) +
offsetof(struct D3D12PixelHistoryValue, depth));
}
}

m_EventIndices[eid] = fragsProcessed;
fragsProcessed += numFragmentsInEvent;

state = prevState;
state.ApplyState(m_pDevice, cmd);
}
bool PostDraw(uint32_t eid, ID3D12GraphicsCommandListX *cmd) { return false; }
void PostRedraw(uint32_t eid, ID3D12GraphicsCommandListX *cmd) {}

// CreatePerFragmentPipelines for getting per fragment information.
PerFragmentPipelines CreatePerFragmentPipelines(const D3D12RenderState &state, uint32_t eid,
uint32_t fragmentIndex, uint32_t colorOutputIndex)
{
PerFragmentPipelines pipes = {};

WrappedID3D12PipelineState *origPSO =
m_pDevice->GetResourceManager()->GetCurrentAs<WrappedID3D12PipelineState>(state.pipe);
if(origPSO == NULL)
{
RDCERR("Failed to retrieve original PSO for pixel history.");
return pipes;
}

D3D12_EXPANDED_PIPELINE_STATE_STREAM_DESC pipeDesc;
origPSO->Fill(pipeDesc);

// TODO: We provide our own render target here, but some of the PSOs use the original shader,
// which may write to a different number of channels (such as a R11G11B10 render target).
// This results in a D3D12 debug layer warning, but shouldn't be hazardous in practice since
// we restrict what we read from based on the source RT target. But maybe there's a way to
// gather the right data and avoid the debug layer warning.
pipeDesc.RTVFormats.NumRenderTargets = 1;
pipeDesc.RTVFormats.RTFormats[0] = DXGI_FORMAT_R32G32B32A32_FLOAT;
pipeDesc.DSVFormat = DXGI_FORMAT_D32_FLOAT_S8X24_UINT;
// TODO: Do we want to get the widest DSV format, or get it from callbackinfo/pixelhistoryresources?

// Modify the stencil state, so that only one fragment passes.
{
pipeDesc.DepthStencilState.StencilEnable = TRUE;
pipeDesc.DepthStencilState.FrontFace.StencilFunc = D3D12_COMPARISON_FUNC_EQUAL;
pipeDesc.DepthStencilState.FrontFace.StencilFailOp = D3D12_STENCIL_OP_INCR_SAT;
pipeDesc.DepthStencilState.FrontFace.StencilPassOp = D3D12_STENCIL_OP_INCR_SAT;
pipeDesc.DepthStencilState.FrontFace.StencilDepthFailOp = D3D12_STENCIL_OP_INCR_SAT;
pipeDesc.DepthStencilState.FrontFace.StencilReadMask = 0xff;
pipeDesc.DepthStencilState.FrontFace.StencilWriteMask = 0xff;
pipeDesc.DepthStencilState.BackFace = pipeDesc.DepthStencilState.FrontFace;
}

// TODO: The original pixel shader may have side effects such as UAV writes. The Vulkan impl
// removes side effects with shader patching but D3D12 does not support that yet.

HRESULT hr = m_pDevice->CreatePipeState(pipeDesc, &pipes.postModPipe);
if(FAILED(hr))
{
RDCERR("Failed to create PSO for pixel history.");
return pipes;
}

m_PSOsToDestroy.push_back(pipes.postModPipe);

for(uint32_t i = 0; i < D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT; ++i)
{
pipeDesc.BlendState.RenderTarget[i].BlendEnable = FALSE;
pipeDesc.BlendState.RenderTarget[i].RenderTargetWriteMask = D3D12_COLOR_WRITE_ENABLE_ALL;
}

{
pipeDesc.DepthStencilState.DepthBoundsTestEnable = FALSE;
pipeDesc.DepthStencilState.DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ALL;
pipeDesc.DepthStencilState.DepthFunc = D3D12_COMPARISON_FUNC_ALWAYS;
}

hr = m_pDevice->CreatePipeState(pipeDesc, &pipes.shaderOutPipe);
if(FAILED(hr))
{
RDCERR("Failed to create PSO for pixel history.");
return pipes;
}

m_PSOsToDestroy.push_back(pipes.shaderOutPipe);

{
pipeDesc.DepthStencilState.DepthEnable = FALSE;
pipeDesc.DepthStencilState.DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ZERO;
}

if(pipeDesc.GS.pShaderBytecode != NULL || pipeDesc.GS.BytecodeLength > 0)
{
RDCWARN("Can't get primitive ID at event %u due to geometry shader usage", eid);
}
else
{
bool dxil =
DXBC::DXBCContainer::CheckForDXIL(pipeDesc.VS.pShaderBytecode, pipeDesc.VS.BytecodeLength);

// TODO: This shader outputs to SV_Target0, will we need to modify the
// shader (or patch it) if the target image isn't RT0?
ID3DBlob *PrimIDPS = m_ShaderCache->GetPrimitiveIdShader(dxil);
pipeDesc.PS.pShaderBytecode = PrimIDPS->GetBufferPointer();
pipeDesc.PS.BytecodeLength = PrimIDPS->GetBufferSize();

hr = m_pDevice->CreatePipeState(pipeDesc, &pipes.primitiveIdPipe);
if(FAILED(hr))
{
RDCERR("Failed to create PSO for pixel history.");
return pipes;
}

m_PSOsToDestroy.push_back(pipes.primitiveIdPipe);
}

return pipes;
}

void PreDispatch(uint32_t eid, ID3D12GraphicsCommandListX *cmd) {}
bool PostDispatch(uint32_t eid, ID3D12GraphicsCommandListX *cmd) { return false; }
void PostRedispatch(uint32_t eid, ID3D12GraphicsCommandListX *cmd) {}

void PreMisc(uint32_t eid, ActionFlags flags, ID3D12GraphicsCommandListX *cmd) {}
bool PostMisc(uint32_t eid, ActionFlags flags, ID3D12GraphicsCommandListX *cmd) { return false; }
void PostRemisc(uint32_t eid, ActionFlags flags, ID3D12GraphicsCommandListX *cmd) {}

void PreCloseCommandList(ID3D12GraphicsCommandListX *cmd) {}
void AliasEvent(uint32_t primary, uint32_t alias) {}

uint32_t GetEventOffset(uint32_t eid)
{
auto it = m_EventIndices.find(eid);
RDCASSERT(it != m_EventIndices.end());
return it->second;
}

private:
// For each event, specifies where the occlusion query results start
std::map<uint32_t, uint32_t> m_EventIndices;
// Number of fragments for each event
std::map<uint32_t, uint32_t> m_EventFragments;
// Pre-modification values for events to initialize attachments to,
// so that we can get blended post-modification values.
std::map<uint32_t, ModificationValue> m_EventPremods;
// Number of fragments processed so far
uint32_t fragsProcessed = 0;

rdcarray<ID3D12PipelineState *> m_PSOsToDestroy;
};

bool D3D12DebugManager::PixelHistorySetupResources(D3D12PixelHistoryResources &resources,
WrappedID3D12Resource *targetImage,
const D3D12_RESOURCE_DESC &desc,
Expand Down

0 comments on commit caef1b9

Please sign in to comment.