Files
renderdoc/renderdoc/driver/d3d12/d3d12_pixelhistory.cpp
T
Jake Turner 932caeb7c8 Enable the D3D12 Pixel History feature
Removed the developer config option D3D12_PixelHistory
2024-02-15 06:01:46 +00:00

3179 lines
119 KiB
C++

/******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2023-2024 Baldur Karlsson
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
******************************************************************************/
// TODO: Broadly speaking, support for pixel history with multiple render targets
// bound needs more test coverage and testing to ensure proper implementation.
/*
* The general algorithm for pixel history is this:
*
* We get passed a list of all events that could have touched the target texture
* We replay all events (up to and including the last event that could have
* touched the target texture) with a number of callbacks:
*
* - First callback: Occlusion callback (D3D12OcclusionCallback)
* This callback performs an occlusion query around each draw event that was
* passed in. Execute the draw with a modified pipeline that disables most tests,
* and uses a fixed color fragment shader, so that we get a non 0 occlusion
* result even if a test failed for the event.
*
* After this callback we collect all events where occlusion result > 0 and all
* other non-draw events (copy, clear, resolve). We also filter out events where
* the image view used did not overlap in the array layer.
* The callbacks below will only deal with these events.
*
* - Second callback: Color and stencil callback (D3D12ColorAndStencilCallback)
* This callback retrieves color/depth values before and after each event, and
* uses a stencil increment to count the number of fragments for each event.
* We then copy color information and associated depth value, and resume a
* render pass, if there was one. Before each draw event we also execute the same
* draw twice with a stencil increment state: 1) with a fixed color fragment shader
* to count the number of fragments not accounting for shader discard, 2) with the
* original fragment shader to count the number of fragments accounting for shader discard.
*
* - Third callback: Tests failed callback (D3D12TestsFailedCallback)
* This callback is used to determine which tests (culling, depth, stencil, etc)
* failed (if any) for each draw event. This replays each draw event a number of times
* with an occlusion query for each test that might have failed (leaves the test
* under question in the original state, and disables all tests that come after).
*
* At this point we retrieve the stencil results that represent the number of fragments,
* and duplicate events that have multiple fragments.
*
* - Fourth callback: Per fragment callback (D3D12PixelHistoryPerFragmentCallback)
* This callback is used to get per fragment data for each event and fragment
* (primitive ID, shader output value, post event value for each fragment).
* For each fragment the draw is replayed 3 times:
* 1) with a fragment shader that outputs primitive ID only
* 2) with blending OFF, to get shader output value
* 3) with blending ON, to get post modification value
* For each such replay we set the stencil reference to the fragment number and set the
* stencil compare to equal, so it passes for that particular fragment only.
*
* - Fifth callback: Discarded fragments callback
* (D3D12PixelHistoryDiscardedFragmentsCallback)
* This callback is used to determine which individual fragments were discarded in a fragment
* shader. Only runs for the events where the number of fragments accounting for shader
* discard is less that the number of fragments not accounting for shader discard.
* This replays the particular fragment with an occlusion query.
*
* We slot the per-fragment data correctly accounting for the fragments that were discarded.
*/
#include "core/settings.h"
#include "driver/dxgi/dxgi_common.h"
#include "maths/formatpacking.h"
#include "d3d12_command_queue.h"
#include "d3d12_debug.h"
#include "d3d12_replay.h"
#include "d3d12_shader_cache.h"
struct D3D12CopyPixelParams
{
// The image being copied from
ID3D12Resource *srcImage;
// The source image format and format to use when copying. In most cases these are
// the same, but for some planar formats, the copy format will correspond to a single
// plane's typeless format, not the multi-plane format.
DXGI_FORMAT srcImageFormat;
DXGI_FORMAT copyFormat;
D3D12_RESOURCE_STATES srcImageState;
// Data about the pixel we want to copy
uint32_t x;
uint32_t y;
uint32_t mip;
uint32_t sample;
uint32_t planeSlice;
uint32_t arraySlice;
// Additional info to inform how to copy
bool depthcopy;
bool multisampled;
bool scratchBuffer;
};
struct D3D12PixelHistoryResources
{
ID3D12Resource *dstBuffer;
// Used for offscreen color/depth/stencil rendering for draw call events.
ID3D12Resource *colorImage;
D3D12Descriptor *colorDescriptor;
ID3D12Resource *dsImage;
D3D12Descriptor *dsDescriptor;
};
struct D3D12PixelHistoryCallbackInfo
{
// Original image for which pixel history is requested.
WrappedID3D12Resource *targetImage;
D3D12_RESOURCE_DESC targetDesc;
// Information about the location of the pixel for which history was requested.
Subresource targetSubresource;
CompType compType;
uint32_t x;
uint32_t y;
uint32_t sampleMask;
// Image used to get per fragment data.
ID3D12Resource *colorImage;
D3D12Descriptor *colorDescriptor;
// Image used to get stencil counts.
ID3D12Resource *dsImage;
D3D12Descriptor *dsDescriptor;
// Buffer used to copy colour and depth information
ID3D12Resource *dstBuffer;
};
struct D3D12PixelHistoryValue
{
// Max size is 4 component with 8 byte component width
uint8_t color[32];
union
{
uint32_t udepth;
float fdepth;
} depth;
int8_t stencil;
uint8_t padding[3 + 8];
};
struct D3D12EventInfo
{
D3D12PixelHistoryValue premod;
D3D12PixelHistoryValue postmod;
uint8_t dsWithoutShaderDiscard[8];
uint8_t padding[8];
uint8_t dsWithShaderDiscard[8];
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;
ID3D12PipelineState *originalShaderStencil;
};
enum D3D12PixelHistoryTests : uint32_t
{
TestEnabled_DepthClipping = 1 << 0,
TestEnabled_Culling = 1 << 1,
TestEnabled_Scissor = 1 << 2, // Scissor test always enabled with D3D12
TestEnabled_SampleMask = 1 << 3,
TestEnabled_DepthBounds = 1 << 4,
TestEnabled_StencilTesting = 1 << 5,
TestEnabled_DepthTesting = 1 << 6,
TestEnabled_FragmentDiscard = 1 << 7,
Blending_Enabled = 1 << 8,
UnboundFragmentShader = 1 << 9,
TestMustFail_Scissor = 1 << 11,
TestMustPass_Scissor = 1 << 12,
TestMustFail_DepthTesting = 1 << 13,
TestMustFail_StencilTesting = 1 << 14,
TestMustFail_SampleMask = 1 << 15,
DepthTest_Shift = 29,
DepthTest_Always = 0U << DepthTest_Shift,
DepthTest_Never = 1U << DepthTest_Shift,
DepthTest_Equal = 2U << DepthTest_Shift,
DepthTest_NotEqual = 3U << DepthTest_Shift,
DepthTest_Less = 4U << DepthTest_Shift,
DepthTest_LessEqual = 5U << DepthTest_Shift,
DepthTest_Greater = 6U << DepthTest_Shift,
DepthTest_GreaterEqual = 7U << DepthTest_Shift,
};
namespace
{
bool IsDepthFormat(D3D12_RESOURCE_DESC desc, CompType typeCast)
{
// TODO: This function might need to handle where the resource is typeless but is actually depth
if(IsDepthFormat(desc.Format))
return true;
if(typeCast == CompType::Depth && (desc.Flags & D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL))
return true;
return false;
}
DXGI_FORMAT GetDepthCopyFormat(DXGI_FORMAT format)
{
if(format == DXGI_FORMAT_D16_UNORM)
return DXGI_FORMAT_R16_TYPELESS;
return DXGI_FORMAT_R32_TYPELESS;
}
}
// Helper function to copy a single pixel out of a source texture, which will handle any texture
// type and binding type, doing any copying as needed. Writes the result to a given buffer UAV.
void D3D12DebugManager::PixelHistoryCopyPixel(ID3D12GraphicsCommandListX *cmd,
ID3D12Resource *dstBuffer, D3D12CopyPixelParams &p,
size_t offset)
{
D3D12RenderState &state = m_pDevice->GetQueue()->GetCommandData()->GetCurRenderState();
D3D12RenderState prevState = state;
state.pipe = GetResID(m_PixelHistoryCopyPso);
state.compute.rootsig = GetResID(m_PixelHistoryCopySig);
bool floatTex = false, uintTex = false, intTex = false;
DXGI_FORMAT srvFormat = p.srcImageFormat;
bool uintStencil = p.depthcopy && p.planeSlice == 1 &&
(p.srcImageFormat == DXGI_FORMAT_X24_TYPELESS_G8_UINT ||
p.srcImageFormat == DXGI_FORMAT_X32_TYPELESS_G8X24_UINT);
if(IsUIntFormat(p.srcImageFormat) || uintStencil)
{
uintTex = true;
srvFormat = GetUIntTypedFormat(srvFormat);
}
else if(IsIntFormat(p.srcImageFormat))
{
intTex = true;
srvFormat = GetSIntTypedFormat(srvFormat);
}
else
{
floatTex = true;
srvFormat = GetFloatTypedFormat(srvFormat);
}
UINT srvIndex = 0;
UINT uavIndex = 0;
// SRV indices by type:
// 0 - depth
// 1 - stencil
// 2 - depth MSAA
// 3 - stencil MSAA
// 4 - float
// 5 - float MSAA
// 6 - uint
// 7 - uint MSAA
// 8 - int
// 9 - int MSAA
// UAV indices by type:
// 0 - depth
// 1 - stencil
// 2 - float
// 3 - uint
// 4 - int
// Determine which SRV we will read from in the shader
if(p.depthcopy)
{
srvIndex = p.planeSlice;
if(p.multisampled)
srvIndex += 2;
// This should already be the SRV format for depth/stencil copies
srvFormat = p.srcImageFormat;
}
else
{
if(floatTex)
srvIndex = 4;
else if(uintTex)
srvIndex = 6;
else if(intTex)
srvIndex = 8;
if(p.multisampled)
srvIndex++;
}
// Determine which UAV we will write to in the shader
if(p.depthcopy && p.planeSlice == 0)
uavIndex = 0;
else if(p.depthcopy && p.planeSlice == 1)
uavIndex = 1;
else if(floatTex)
uavIndex = 2;
else if(uintTex)
uavIndex = 3;
else
uavIndex = 4;
struct CopyPixelShaderInput
{
Vec4u src_coord; // x, y, mip/sample, slice
uint32_t dst_slot;
uint32_t copy_depth;
uint32_t copy_stencil;
uint32_t multisampled;
uint32_t is_float;
uint32_t is_uint;
uint32_t is_int;
} inputData;
inputData.src_coord = {p.x, p.y, p.multisampled ? p.sample : p.mip, p.arraySlice};
inputData.multisampled = p.multisampled;
inputData.is_float = floatTex;
inputData.is_uint = uintTex;
inputData.is_int = intTex;
inputData.dst_slot = (uint32_t)(offset / sizeof(float));
inputData.copy_depth = p.depthcopy && p.planeSlice == 0;
inputData.copy_stencil = p.depthcopy && p.planeSlice == 1;
// When copying a scratch buffer, we need to use a different SRV range from the heap
CBVUAVSRVSlot srvStartSlot =
p.scratchBuffer ? FIRST_PIXELHISTORY_SCRATCH_SRV : FIRST_PIXELHISTORY_SRV;
D3D12_CPU_DESCRIPTOR_HANDLE srv = m_pDevice->GetDebugManager()->GetCPUHandle(srvStartSlot);
D3D12_CPU_DESCRIPTOR_HANDLE uav =
m_pDevice->GetDebugManager()->GetCPUHandle(FIRST_PIXELHISTORY_UAV);
m_pDevice->GetDebugManager()->SetDescriptorHeaps(state.heaps, true, false);
state.compute.sigelems = {
D3D12RenderState::SignatureElement(
eRootCBV, m_pDevice->GetDebugManager()->UploadConstants(&inputData, sizeof(inputData))),
D3D12RenderState::SignatureElement(eRootTable, uav),
D3D12RenderState::SignatureElement(eRootTable, srv),
};
srv.ptr += srvIndex * sizeof(D3D12Descriptor);
D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {};
srvDesc.ViewDimension =
p.multisampled ? D3D12_SRV_DIMENSION_TEXTURE2DMS : D3D12_SRV_DIMENSION_TEXTURE2D;
srvDesc.Format = p.srcImageFormat;
srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
if(!p.multisampled)
{
srvDesc.Texture2D.MipLevels = 1;
srvDesc.Texture2D.PlaneSlice = p.planeSlice;
}
m_pDevice->CreateShaderResourceView(p.srcImage, &srvDesc, srv);
uav.ptr += uavIndex * sizeof(D3D12Descriptor);
D3D12_UNORDERED_ACCESS_VIEW_DESC uavDesc = {};
uavDesc.ViewDimension = D3D12_UAV_DIMENSION_BUFFER;
uavDesc.Format = DXGI_FORMAT_UNKNOWN;
uavDesc.Buffer.NumElements = (UINT)(dstBuffer->GetDesc().Width / sizeof(float));
uavDesc.Buffer.FirstElement = 0;
uavDesc.Buffer.StructureByteStride = sizeof(float);
m_pDevice->CreateUnorderedAccessView(dstBuffer, NULL, &uavDesc, uav);
state.ApplyState(m_pDevice, cmd);
cmd->Dispatch(1, 1, 1);
state = prevState;
state.ApplyState(m_pDevice, cmd);
}
// D3D12PixelHistoryShaderCache manages temporary shaders created for pixel history.
struct D3D12PixelHistoryShaderCache
{
D3D12PixelHistoryShaderCache(WrappedID3D12Device *device, ID3DBlob *PersistentPrimIDPS,
ID3DBlob *PersistentPrimIDPSDxil,
ID3DBlob *FixedColorPS[D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT],
ID3DBlob *FixedColorPSDxil[D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT])
: m_pDevice(device), m_PrimIDPS(PersistentPrimIDPS), m_PrimIDPSDxil(PersistentPrimIDPSDxil)
{
for(int i = 0; i < D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT; ++i)
{
m_FixedColorPS[i] = FixedColorPS[i];
m_FixedColorPSDxil[i] = FixedColorPSDxil[i];
}
}
~D3D12PixelHistoryShaderCache() {}
// Returns a fragment shader that outputs a fixed color
ID3DBlob *GetFixedColorShader(bool dxil, int outputIndex)
{
return dxil ? m_FixedColorPSDxil[outputIndex] : m_FixedColorPS[outputIndex];
}
// Returns a fragment shader that outputs primitive ID
ID3DBlob *GetPrimitiveIdShader(bool dxil) { return dxil ? m_PrimIDPSDxil : m_PrimIDPS; }
// TODO: This class should also manage any shader replacements needed during pixel history
private:
WrappedID3D12Device *m_pDevice;
ID3DBlob *m_PrimIDPS;
ID3DBlob *m_PrimIDPSDxil;
ID3DBlob *m_FixedColorPS[D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT];
ID3DBlob *m_FixedColorPSDxil[D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT];
};
// D3D12PixelHistoryCallback is a generic D3D12ActionCallback that can be used
// for pixel history replays.
struct D3D12PixelHistoryCallback : public D3D12ActionCallback
{
D3D12PixelHistoryCallback(WrappedID3D12Device *device, D3D12PixelHistoryShaderCache *shaderCache,
const D3D12PixelHistoryCallbackInfo &callbackInfo,
ID3D12QueryHeap *occlusionQueryHeap)
: m_pDevice(device),
m_ShaderCache(shaderCache),
m_CallbackInfo(callbackInfo),
m_OcclusionQueryHeap(occlusionQueryHeap)
{
m_pDevice->GetQueue()->GetCommandData()->m_ActionCallback = this;
}
virtual ~D3D12PixelHistoryCallback()
{
m_pDevice->GetQueue()->GetCommandData()->m_ActionCallback = NULL;
}
protected:
// Update the given scissor to just the pixel for which pixel history was requested.
void ScissorToPixel(const D3D12_VIEWPORT &view, D3D12_RECT &scissor)
{
float fx = (float)m_CallbackInfo.x;
float fy = (float)m_CallbackInfo.y;
float y_start = view.TopLeftY;
float y_end = view.TopLeftY + view.Height;
if(view.Height < 0)
{
// Handle negative viewport which was added in Agility SDK 1.602.0
y_start = view.TopLeftY + view.Height;
y_end = view.TopLeftY;
}
if(fx < view.TopLeftX || fy < y_start || fx >= view.TopLeftX + view.Width || fy >= y_end)
{
scissor.left = scissor.top = scissor.right = scissor.bottom = 0;
}
else
{
scissor.left = m_CallbackInfo.x;
scissor.top = m_CallbackInfo.y;
scissor.right = scissor.left + 1;
scissor.bottom = scissor.top + 1;
}
}
// Intersects the originalScissor and newScissor and writes intersection to the newScissor.
// newScissor always covers a single pixel, so if originalScissor does not touch that pixel
// returns an empty scissor.
void IntersectScissors(const D3D12_RECT &originalScissor, D3D12_RECT &newScissor)
{
RDCASSERT(newScissor.right == newScissor.left + 1);
RDCASSERT(newScissor.bottom == newScissor.top + 1);
if(originalScissor.left > newScissor.left || originalScissor.right < newScissor.right ||
originalScissor.top > newScissor.top || originalScissor.bottom < newScissor.bottom)
{
// Scissor does not touch our target pixel, make it empty
newScissor.left = newScissor.top = newScissor.right = newScissor.bottom = 0;
}
}
bool IsPSOUsingDXIL(D3D12_EXPANDED_PIPELINE_STATE_STREAM_DESC &pipeDesc)
{
// Check early stages first, for both VS and MS, but use PS as a fallback. If any shader stage
// uses DXIL, they all need to use DXIL.
return DXBC::DXBCContainer::CheckForDXIL(pipeDesc.VS.pShaderBytecode, pipeDesc.VS.BytecodeLength) ||
DXBC::DXBCContainer::CheckForDXIL(pipeDesc.MS.pShaderBytecode, pipeDesc.MS.BytecodeLength) ||
DXBC::DXBCContainer::CheckForDXIL(pipeDesc.PS.pShaderBytecode, pipeDesc.PS.BytecodeLength);
}
// ModifyPSOForStencilIncrement modifies the provided pipeDesc, by disabling depth test
// and write, stencil is set to always pass and increment, scissor is set to scissor around
// the target pixel, and all color modifications are disabled.
// Optionally disables other tests like culling, depth bounds.
void ModifyPSOForStencilIncrement(uint32_t eid, D3D12_EXPANDED_PIPELINE_STATE_STREAM_DESC &pipeDesc,
bool disableTests)
{
pipeDesc.DepthStencilState.DepthEnable = FALSE;
pipeDesc.DepthStencilState.DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ZERO;
if(disableTests)
{
pipeDesc.RasterizerState.CullMode = D3D12_CULL_MODE_NONE;
pipeDesc.RasterizerState.DepthClipEnable = FALSE;
pipeDesc.DepthStencilState.DepthBoundsTestEnable = FALSE;
}
// TODO: Get from callbackinfo/pixelhistoryresources?
pipeDesc.DSVFormat = DXGI_FORMAT_D32_FLOAT_S8X24_UINT;
// TODO: If the original depth buffer doesn't have stencil, this will not work as expected.
// We will need to detect that and switch to a DSV with a stencil for some pixel history passes.
// Set up the stencil state.
{
pipeDesc.DepthStencilState.StencilEnable = TRUE;
pipeDesc.DepthStencilState.FrontFace.StencilFunc = D3D12_COMPARISON_FUNC_ALWAYS;
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;
// Stencil ref is set separately from the PSO
}
// Narrow on the specific pixel and sample.
{
pipeDesc.SampleMask = m_CallbackInfo.sampleMask;
}
// Turn off all color modifications.
{
for(uint32_t i = 0; i < D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT; i++)
pipeDesc.BlendState.RenderTarget[i].RenderTargetWriteMask = 0;
}
}
void CopyImagePixel(ID3D12GraphicsCommandListX *cmd, D3D12CopyPixelParams &p, size_t offset)
{
uint32_t baseMip = m_CallbackInfo.targetSubresource.mip;
uint32_t baseSlice = m_CallbackInfo.targetSubresource.slice;
// The images that are created specifically for evaluating pixel history are
// already based on the target mip/slice
if(p.srcImage == m_CallbackInfo.colorImage || p.srcImage == m_CallbackInfo.dsImage)
{
// TODO: Is this always true when we call CopyImagePixel? Also need to test this case with MSAA
baseMip = 0;
baseSlice = 0;
}
// For pipeline barriers.
D3D12_RESOURCE_BARRIER barrier = {};
barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
barrier.Transition.pResource = p.srcImage;
barrier.Transition.StateBefore = p.srcImageState;
barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_COPY_SOURCE;
barrier.Transition.Subresource =
D3D12CalcSubresource(baseMip, baseSlice, p.planeSlice, m_CallbackInfo.targetDesc.MipLevels,
m_CallbackInfo.targetDesc.DepthOrArraySize);
// Multi-sampled images can't call CopyTextureRegion for a single sample, so instead
// copy using a compute shader into a staging image first
if(p.multisampled)
{
// For pipeline barriers.
D3D12_RESOURCE_BARRIER barriers[2] = {};
barriers[0] = barrier;
barriers[0].Transition.StateAfter = D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE;
// Validation will complain if we don't transition all subresources for an SRV.
barriers[0].Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
barriers[1].Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
barriers[1].Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
barriers[1].Transition.pResource = m_CallbackInfo.dstBuffer;
barriers[1].Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_DEST;
barriers[1].Transition.StateAfter = D3D12_RESOURCE_STATE_UNORDERED_ACCESS;
barriers[1].Transition.Subresource = 0;
cmd->ResourceBarrier(2, barriers);
m_pDevice->GetDebugManager()->PixelHistoryCopyPixel(cmd, m_CallbackInfo.dstBuffer, p, offset);
std::swap(barriers[0].Transition.StateBefore, barriers[0].Transition.StateAfter);
std::swap(barriers[1].Transition.StateBefore, barriers[1].Transition.StateAfter);
cmd->ResourceBarrier(2, barriers);
}
else
{
cmd->ResourceBarrier(1, &barrier);
D3D12_TEXTURE_COPY_LOCATION dst = {}, src = {};
src.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
src.pResource = p.srcImage;
src.SubresourceIndex = barrier.Transition.Subresource;
// Copy into a buffer, but treat the footprint as the same format as the target image
uint32_t elementSize = GetByteSize(0, 0, 0, p.copyFormat, 0);
// Use Offset to get to the nearest 16KB
UINT64 footprintOffset = (offset >> 14) << 14;
UINT64 offsetRemaider = offset - footprintOffset;
UINT dstX = (UINT)(offsetRemaider / elementSize);
dst.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT;
dst.pResource = m_CallbackInfo.dstBuffer;
dst.PlacedFootprint.Offset = footprintOffset;
dst.PlacedFootprint.Footprint.Width = dstX + 1;
dst.PlacedFootprint.Footprint.Height = 1;
dst.PlacedFootprint.Footprint.Depth = 1;
dst.PlacedFootprint.Footprint.Format = p.copyFormat;
dst.PlacedFootprint.Footprint.RowPitch =
AlignUp(dst.PlacedFootprint.Footprint.Width * elementSize,
(uint32_t)D3D12_TEXTURE_DATA_PITCH_ALIGNMENT);
D3D12_BOX srcBox = {};
srcBox.left = p.x;
srcBox.top = p.y;
srcBox.right = srcBox.left + 1;
srcBox.bottom = srcBox.top + 1;
srcBox.front = 0;
srcBox.back = 1;
RDCASSERT((offset % elementSize) == 0);
cmd->CopyTextureRegion(&dst, dstX, 0, 0, &src, &srcBox);
std::swap(barrier.Transition.StateBefore, barrier.Transition.StateAfter);
cmd->ResourceBarrier(1, &barrier);
}
}
// Returns the render target index that corresponds to the target image for pixel history.
uint32_t GetPixelHistoryRenderTargetIndex(const D3D12RenderState &renderstate)
{
ResourceId targetId = GetResID(m_CallbackInfo.targetImage);
if(renderstate.dsv.GetResResourceId() == targetId)
return 0;
uint32_t targetIndex = 0;
for(uint32_t i = 0; i < renderstate.rts.size(); i++)
{
ResourceId id = renderstate.rts[i].GetResResourceId();
if(id == targetId)
{
targetIndex = i;
break;
}
}
return targetIndex;
}
D3D12_RESOURCE_STATES GetImageState(ID3D12Resource *target, uint32_t planeSlice,
D3D12_RESOURCE_STATES fallback)
{
RDCASSERTMSG("This function is not meant for use with the non-capture resources.",
target != m_CallbackInfo.colorImage && target != m_CallbackInfo.dsImage);
D3D12CommandData &cmdData = *m_pDevice->GetQueue()->GetCommandData();
SubresourceStateVector targetStates =
cmdData.m_BakedCmdListInfo[cmdData.m_LastCmdListID].GetState(m_pDevice, GetResID(target));
uint32_t baseMip = m_CallbackInfo.targetSubresource.mip;
uint32_t baseSlice = m_CallbackInfo.targetSubresource.slice;
uint32_t subresource =
D3D12CalcSubresource(baseMip, baseSlice, planeSlice, m_CallbackInfo.targetDesc.MipLevels,
m_CallbackInfo.targetDesc.DepthOrArraySize);
if(targetStates.size() > subresource && targetStates[subresource].IsStates())
{
return targetStates[subresource].ToStates();
}
return fallback;
}
WrappedID3D12Device *m_pDevice;
D3D12PixelHistoryShaderCache *m_ShaderCache;
D3D12PixelHistoryCallbackInfo m_CallbackInfo;
ID3D12QueryHeap *m_OcclusionQueryHeap;
};
// D3D12OcclusionCallback callback is used to determine which draw events might
// have modified the pixel by doing an occlusion query.
struct D3D12OcclusionCallback : public D3D12PixelHistoryCallback
{
D3D12OcclusionCallback(WrappedID3D12Device *device, D3D12PixelHistoryShaderCache *shaderCache,
const D3D12PixelHistoryCallbackInfo &callbackInfo,
ID3D12QueryHeap *occlusionQueryHeap, const rdcarray<EventUsage> &allEvents)
: D3D12PixelHistoryCallback(device, shaderCache, callbackInfo, occlusionQueryHeap)
{
for(size_t i = 0; i < allEvents.size(); i++)
m_Events.push_back(allEvents[i].eventId);
}
~D3D12OcclusionCallback()
{
for(auto it = m_PipeCache.begin(); it != m_PipeCache.end(); ++it)
SAFE_RELEASE(it->second);
}
void PreDraw(uint32_t eid, ID3D12GraphicsCommandListX *cmd)
{
if(!m_Events.contains(eid))
return;
D3D12MarkerRegion::Set(cmd, StringFormat::Fmt("Replaying event %u", eid));
m_SavedState = m_pDevice->GetQueue()->GetCommandData()->GetCurRenderState();
D3D12RenderState pipeState = m_pDevice->GetQueue()->GetCommandData()->GetCurRenderState();
pipeState.rts.clear();
pipeState.dsv = *m_CallbackInfo.dsDescriptor;
ID3D12PipelineState *pso = GetPixelOcclusionPipeline(eid, pipeState);
pipeState.pipe = GetResID(pso);
// set the scissor
for(uint32_t i = 0; i < pipeState.views.size(); i++)
ScissorToPixel(pipeState.views[i], pipeState.scissors[i]);
pipeState.stencilRefFront = 0;
pipeState.stencilRefBack = 0;
pipeState.ApplyState(m_pDevice, cmd);
uint32_t occlIndex = (uint32_t)m_OcclusionQueries.size();
cmd->BeginQuery(m_OcclusionQueryHeap, D3D12_QUERY_TYPE_OCCLUSION, occlIndex);
}
bool PostDraw(uint32_t eid, ID3D12GraphicsCommandListX *cmd)
{
if(!m_Events.contains(eid))
return false;
uint32_t occlIndex = (uint32_t)m_OcclusionQueries.size();
cmd->EndQuery(m_OcclusionQueryHeap, D3D12_QUERY_TYPE_OCCLUSION, occlIndex);
m_OcclusionQueries.insert(std::make_pair(eid, occlIndex));
m_SavedState.ApplyState(m_pDevice, cmd);
return false;
}
void PostRedraw(uint32_t eid, ID3D12GraphicsCommandListX *cmd) {}
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) {}
void FetchOcclusionResults()
{
if(m_OcclusionQueries.size() == 0)
return;
D3D12_HEAP_PROPERTIES heapProps;
heapProps.Type = D3D12_HEAP_TYPE_READBACK;
heapProps.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
heapProps.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;
heapProps.CreationNodeMask = 1;
heapProps.VisibleNodeMask = 1;
D3D12_RESOURCE_DESC bufDesc;
bufDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
bufDesc.Alignment = 0;
bufDesc.Width = sizeof(uint64_t) * m_OcclusionQueries.size();
bufDesc.Height = 1;
bufDesc.DepthOrArraySize = 1;
bufDesc.MipLevels = 1;
bufDesc.Format = DXGI_FORMAT_UNKNOWN;
bufDesc.SampleDesc.Count = 1;
bufDesc.SampleDesc.Quality = 0;
bufDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
bufDesc.Flags = D3D12_RESOURCE_FLAG_NONE;
ID3D12Resource *readbackBuf = NULL;
HRESULT hr = m_pDevice->CreateCommittedResource(&heapProps, D3D12_HEAP_FLAG_NONE, &bufDesc,
D3D12_RESOURCE_STATE_COPY_DEST, NULL,
__uuidof(ID3D12Resource), (void **)&readbackBuf);
m_pDevice->CheckHRESULT(hr);
if(FAILED(hr))
{
RDCERR("Failed to create query readback buffer HRESULT: %s", ToStr(hr).c_str());
return;
}
ID3D12GraphicsCommandListX *list = m_pDevice->GetNewList();
if(!list)
return;
list->ResolveQueryData(m_OcclusionQueryHeap, D3D12_QUERY_TYPE_OCCLUSION, 0,
(UINT)m_OcclusionQueries.size(), readbackBuf, 0);
list->Close();
m_pDevice->ExecuteLists();
m_pDevice->FlushLists();
D3D12_RANGE range;
range.Begin = 0;
range.End = (SIZE_T)bufDesc.Width;
uint64_t *data;
hr = readbackBuf->Map(0, &range, (void **)&data);
m_pDevice->CheckHRESULT(hr);
if(FAILED(hr))
{
RDCERR("Failed to map query heap data HRESULT: %s", ToStr(hr).c_str());
SAFE_RELEASE(readbackBuf);
return;
}
m_OcclusionResults.resize(m_OcclusionQueries.size());
for(size_t i = 0; i < m_OcclusionResults.size(); ++i)
m_OcclusionResults[i] = data[i];
readbackBuf->Unmap(0, NULL);
SAFE_RELEASE(readbackBuf);
}
uint64_t GetOcclusionResult(uint32_t eventId)
{
auto it = m_OcclusionQueries.find(eventId);
if(it == m_OcclusionQueries.end())
return 0;
RDCASSERT(it->second < m_OcclusionResults.size());
return m_OcclusionResults[it->second];
}
private:
ID3D12PipelineState *GetPixelOcclusionPipeline(uint32_t eid, D3D12RenderState &state)
{
auto it = m_PipeCache.find(state.pipe);
if(it != m_PipeCache.end())
return it->second;
WrappedID3D12PipelineState *origPSO =
m_pDevice->GetResourceManager()->GetCurrentAs<WrappedID3D12PipelineState>(state.pipe);
if(origPSO == NULL)
{
RDCERR("Failed to retrieve original PSO for pixel history.");
return NULL;
}
D3D12_EXPANDED_PIPELINE_STATE_STREAM_DESC pipeDesc;
origPSO->Fill(pipeDesc);
ModifyPSOForStencilIncrement(eid, pipeDesc, true);
bool dxil = IsPSOUsingDXIL(pipeDesc);
ID3DBlob *psBlob =
m_pDevice->GetShaderCache()->MakeFixedColShader(D3D12ShaderCache::HIGHLIGHT, dxil);
if(psBlob == NULL)
{
RDCERR("Failed to create fixed color shader for pixel history.");
return NULL;
}
pipeDesc.PS.pShaderBytecode = psBlob->GetBufferPointer();
pipeDesc.PS.BytecodeLength = psBlob->GetBufferSize();
ID3D12PipelineState *pso = NULL;
HRESULT hr = m_pDevice->CreatePipeState(pipeDesc, &pso);
if(FAILED(hr))
{
RDCERR("Failed to create PSO for pixel history.");
SAFE_RELEASE(psBlob);
return NULL;
}
m_PipeCache.insert(std::make_pair(state.pipe, pso));
SAFE_RELEASE(psBlob);
return pso;
}
private:
D3D12RenderState m_SavedState;
std::map<ResourceId, ID3D12PipelineState *> m_PipeCache;
rdcarray<uint32_t> m_Events;
// Key is event ID, and value is an index of where the occlusion result.
std::map<uint32_t, uint32_t> m_OcclusionQueries;
rdcarray<uint64_t> m_OcclusionResults;
};
struct D3D12ColorAndStencilCallback : public D3D12PixelHistoryCallback
{
D3D12ColorAndStencilCallback(WrappedID3D12Device *device, D3D12PixelHistoryShaderCache *shaderCache,
const D3D12PixelHistoryCallbackInfo &callbackInfo,
const rdcarray<uint32_t> &events)
: D3D12PixelHistoryCallback(device, shaderCache, callbackInfo, NULL), m_Events(events)
{
}
~D3D12ColorAndStencilCallback()
{
for(auto it = m_PipeCache.begin(); it != m_PipeCache.end(); ++it)
{
SAFE_RELEASE(it->second.fixedShaderStencil);
SAFE_RELEASE(it->second.originalShaderStencil);
}
}
void PreDraw(uint32_t eid, ID3D12GraphicsCommandListX *cmd)
{
if(!m_Events.contains(eid))
return;
D3D12MarkerRegion::Set(cmd, StringFormat::Fmt("Replaying event %u", eid));
m_SavedState = m_pDevice->GetQueue()->GetCommandData()->GetCurRenderState();
D3D12RenderState &pipeState = m_pDevice->GetQueue()->GetCommandData()->GetCurRenderState();
// Get pre-modification values
size_t storeOffset = m_EventIndices.size() * sizeof(D3D12EventInfo);
CopyPixel(eid, cmd, storeOffset + offsetof(struct D3D12EventInfo, premod));
{
D3D12PipelineReplacements replacements = GetPipelineReplacements(eid, pipeState);
// Set scissor to only the pixel we're getting history for
for(uint32_t i = 0; i < pipeState.views.size(); i++)
ScissorToPixel(pipeState.views[i], pipeState.scissors[i]);
// Replay the draw with the original shader, but with state changed to
// count fragments with stencil
pipeState.rts.clear();
pipeState.dsv = *m_CallbackInfo.dsDescriptor;
pipeState.stencilRefFront = 0;
pipeState.stencilRefBack = 0;
pipeState.pipe = GetResID(replacements.originalShaderStencil);
pipeState.ApplyState(m_pDevice, cmd);
ReplayDraw(cmd, eid, true);
D3D12CopyPixelParams params = {};
params.scratchBuffer = true;
params.srcImage = m_CallbackInfo.dsImage;
params.srcImageState = D3D12_RESOURCE_STATE_DEPTH_WRITE;
params.srcImageFormat = GetDepthSRVFormat(m_CallbackInfo.dsImage->GetDesc().Format, 1);
params.copyFormat = DXGI_FORMAT_R8_TYPELESS;
params.x = m_CallbackInfo.x;
params.y = m_CallbackInfo.y;
params.sample = m_CallbackInfo.targetSubresource.sample;
params.mip = m_CallbackInfo.targetSubresource.mip;
params.arraySlice = m_CallbackInfo.targetSubresource.slice;
params.depthcopy = true;
params.planeSlice = 1;
params.multisampled = m_CallbackInfo.targetDesc.SampleDesc.Count > 1;
CopyImagePixel(cmd, params,
storeOffset + offsetof(struct D3D12EventInfo, dsWithoutShaderDiscard));
// TODO: In between draws, do we need to reset the depth/stencil value?
// Replay the draw with a fixed color shader that never discards, and stencil
// increment to count number of fragments. We will get the number of fragments
// not accounting for shader discard.
pipeState.pipe = GetResID(replacements.fixedShaderStencil);
pipeState.ApplyState(m_pDevice, cmd);
ReplayDraw(cmd, eid, true);
CopyImagePixel(cmd, params, storeOffset + offsetof(struct D3D12EventInfo, dsWithShaderDiscard));
}
// Restore the state.
pipeState = m_SavedState;
pipeState.ApplyState(m_pDevice, cmd);
}
bool PostDraw(uint32_t eid, ID3D12GraphicsCommandListX *cmd)
{
if(!m_Events.contains(eid))
return false;
// Get post-modification values
size_t storeOffset =
m_EventIndices.size() * sizeof(D3D12EventInfo) + offsetof(struct D3D12EventInfo, postmod);
CopyPixel(eid, cmd, storeOffset);
m_EventIndices.insert(std::make_pair(eid, m_EventIndices.size()));
D3D12MarkerRegion::Set(cmd, StringFormat::Fmt("Finished replaying event %u", eid));
return false;
}
void PostRedraw(uint32_t eid, ID3D12GraphicsCommandListX *cmd) {}
void PreDispatch(uint32_t eid, ID3D12GraphicsCommandListX *cmd)
{
if(!m_Events.contains(eid))
return;
D3D12MarkerRegion::Set(cmd, StringFormat::Fmt("Replaying event %u", eid));
size_t storeOffset =
m_EventIndices.size() * sizeof(D3D12EventInfo) + offsetof(struct D3D12EventInfo, premod);
m_SavedState = m_pDevice->GetQueue()->GetCommandData()->GetCurRenderState();
CopyPixel(eid, cmd, storeOffset);
}
bool PostDispatch(uint32_t eid, ID3D12GraphicsCommandListX *cmd)
{
if(!m_Events.contains(eid))
return false;
size_t storeOffset =
m_EventIndices.size() * sizeof(D3D12EventInfo) + offsetof(struct D3D12EventInfo, postmod);
CopyPixel(eid, cmd, storeOffset);
m_EventIndices.insert(std::make_pair(eid, m_EventIndices.size()));
D3D12MarkerRegion::Set(cmd, StringFormat::Fmt("Finished replaying event %u", eid));
return false;
}
void PostRedispatch(uint32_t eid, ID3D12GraphicsCommandListX *cmd) {}
void PreMisc(uint32_t eid, ActionFlags flags, ID3D12GraphicsCommandListX *cmd)
{
PreDispatch(eid, cmd);
}
bool PostMisc(uint32_t eid, ActionFlags flags, ID3D12GraphicsCommandListX *cmd)
{
return PostDispatch(eid, cmd);
}
void PostRemisc(uint32_t eid, ActionFlags flags, ID3D12GraphicsCommandListX *cmd) {}
void PreCloseCommandList(ID3D12GraphicsCommandListX *cmd) {}
void AliasEvent(uint32_t primary, uint32_t alias)
{
RDCWARN(
"Alised events are not supported, results might be inaccurate. "
"Primary event id: %u, "
"alias: %u.",
primary, alias);
}
int32_t GetEventIndex(uint32_t eventId)
{
auto it = m_EventIndices.find(eventId);
if(it == m_EventIndices.end())
// Most likely a secondary command buffer event for which there is no information.
return -1;
RDCASSERT(it != m_EventIndices.end());
return (int32_t)it->second;
}
DXGI_FORMAT GetDepthFormat(uint32_t eventId)
{
if(IsDepthFormat(m_CallbackInfo.targetDesc.Format))
return m_CallbackInfo.targetDesc.Format;
auto it = m_DepthFormats.find(eventId);
if(it == m_DepthFormats.end())
return DXGI_FORMAT_UNKNOWN;
return it->second;
}
private:
void CopyPixel(uint32_t eid, ID3D12GraphicsCommandListX *cmd, size_t offset)
{
D3D12CopyPixelParams targetCopyParams = {};
targetCopyParams.scratchBuffer = false;
targetCopyParams.srcImage = m_CallbackInfo.targetImage;
targetCopyParams.srcImageFormat = m_CallbackInfo.targetDesc.Format;
targetCopyParams.copyFormat = m_CallbackInfo.targetDesc.Format;
targetCopyParams.x = m_CallbackInfo.x;
targetCopyParams.y = m_CallbackInfo.y;
targetCopyParams.sample = m_CallbackInfo.targetSubresource.sample;
targetCopyParams.mip = m_CallbackInfo.targetSubresource.mip;
targetCopyParams.arraySlice = m_CallbackInfo.targetSubresource.slice;
targetCopyParams.multisampled = (m_CallbackInfo.targetDesc.SampleDesc.Count != 1);
D3D12_RESOURCE_STATES fallback = D3D12_RESOURCE_STATE_RENDER_TARGET;
if(IsDepthFormat(m_CallbackInfo.targetDesc, m_CallbackInfo.compType))
{
fallback = m_SavedState.dsv.GetDSV().Flags & D3D12_DSV_FLAG_READ_ONLY_DEPTH
? D3D12_RESOURCE_STATE_DEPTH_READ
: D3D12_RESOURCE_STATE_DEPTH_WRITE;
targetCopyParams.srcImageFormat = GetDepthSRVFormat(m_CallbackInfo.targetDesc.Format, 0);
targetCopyParams.depthcopy = true;
targetCopyParams.copyFormat = GetDepthCopyFormat(m_CallbackInfo.targetDesc.Format);
offset += offsetof(struct D3D12PixelHistoryValue, depth);
}
targetCopyParams.srcImageState =
GetImageState(m_CallbackInfo.targetImage, targetCopyParams.planeSlice, fallback);
CopyImagePixel(cmd, targetCopyParams, offset);
if(IsDepthAndStencilFormat(m_CallbackInfo.targetDesc.Format))
{
D3D12CopyPixelParams stencilCopyParams = targetCopyParams;
stencilCopyParams.srcImageFormat =
GetDepthSRVFormat(m_CallbackInfo.targetImage->GetDesc().Format, 1);
stencilCopyParams.copyFormat = DXGI_FORMAT_R8_TYPELESS;
stencilCopyParams.planeSlice = 1;
fallback = m_SavedState.dsv.GetDSV().Flags & D3D12_DSV_FLAG_READ_ONLY_STENCIL
? D3D12_RESOURCE_STATE_DEPTH_READ
: D3D12_RESOURCE_STATE_DEPTH_WRITE;
stencilCopyParams.srcImageState =
GetImageState(m_CallbackInfo.targetImage, stencilCopyParams.planeSlice, fallback);
CopyImagePixel(cmd, stencilCopyParams, offset + sizeof(float));
}
// If the target image is a depth/stencil view, we already copied the value above.
if(IsDepthFormat(m_CallbackInfo.targetDesc.Format))
return;
// Get the bound depth format for this event
ResourceId resId = m_SavedState.dsv.GetResResourceId();
if(resId != ResourceId())
{
WrappedID3D12Resource *depthImage =
m_pDevice->GetResourceManager()->GetCurrentAs<WrappedID3D12Resource>(resId);
DXGI_FORMAT depthFormat = m_SavedState.dsv.GetDSV().Format;
// Descriptors with unknown type are valid and indicate to use the resource's format
if(depthFormat == DXGI_FORMAT_UNKNOWN)
depthFormat = depthImage->GetDesc().Format;
D3D12CopyPixelParams depthCopyParams = targetCopyParams;
depthCopyParams.srcImage = depthImage;
depthCopyParams.srcImageFormat = GetDepthSRVFormat(depthFormat, 0);
depthCopyParams.copyFormat = GetDepthCopyFormat(depthFormat);
depthCopyParams.depthcopy = true;
fallback = m_SavedState.dsv.GetDSV().Flags & D3D12_DSV_FLAG_READ_ONLY_DEPTH
? D3D12_RESOURCE_STATE_DEPTH_READ
: D3D12_RESOURCE_STATE_DEPTH_WRITE;
depthCopyParams.srcImageState = GetImageState(depthImage, depthCopyParams.planeSlice, fallback);
CopyImagePixel(cmd, depthCopyParams, offset + offsetof(struct D3D12PixelHistoryValue, depth));
if(IsDepthAndStencilFormat(depthFormat))
{
depthCopyParams.srcImageFormat = GetDepthSRVFormat(depthFormat, 1);
depthCopyParams.copyFormat = DXGI_FORMAT_R8_TYPELESS;
depthCopyParams.planeSlice = 1;
fallback = m_SavedState.dsv.GetDSV().Flags & D3D12_DSV_FLAG_READ_ONLY_STENCIL
? D3D12_RESOURCE_STATE_DEPTH_READ
: D3D12_RESOURCE_STATE_DEPTH_WRITE;
depthCopyParams.srcImageState =
GetImageState(depthImage, depthCopyParams.planeSlice, fallback);
CopyImagePixel(cmd, depthCopyParams,
offset + offsetof(struct D3D12PixelHistoryValue, stencil));
}
m_DepthFormats.insert(std::make_pair(eid, depthFormat));
}
}
// Executes a single draw defined by the eventId
void ReplayDraw(ID3D12GraphicsCommandListX *cmd, uint32_t eventId, bool clear = false)
{
if(clear)
{
D3D12_RECT clearRect;
clearRect.left = m_CallbackInfo.x;
clearRect.top = m_CallbackInfo.y;
clearRect.right = clearRect.left + 1;
clearRect.bottom = clearRect.top + 1;
cmd->ClearDepthStencilView(m_pDevice->GetDebugManager()->GetCPUHandle(PIXEL_HISTORY_DSV),
D3D12_CLEAR_FLAG_STENCIL, 0.0f, 0, 1, &clearRect);
}
const ActionDescription *action = m_pDevice->GetAction(eventId);
m_pDevice->ReplayDraw(cmd, *action);
}
// GetPipelineReplacements creates pipeline replacements that disable all tests,
// and use either fixed or original fragment shader, and shaders that don't have side effects.
D3D12PipelineReplacements GetPipelineReplacements(uint32_t eid, const D3D12RenderState &state)
{
// The map does not keep track of the event ID, event ID is only used to figure out
// which shaders need to be modified. Those flags are based on the shaders bound,
// so in theory all events should share those flags if they are using the same pipeline.
auto pipeIt = m_PipeCache.find(state.pipe);
if(pipeIt != m_PipeCache.end())
return pipeIt->second;
D3D12PipelineReplacements replacements = {};
WrappedID3D12PipelineState *origPSO =
m_pDevice->GetResourceManager()->GetCurrentAs<WrappedID3D12PipelineState>(state.pipe);
D3D12_EXPANDED_PIPELINE_STATE_STREAM_DESC desc;
origPSO->Fill(desc);
bool dxil = IsPSOUsingDXIL(desc);
ID3DBlob *psBlob =
m_pDevice->GetShaderCache()->MakeFixedColShader(D3D12ShaderCache::HIGHLIGHT, dxil);
if(psBlob == NULL)
{
RDCERR("Failed to create fixed color shader for pixel history.");
return replacements;
}
// Modify state so that we increment stencil and skip depth testing
desc.DepthStencilState.FrontFace.StencilFunc = D3D12_COMPARISON_FUNC_ALWAYS;
desc.DepthStencilState.FrontFace.StencilPassOp = D3D12_STENCIL_OP_INCR_SAT;
desc.DepthStencilState.FrontFace.StencilFailOp = D3D12_STENCIL_OP_KEEP;
desc.DepthStencilState.FrontFace.StencilDepthFailOp = D3D12_STENCIL_OP_KEEP;
desc.DepthStencilState.FrontFace.StencilReadMask = 0xff;
desc.DepthStencilState.FrontFace.StencilWriteMask = 0xff;
desc.DepthStencilState.BackFace = desc.DepthStencilState.FrontFace;
desc.DepthStencilState.StencilEnable = TRUE;
desc.DepthStencilState.DepthEnable = TRUE;
desc.DepthStencilState.DepthFunc = D3D12_COMPARISON_FUNC_ALWAYS;
desc.DepthStencilState.DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ZERO;
desc.DepthStencilState.DepthBoundsTestEnable = FALSE;
desc.DSVFormat = DXGI_FORMAT_D32_FLOAT_S8X24_UINT;
// TODO: Get DSVFormat from callbackinfo/pixelhistoryresources?
// TODO: Any other state need updated?
// Create the PSO for testing with the original shader, to account for shader discards
HRESULT hr = m_pDevice->CreatePipeState(desc, &replacements.originalShaderStencil);
if(FAILED(hr))
{
RDCERR("Failed to create PSO for pixel history.");
SAFE_RELEASE(psBlob);
return replacements;
}
// Create the PSO for testing with a fixed color shader, to count fragments
// without discards
desc.PS.pShaderBytecode = psBlob->GetBufferPointer();
desc.PS.BytecodeLength = psBlob->GetBufferSize();
hr = m_pDevice->CreatePipeState(desc, &replacements.fixedShaderStencil);
if(FAILED(hr))
{
RDCERR("Failed to create PSO for pixel history.");
SAFE_RELEASE(psBlob);
SAFE_RELEASE(replacements.originalShaderStencil);
return replacements;
}
m_PipeCache.insert(std::make_pair(state.pipe, replacements));
SAFE_RELEASE(psBlob);
return replacements;
}
D3D12RenderState m_SavedState;
std::map<ResourceId, D3D12PipelineReplacements> m_PipeCache;
rdcarray<uint32_t> m_Events;
// Key is event ID, and value is an index of where the event data is stored.
std::map<uint32_t, size_t> m_EventIndices;
std::map<uint32_t, DXGI_FORMAT> m_DepthFormats;
};
// TestsFailedCallback replays draws to figure out which tests failed, such as
// depth test, stencil test, etc.
struct D3D12TestsFailedCallback : public D3D12PixelHistoryCallback
{
D3D12TestsFailedCallback(WrappedID3D12Device *device, D3D12PixelHistoryShaderCache *shaderCache,
const D3D12PixelHistoryCallbackInfo &callbackInfo,
ID3D12QueryHeap *occlusionQueryHeap, rdcarray<uint32_t> events)
: D3D12PixelHistoryCallback(device, shaderCache, callbackInfo, occlusionQueryHeap),
m_Events(events)
{
}
~D3D12TestsFailedCallback()
{
for(auto it = m_PipeCache.begin(); it != m_PipeCache.end(); ++it)
{
SAFE_RELEASE(it->second);
}
}
void PreDraw(uint32_t eid, ID3D12GraphicsCommandListX *cmd)
{
if(!m_Events.contains(eid))
return;
D3D12RenderState pipeState = m_pDevice->GetQueue()->GetCommandData()->GetCurRenderState();
uint32_t eventFlags = CalculateEventFlags(pipeState);
m_EventFlags[eid] = eventFlags;
WrappedID3D12PipelineState *origPSO =
m_pDevice->GetResourceManager()->GetCurrentAs<WrappedID3D12PipelineState>(pipeState.pipe);
if(origPSO == NULL)
RDCERR("Failed to retrieve original PSO for pixel history.");
D3D12_EXPANDED_PIPELINE_STATE_STREAM_DESC pipeDesc;
origPSO->Fill(pipeDesc);
if(pipeDesc.DepthStencilState.DepthBoundsTestEnable)
m_EventDepthBounds[eid] = {pipeState.depthBoundsMin, pipeState.depthBoundsMax};
else
m_EventDepthBounds[eid] = {};
// TODO: figure out if the shader has early fragments tests turned on,
// based on the currently bound fragment shader.
bool earlyFragmentTests = false;
m_HasEarlyFragments[eid] = earlyFragmentTests;
ReplayDrawWithTests(cmd, eid, eventFlags, pipeState, GetPixelHistoryRenderTargetIndex(pipeState));
pipeState.ApplyState(m_pDevice, cmd);
}
bool PostDraw(uint32_t eid, ID3D12GraphicsCommandListX *cmd) { return false; }
void AliasEvent(uint32_t primary, uint32_t alias) {}
void PostRedraw(uint32_t eid, ID3D12GraphicsCommandListX *cmd) {}
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) {}
bool HasEventFlags(uint32_t eventId) { return m_EventFlags.find(eventId) != m_EventFlags.end(); }
uint32_t GetEventFlags(uint32_t eventId)
{
auto it = m_EventFlags.find(eventId);
if(it == m_EventFlags.end())
RDCERR("Can't find event flags for event %u", eventId);
return it->second;
}
rdcpair<float, float> GetEventDepthBounds(uint32_t eventId)
{
auto it = m_EventDepthBounds.find(eventId);
if(it == m_EventDepthBounds.end())
RDCERR("Can't find event flags for event %u", eventId);
return it->second;
}
void FetchOcclusionResults()
{
if(m_OcclusionQueries.size() == 0)
return;
D3D12_HEAP_PROPERTIES heapProps;
heapProps.Type = D3D12_HEAP_TYPE_READBACK;
heapProps.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
heapProps.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;
heapProps.CreationNodeMask = 1;
heapProps.VisibleNodeMask = 1;
D3D12_RESOURCE_DESC bufDesc;
bufDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
bufDesc.Alignment = 0;
bufDesc.Width = sizeof(uint64_t) * m_OcclusionQueries.size();
bufDesc.Height = 1;
bufDesc.DepthOrArraySize = 1;
bufDesc.MipLevels = 1;
bufDesc.Format = DXGI_FORMAT_UNKNOWN;
bufDesc.SampleDesc.Count = 1;
bufDesc.SampleDesc.Quality = 0;
bufDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
bufDesc.Flags = D3D12_RESOURCE_FLAG_NONE;
ID3D12Resource *readbackBuf = NULL;
HRESULT hr = m_pDevice->CreateCommittedResource(&heapProps, D3D12_HEAP_FLAG_NONE, &bufDesc,
D3D12_RESOURCE_STATE_COPY_DEST, NULL,
__uuidof(ID3D12Resource), (void **)&readbackBuf);
m_pDevice->CheckHRESULT(hr);
if(FAILED(hr))
{
RDCERR("Failed to create query readback buffer HRESULT: %s", ToStr(hr).c_str());
return;
}
ID3D12GraphicsCommandListX *list = m_pDevice->GetNewList();
if(!list)
return;
list->ResolveQueryData(m_OcclusionQueryHeap, D3D12_QUERY_TYPE_OCCLUSION, 0,
(UINT)m_OcclusionQueries.size(), readbackBuf, 0);
list->Close();
m_pDevice->ExecuteLists();
m_pDevice->FlushLists();
D3D12_RANGE range;
range.Begin = 0;
range.End = (SIZE_T)bufDesc.Width;
uint64_t *data;
hr = readbackBuf->Map(0, &range, (void **)&data);
m_pDevice->CheckHRESULT(hr);
if(FAILED(hr))
{
RDCERR("Failed to map query heap data HRESULT: %s", ToStr(hr).c_str());
SAFE_RELEASE(readbackBuf);
return;
}
m_OcclusionResults.resize(m_OcclusionQueries.size());
for(size_t i = 0; i < m_OcclusionResults.size(); ++i)
m_OcclusionResults[i] = data[i];
readbackBuf->Unmap(0, NULL);
SAFE_RELEASE(readbackBuf);
}
uint64_t GetOcclusionResult(uint32_t eventId, uint32_t test) const
{
auto it = m_OcclusionQueries.find(rdcpair<uint32_t, uint32_t>(eventId, test));
if(it == m_OcclusionQueries.end())
RDCERR("Can't locate occlusion query for event id %u and test flags %u", eventId, test);
if(it->second >= m_OcclusionResults.size())
RDCERR(
"Event %u, occlusion index is %u, and the total # of occlusion "
"query data %zu",
eventId, it->second, m_OcclusionResults.size());
return m_OcclusionResults[it->second];
}
bool HasEarlyFragments(uint32_t eventId) const
{
auto it = m_HasEarlyFragments.find(eventId);
RDCASSERT(it != m_HasEarlyFragments.end());
return it->second;
}
private:
uint32_t CalculateEventFlags(const D3D12RenderState &pipeState)
{
uint32_t flags = 0;
WrappedID3D12PipelineState *origPSO =
m_pDevice->GetResourceManager()->GetCurrentAs<WrappedID3D12PipelineState>(pipeState.pipe);
if(origPSO == NULL)
{
RDCERR("Failed to retrieve original PSO for pixel history.");
return flags;
}
D3D12_EXPANDED_PIPELINE_STATE_STREAM_DESC pipeDesc;
origPSO->Fill(pipeDesc);
// Culling
{
if(pipeDesc.RasterizerState.DepthClipEnable)
flags |= TestEnabled_DepthClipping;
if(pipeDesc.RasterizerState.CullMode != D3D12_CULL_MODE_NONE)
flags |= TestEnabled_Culling;
}
// Depth and Stencil tests
if(pipeState.dsv.GetResResourceId() != ResourceId())
{
if(pipeDesc.DepthStencilState.DepthBoundsTestEnable)
flags |= TestEnabled_DepthBounds;
if(pipeDesc.DepthStencilState.DepthEnable)
{
if(pipeDesc.DepthStencilState.DepthFunc != D3D12_COMPARISON_FUNC_ALWAYS)
flags |= TestEnabled_DepthTesting;
if(pipeDesc.DepthStencilState.DepthFunc == D3D12_COMPARISON_FUNC_NEVER)
flags |= TestMustFail_DepthTesting;
if(pipeDesc.DepthStencilState.DepthFunc == D3D12_COMPARISON_FUNC_NEVER)
flags |= DepthTest_Never;
if(pipeDesc.DepthStencilState.DepthFunc == D3D12_COMPARISON_FUNC_LESS)
flags |= DepthTest_Less;
if(pipeDesc.DepthStencilState.DepthFunc == D3D12_COMPARISON_FUNC_EQUAL)
flags |= DepthTest_Equal;
if(pipeDesc.DepthStencilState.DepthFunc == D3D12_COMPARISON_FUNC_LESS_EQUAL)
flags |= DepthTest_LessEqual;
if(pipeDesc.DepthStencilState.DepthFunc == D3D12_COMPARISON_FUNC_GREATER)
flags |= DepthTest_Greater;
if(pipeDesc.DepthStencilState.DepthFunc == D3D12_COMPARISON_FUNC_NOT_EQUAL)
flags |= DepthTest_NotEqual;
if(pipeDesc.DepthStencilState.DepthFunc == D3D12_COMPARISON_FUNC_GREATER_EQUAL)
flags |= DepthTest_GreaterEqual;
if(pipeDesc.DepthStencilState.DepthFunc == D3D12_COMPARISON_FUNC_ALWAYS)
flags |= DepthTest_Always;
}
else
{
flags |= DepthTest_Always;
}
if(pipeDesc.DepthStencilState.StencilEnable)
{
if(pipeDesc.DepthStencilState.FrontFace.StencilFunc != D3D12_COMPARISON_FUNC_ALWAYS ||
pipeDesc.DepthStencilState.BackFace.StencilFunc != D3D12_COMPARISON_FUNC_ALWAYS)
flags |= TestEnabled_StencilTesting;
if(pipeDesc.DepthStencilState.FrontFace.StencilFunc == D3D12_COMPARISON_FUNC_NEVER &&
pipeDesc.DepthStencilState.BackFace.StencilFunc == D3D12_COMPARISON_FUNC_NEVER)
flags |= TestMustFail_StencilTesting;
else if(pipeDesc.DepthStencilState.FrontFace.StencilFunc == D3D12_COMPARISON_FUNC_NEVER &&
pipeDesc.RasterizerState.CullMode == D3D12_CULL_MODE_BACK)
flags |= TestMustFail_StencilTesting;
else if(pipeDesc.RasterizerState.CullMode == D3D12_CULL_MODE_FRONT &&
pipeDesc.DepthStencilState.BackFace.StencilFunc == D3D12_COMPARISON_FUNC_NEVER)
flags |= TestMustFail_StencilTesting;
}
}
// Scissor
{
// Scissor is always enabled in D3D12
flags |= TestEnabled_Scissor;
bool inRegion = false;
bool inAllRegions = true;
// Do we even need to know viewport here?
const D3D12_RECT *pScissors = pipeState.scissors.data();
size_t scissorCount = pipeState.scissors.size();
for(size_t i = 0; i < scissorCount; ++i)
{
if((int32_t)m_CallbackInfo.x >= pScissors[i].left &&
(int32_t)m_CallbackInfo.y >= pScissors[i].top &&
(int32_t)m_CallbackInfo.x < pScissors[i].right &&
(int32_t)m_CallbackInfo.y < pScissors[i].bottom)
inRegion = true;
else
inAllRegions = false;
}
if(!inRegion)
flags |= TestMustFail_Scissor;
if(inAllRegions)
flags |= TestMustPass_Scissor;
}
// Blending
{
if(pipeDesc.BlendState.IndependentBlendEnable)
{
for(size_t i = 0; i < pipeState.rts.size(); ++i)
{
if(pipeDesc.BlendState.RenderTarget[i].BlendEnable)
{
flags |= Blending_Enabled;
break;
}
}
}
else
{
// Might not have render targets if rasterization is disabled
if(pipeState.rts.size() > 0 && pipeDesc.BlendState.RenderTarget[0].BlendEnable)
flags |= Blending_Enabled;
}
}
// TODO: Is there a better test for this?
if(pipeDesc.PS.pShaderBytecode == NULL)
flags |= UnboundFragmentShader;
// Samples
{
// TODO: figure out if we always need to check this.
flags |= TestEnabled_SampleMask;
if((pipeDesc.SampleMask & m_CallbackInfo.sampleMask) == 0)
flags |= TestMustFail_SampleMask;
}
// TODO: is shader discard always possible when PS is bound?
if(pipeDesc.PS.BytecodeLength > 0 && pipeDesc.PS.pShaderBytecode != NULL)
flags |= TestEnabled_FragmentDiscard;
return flags;
}
// Flags to create a pipeline for tests, can be combined to control how
// a pipeline is created.
enum
{
PipelineCreationFlags_DisableCulling = 1 << 0,
PipelineCreationFlags_DisableDepthTest = 1 << 1,
PipelineCreationFlags_DisableStencilTest = 1 << 2,
PipelineCreationFlags_DisableDepthBoundsTest = 1 << 3,
PipelineCreationFlags_DisableDepthClipping = 1 << 4,
PipelineCreationFlags_FixedColorShader = 1 << 5,
PipelineCreationFlags_IntersectOriginalScissor = 1 << 6,
};
void ReplayDrawWithTests(ID3D12GraphicsCommandListX *cmd, uint32_t eid, uint32_t eventFlags,
D3D12RenderState pipeState, uint32_t outputIndex)
{
// TODO: Handle shader side effects?
rdcarray<D3D12_RECT> prevScissors = pipeState.scissors;
for(uint32_t i = 0; i < pipeState.views.size(); i++)
ScissorToPixel(pipeState.views[i], pipeState.scissors[i]);
pipeState.ApplyState(m_pDevice, cmd);
if(eventFlags & TestEnabled_Culling)
{
uint32_t pipeFlags =
PipelineCreationFlags_DisableDepthTest | PipelineCreationFlags_DisableDepthClipping |
PipelineCreationFlags_DisableDepthBoundsTest | PipelineCreationFlags_DisableStencilTest |
PipelineCreationFlags_FixedColorShader;
ID3D12PipelineState *pso = CreatePipeline(pipeState, pipeFlags, outputIndex);
D3D12MarkerRegion::Set(cmd, StringFormat::Fmt("Test culling on %u", eid));
ReplayDraw(cmd, pipeState, pso, eid, TestEnabled_Culling);
}
if(eventFlags & TestEnabled_DepthClipping)
{
uint32_t pipeFlags =
PipelineCreationFlags_DisableDepthTest | PipelineCreationFlags_DisableDepthBoundsTest |
PipelineCreationFlags_DisableStencilTest | PipelineCreationFlags_FixedColorShader;
ID3D12PipelineState *pso = CreatePipeline(pipeState, pipeFlags, outputIndex);
D3D12MarkerRegion::Set(cmd, StringFormat::Fmt("Test depth clipping on %u", eid));
ReplayDraw(cmd, pipeState, pso, eid, TestEnabled_DepthClipping);
}
// Scissor is always enabled on D3D12 but we still check some ensured pass/fail cases
// If scissor must fail, we're done
if(eventFlags & TestMustFail_Scissor)
return;
// If scissor must pass, we can skip this test
if((eventFlags & TestMustPass_Scissor) == 0)
{
uint32_t pipeFlags =
PipelineCreationFlags_IntersectOriginalScissor | PipelineCreationFlags_DisableDepthTest |
PipelineCreationFlags_DisableDepthBoundsTest | PipelineCreationFlags_DisableStencilTest |
PipelineCreationFlags_FixedColorShader;
ID3D12PipelineState *pso = CreatePipeline(pipeState, pipeFlags, outputIndex);
// This will change the scissor for the later tests, but since those
// tests happen later in the pipeline, it does not matter.
for(uint32_t i = 0; i < pipeState.views.size(); i++)
IntersectScissors(prevScissors[i], pipeState.scissors[i]);
D3D12MarkerRegion::Set(cmd, StringFormat::Fmt("Test scissor on %u", eid));
ReplayDraw(cmd, pipeState, pso, eid, TestEnabled_Scissor);
}
// Sample mask
if(eventFlags & TestMustFail_SampleMask)
return;
if(eventFlags & TestEnabled_SampleMask)
{
uint32_t pipeFlags =
PipelineCreationFlags_DisableDepthBoundsTest | PipelineCreationFlags_DisableStencilTest |
PipelineCreationFlags_DisableDepthTest | PipelineCreationFlags_FixedColorShader;
ID3D12PipelineState *pso = CreatePipeline(pipeState, pipeFlags, outputIndex);
D3D12MarkerRegion::Set(cmd, StringFormat::Fmt("Test sample mask on %u", eid));
ReplayDraw(cmd, pipeState, pso, eid, TestEnabled_SampleMask);
}
// Depth bounds
if(eventFlags & TestEnabled_DepthBounds)
{
uint32_t pipeFlags = PipelineCreationFlags_DisableStencilTest |
PipelineCreationFlags_DisableDepthTest |
PipelineCreationFlags_FixedColorShader;
ID3D12PipelineState *pso = CreatePipeline(pipeState, pipeFlags, outputIndex);
D3D12MarkerRegion::Set(cmd, StringFormat::Fmt("Test depth bounds on %u", eid));
ReplayDraw(cmd, pipeState, pso, eid, TestEnabled_DepthBounds);
}
// Stencil test
if(eventFlags & TestMustFail_StencilTesting)
return;
if(eventFlags & TestEnabled_StencilTesting)
{
uint32_t pipeFlags =
PipelineCreationFlags_DisableDepthTest | PipelineCreationFlags_FixedColorShader;
ID3D12PipelineState *pso = CreatePipeline(pipeState, pipeFlags, outputIndex);
D3D12MarkerRegion::Set(cmd, StringFormat::Fmt("Test stencil on %u", eid));
ReplayDraw(cmd, pipeState, pso, eid, TestEnabled_StencilTesting);
}
// Depth test
if(eventFlags & TestMustFail_DepthTesting)
return;
if(eventFlags & TestEnabled_DepthTesting)
{
// Previous test might have modified the stencil state, which could cause this event to fail.
uint32_t pipeFlags =
PipelineCreationFlags_DisableStencilTest | PipelineCreationFlags_FixedColorShader;
ID3D12PipelineState *pso = CreatePipeline(pipeState, pipeFlags, outputIndex);
D3D12MarkerRegion::Set(cmd, StringFormat::Fmt("Test depth on %u", eid));
ReplayDraw(cmd, pipeState, pso, eid, TestEnabled_DepthTesting);
}
// Shader discard
if(eventFlags & TestEnabled_FragmentDiscard)
{
// With early fragment tests, sample counting (occlusion query) will be
// done before the shader executes.
// TODO: remove early fragment tests if it is ON.
uint32_t pipeFlags = PipelineCreationFlags_DisableDepthBoundsTest |
PipelineCreationFlags_DisableStencilTest |
PipelineCreationFlags_DisableDepthTest;
ID3D12PipelineState *pso = CreatePipeline(pipeState, pipeFlags, outputIndex);
D3D12MarkerRegion::Set(cmd, StringFormat::Fmt("Test shader discard on %u", eid));
ReplayDraw(cmd, pipeState, pso, eid, TestEnabled_FragmentDiscard);
}
}
// Creates a pipeline that is based on the given pipeline and the given
// pipeline flags. Modifies the base pipeline according to the flags, and
// leaves the original pipeline behavior if a flag is not set.
ID3D12PipelineState *CreatePipeline(D3D12RenderState baseState, uint32_t pipeCreateFlags,
uint32_t outputIndex)
{
rdcpair<ResourceId, uint32_t> pipeKey(baseState.pipe, pipeCreateFlags);
auto it = m_PipeCache.find(pipeKey);
// Check if we processed this pipeline before.
if(it != m_PipeCache.end())
return it->second;
WrappedID3D12PipelineState *origPSO =
m_pDevice->GetResourceManager()->GetCurrentAs<WrappedID3D12PipelineState>(baseState.pipe);
if(origPSO == NULL)
{
RDCERR("Failed to retrieve original PSO for pixel history.");
return NULL;
}
D3D12_EXPANDED_PIPELINE_STATE_STREAM_DESC pipeDesc;
origPSO->Fill(pipeDesc);
// Only interested in a single sample.
pipeDesc.SampleMask = m_CallbackInfo.sampleMask;
// We are going to replay a draw multiple times, don't want to modify the
// depth value, not to influence later tests.
pipeDesc.DepthStencilState.DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ZERO;
if(pipeCreateFlags & PipelineCreationFlags_DisableCulling)
pipeDesc.RasterizerState.CullMode = D3D12_CULL_MODE_NONE;
if(pipeCreateFlags & PipelineCreationFlags_DisableDepthTest)
pipeDesc.DepthStencilState.DepthEnable = FALSE;
if(pipeCreateFlags & PipelineCreationFlags_DisableStencilTest)
pipeDesc.DepthStencilState.StencilEnable = FALSE;
if(pipeCreateFlags & PipelineCreationFlags_DisableDepthBoundsTest)
pipeDesc.DepthStencilState.DepthBoundsTestEnable = FALSE;
if(pipeCreateFlags & PipelineCreationFlags_DisableDepthClipping)
pipeDesc.RasterizerState.DepthClipEnable = FALSE;
if(pipeCreateFlags & PipelineCreationFlags_FixedColorShader)
{
bool dxil = IsPSOUsingDXIL(pipeDesc);
ID3DBlob *FixedColorPS = m_ShaderCache->GetFixedColorShader(dxil, outputIndex);
pipeDesc.PS.pShaderBytecode = FixedColorPS->GetBufferPointer();
pipeDesc.PS.BytecodeLength = FixedColorPS->GetBufferSize();
}
ID3D12PipelineState *pso = NULL;
HRESULT hr = m_pDevice->CreatePipeState(pipeDesc, &pso);
if(FAILED(hr))
{
RDCERR("Failed to create PSO for pixel history.");
return NULL;
}
m_PipeCache.insert(std::make_pair(pipeKey, pso));
return pso;
}
void ReplayDraw(ID3D12GraphicsCommandListX *cmd, D3D12RenderState pipeState,
ID3D12PipelineState *pso, int eventId, uint32_t test)
{
pipeState.pipe = GetResID(pso);
pipeState.ApplyState(m_pDevice, cmd);
uint32_t index = (uint32_t)m_OcclusionQueries.size();
if(m_OcclusionQueries.find(rdcpair<uint32_t, uint32_t>(eventId, test)) != m_OcclusionQueries.end())
RDCERR("A query already exist for event id %u and test %u", eventId, test);
m_OcclusionQueries.insert(std::make_pair(rdcpair<uint32_t, uint32_t>(eventId, test), index));
cmd->BeginQuery(m_OcclusionQueryHeap, D3D12_QUERY_TYPE_OCCLUSION, index);
m_pDevice->ReplayDraw(cmd, *m_pDevice->GetAction(eventId));
cmd->EndQuery(m_OcclusionQueryHeap, D3D12_QUERY_TYPE_OCCLUSION, index);
}
rdcarray<uint32_t> m_Events;
// Key is event ID, value is the flags for that event.
std::map<uint32_t, uint32_t> m_EventFlags;
std::map<uint32_t, rdcpair<float, float>> m_EventDepthBounds;
// Key is a pair <Base pipeline, pipeline flags>
std::map<rdcpair<ResourceId, uint32_t>, ID3D12PipelineState *> m_PipeCache;
// Key: pair <event ID, test>
// value: the index where occlusion query is in m_OcclusionResults
std::map<rdcpair<uint32_t, uint32_t>, uint32_t> m_OcclusionQueries;
std::map<uint32_t, bool> m_HasEarlyFragments;
rdcarray<uint64_t> m_OcclusionResults;
};
void D3D12UpdateTestsFailed(const D3D12TestsFailedCallback *tfCb, uint32_t eventId,
uint32_t eventFlags, PixelModification &mod)
{
bool earlyFragmentTests = tfCb->HasEarlyFragments(eventId);
if(eventFlags & TestEnabled_Culling)
{
uint64_t occlData = tfCb->GetOcclusionResult(eventId, TestEnabled_Culling);
mod.backfaceCulled = (occlData == 0);
}
if(mod.backfaceCulled)
return;
if(eventFlags & TestEnabled_DepthClipping)
{
uint64_t occlData = tfCb->GetOcclusionResult(eventId, TestEnabled_DepthClipping);
mod.depthClipped = (occlData == 0);
}
if(mod.depthClipped)
return;
if((eventFlags & (TestEnabled_Scissor | TestMustPass_Scissor | TestMustFail_Scissor)) ==
TestEnabled_Scissor)
{
uint64_t occlData = tfCb->GetOcclusionResult(eventId, TestEnabled_Scissor);
mod.scissorClipped = (occlData == 0);
}
if(mod.scissorClipped)
return;
// TODO: Exclusive Scissor Test if NV extension is turned on?
if((eventFlags & (TestEnabled_SampleMask | TestMustFail_SampleMask)) == TestEnabled_SampleMask)
{
uint64_t occlData = tfCb->GetOcclusionResult(eventId, TestEnabled_SampleMask);
mod.sampleMasked = (occlData == 0);
}
if(mod.sampleMasked)
return;
// Shader discard with default fragment tests order.
if((eventFlags & TestEnabled_FragmentDiscard) && !earlyFragmentTests)
{
uint64_t occlData = tfCb->GetOcclusionResult(eventId, TestEnabled_FragmentDiscard);
mod.shaderDiscarded = (occlData == 0);
if(mod.shaderDiscarded)
return;
}
if(eventFlags & TestEnabled_DepthBounds)
{
uint64_t occlData = tfCb->GetOcclusionResult(eventId, TestEnabled_DepthBounds);
mod.depthBoundsFailed = (occlData == 0);
}
if(mod.depthBoundsFailed)
return;
if((eventFlags & (TestEnabled_StencilTesting | TestMustFail_StencilTesting)) ==
TestEnabled_StencilTesting)
{
uint64_t occlData = tfCb->GetOcclusionResult(eventId, TestEnabled_StencilTesting);
mod.stencilTestFailed = (occlData == 0);
}
if(mod.stencilTestFailed)
return;
if((eventFlags & (TestEnabled_DepthTesting | TestMustFail_DepthTesting)) == TestEnabled_DepthTesting)
{
uint64_t occlData = tfCb->GetOcclusionResult(eventId, TestEnabled_DepthTesting);
mod.depthTestFailed = (occlData == 0);
}
if(mod.depthTestFailed)
return;
// Shader discard with early fragment tests order.
if((eventFlags & TestEnabled_FragmentDiscard) && earlyFragmentTests)
{
uint64_t occlData = tfCb->GetOcclusionResult(eventId, TestEnabled_FragmentDiscard);
mod.shaderDiscarded = (occlData == 0);
}
}
// 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))
{
// Color target not needed
renderTargetIndex = D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT;
}
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));
rdcarray<D3D12Descriptor> origRts = state.rts;
// 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);
bool isPrimPass = (i == 0);
D3D12MarkerRegion region(
cmd, StringFormat::Fmt("Getting %s for %u",
isPrimPass ? "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]);
if(isPrimPass)
{
state.rts.resize(1);
state.rts[0] = *m_CallbackInfo.colorDescriptor;
}
else if(renderTargetIndex != D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT)
{
state.rts[renderTargetIndex] = *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);
m_pDevice->ReplayDraw(cmd, *action);
if(!isPrimPass)
{
storeOffset += offsetof(struct D3D12PerFragmentInfo, shaderOut);
if(depthEnabled)
{
DXGI_FORMAT depthFormat = m_CallbackInfo.dsImage->GetDesc().Format;
D3D12CopyPixelParams depthCopyParams = colorCopyParams;
depthCopyParams.srcImage = m_CallbackInfo.dsImage;
depthCopyParams.srcImageFormat = GetDepthSRVFormat(depthFormat, 0);
depthCopyParams.copyFormat = GetDepthCopyFormat(depthFormat);
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);
// restore the original render targets as subsequent steps will use them
state.rts = origRts;
}
}
if(renderTargetIndex != D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT)
{
state.rts[renderTargetIndex] = *m_CallbackInfo.colorDescriptor;
}
// Get post-modification value, use the original framebuffer attachment.
state.pipe = GetResID(pipes.postModPipe);
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));
// 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);
m_pDevice->ReplayDraw(cmd, *action);
CopyImagePixel(cmd, colorCopyParams,
(fragsProcessed + f) * sizeof(D3D12PerFragmentInfo) +
offsetof(struct D3D12PerFragmentInfo, postMod));
if(prevState.dsv.GetResResourceId() != ResourceId())
{
DXGI_FORMAT depthFormat = m_CallbackInfo.dsImage->GetDesc().Format;
D3D12CopyPixelParams depthCopyParams = colorCopyParams;
depthCopyParams.srcImage = m_CallbackInfo.dsImage;
depthCopyParams.srcImageFormat = GetDepthSRVFormat(depthFormat, 0);
depthCopyParams.copyFormat = GetDepthCopyFormat(depthFormat);
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);
if(colorOutputIndex != D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT)
{
// 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.
DXGI_FORMAT colorFormat = DXGI_FORMAT_R32G32B32A32_FLOAT;
// For this pass, we will be binding the color target for capturing the shader output to the
// slot in question. The others can be left intact.
RDCASSERT(colorOutputIndex < pipeDesc.RTVFormats.NumRenderTargets);
pipeDesc.RTVFormats.RTFormats[colorOutputIndex] = colorFormat;
// In order to have different write masks per render target, we need to switch to independent
// blend if not already in use.
if(!pipeDesc.BlendState.IndependentBlendEnable)
{
pipeDesc.BlendState.IndependentBlendEnable = TRUE;
for(uint32_t i = 1; i < D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT; ++i)
pipeDesc.BlendState.RenderTarget[i] = pipeDesc.BlendState.RenderTarget[0];
}
// Mask out writes to targets which aren't the pixel history color target
for(uint32_t i = 0; i < D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT; ++i)
{
pipeDesc.BlendState.RenderTarget[i].BlendEnable = FALSE;
if(i != colorOutputIndex)
pipeDesc.BlendState.RenderTarget[i].RenderTargetWriteMask = 0;
}
}
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);
// Other targets were already disabled for the post mod pass, now force enable the
// pixel history color target
if(colorOutputIndex != D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT)
pipeDesc.BlendState.RenderTarget[colorOutputIndex].RenderTargetWriteMask =
D3D12_COLOR_WRITE_ENABLE_ALL;
for(uint32_t i = 0; i < D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT; ++i)
{
pipeDesc.BlendState.RenderTarget[i].BlendEnable = FALSE;
}
{
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.MS.pShaderBytecode != NULL || pipeDesc.MS.BytecodeLength > 0)
{
RDCWARN("Can't get primitive ID at event %u due to mesh shader usage", eid);
}
else 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
{
// Regardless of which RT is the one in question for history, we write the
// primitive ID to RT0.
pipeDesc.RTVFormats.NumRenderTargets = 1;
pipeDesc.RTVFormats.RTFormats[0] = DXGI_FORMAT_R32G32B32A32_FLOAT;
for(int i = 1; i < D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT; ++i)
pipeDesc.RTVFormats.RTFormats[i] = DXGI_FORMAT_UNKNOWN;
pipeDesc.BlendState.IndependentBlendEnable = FALSE;
pipeDesc.BlendState.RenderTarget[0].RenderTargetWriteMask = D3D12_COLOR_WRITE_ENABLE_ALL;
bool dxil = IsPSOUsingDXIL(pipeDesc);
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;
};
// Callback used to determine the shader discard status for each fragment, where
// an event has multiple fragments with some being discarded in a fragment shader.
struct D3D12PixelHistoryDiscardedFragmentsCallback : D3D12PixelHistoryCallback
{
// Key is event ID and value is a list of primitive IDs
std::map<uint32_t, rdcarray<int32_t>> m_Events;
D3D12PixelHistoryDiscardedFragmentsCallback(WrappedID3D12Device *device,
D3D12PixelHistoryShaderCache *shaderCache,
const D3D12PixelHistoryCallbackInfo &callbackInfo,
std::map<uint32_t, rdcarray<int32_t>> events,
ID3D12QueryHeap *occlusionQueryHeap)
: D3D12PixelHistoryCallback(device, shaderCache, callbackInfo, occlusionQueryHeap),
m_Events(events)
{
}
~D3D12PixelHistoryDiscardedFragmentsCallback()
{
for(ID3D12PipelineState *pso : m_PSOsToDestroy)
SAFE_RELEASE(pso);
}
void PreDraw(uint32_t eid, ID3D12GraphicsCommandListX *cmd)
{
if(m_Events.find(eid) == m_Events.end())
return;
const rdcarray<int32_t> &primIds = m_Events[eid];
D3D12RenderState &state = m_pDevice->GetQueue()->GetCommandData()->GetCurRenderState();
D3D12RenderState prevState = state;
// Create a pipeline with a scissor and colorWriteMask = 0, and disable all tests.
ID3D12PipelineState *newPso = CreateDiscardedFragmentPipeline(state, eid);
for(uint32_t i = 0; i < state.views.size(); i++)
ScissorToPixel(state.views[i], state.scissors[i]);
Topology topo = MakePrimitiveTopology(state.topo);
state.pipe = GetResID(newPso);
state.ApplyState(m_pDevice, cmd);
for(uint32_t i = 0; i < primIds.size(); i++)
{
uint32_t queryId = (uint32_t)m_OcclusionQueries.size();
cmd->BeginQuery(m_OcclusionQueryHeap, D3D12_QUERY_TYPE_OCCLUSION, queryId);
uint32_t primId = primIds[i];
ActionDescription action = *m_pDevice->GetAction(eid);
action.numIndices = RENDERDOC_NumVerticesPerPrimitive(topo);
action.indexOffset += RENDERDOC_VertexOffset(topo, primId);
action.vertexOffset += RENDERDOC_VertexOffset(topo, primId);
// TODO once pixel history distinguishes between instances, draw only the instance
// for this fragment.
// TODO replay with a dummy index buffer so that all primitives other than the target
// one are degenerate - that way the vertex index etc is still the same as it should be.
m_pDevice->ReplayDraw(cmd, action);
cmd->EndQuery(m_OcclusionQueryHeap, D3D12_QUERY_TYPE_OCCLUSION, queryId);
m_OcclusionQueries[make_rdcpair<uint32_t, uint32_t>(eid, primId)] = queryId;
}
state = prevState;
state.ApplyState(m_pDevice, cmd);
}
void FetchOcclusionResults()
{
if(m_OcclusionQueries.size() == 0)
return;
D3D12_HEAP_PROPERTIES heapProps;
heapProps.Type = D3D12_HEAP_TYPE_READBACK;
heapProps.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
heapProps.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;
heapProps.CreationNodeMask = 1;
heapProps.VisibleNodeMask = 1;
D3D12_RESOURCE_DESC bufDesc;
bufDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
bufDesc.Alignment = 0;
bufDesc.Width = sizeof(uint64_t) * m_OcclusionQueries.size();
bufDesc.Height = 1;
bufDesc.DepthOrArraySize = 1;
bufDesc.MipLevels = 1;
bufDesc.Format = DXGI_FORMAT_UNKNOWN;
bufDesc.SampleDesc.Count = 1;
bufDesc.SampleDesc.Quality = 0;
bufDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
bufDesc.Flags = D3D12_RESOURCE_FLAG_NONE;
ID3D12Resource *readbackBuf = NULL;
HRESULT hr = m_pDevice->CreateCommittedResource(&heapProps, D3D12_HEAP_FLAG_NONE, &bufDesc,
D3D12_RESOURCE_STATE_COPY_DEST, NULL,
__uuidof(ID3D12Resource), (void **)&readbackBuf);
m_pDevice->CheckHRESULT(hr);
if(FAILED(hr))
{
RDCERR("Failed to create query readback buffer HRESULT: %s", ToStr(hr).c_str());
return;
}
ID3D12GraphicsCommandListX *list = m_pDevice->GetNewList();
if(!list)
return;
list->ResolveQueryData(m_OcclusionQueryHeap, D3D12_QUERY_TYPE_OCCLUSION, 0,
(UINT)m_OcclusionQueries.size(), readbackBuf, 0);
list->Close();
m_pDevice->ExecuteLists();
m_pDevice->FlushLists();
D3D12_RANGE range;
range.Begin = 0;
range.End = (SIZE_T)bufDesc.Width;
uint64_t *data;
hr = readbackBuf->Map(0, &range, (void **)&data);
m_pDevice->CheckHRESULT(hr);
if(FAILED(hr))
{
RDCERR("Failed to map query heap data HRESULT: %s", ToStr(hr).c_str());
SAFE_RELEASE(readbackBuf);
return;
}
m_OcclusionResults.resize(m_OcclusionQueries.size());
for(size_t i = 0; i < m_OcclusionResults.size(); ++i)
m_OcclusionResults[i] = data[i];
readbackBuf->Unmap(0, NULL);
SAFE_RELEASE(readbackBuf);
}
uint64_t GetOcclusionResult(uint32_t eventId, uint32_t test) const
{
auto it = m_OcclusionQueries.find(rdcpair<uint32_t, uint32_t>(eventId, test));
if(it == m_OcclusionQueries.end())
RDCERR("Can't locate occlusion query for event id %u and test flags %u", eventId, test);
if(it->second >= m_OcclusionResults.size())
RDCERR(
"Event %u, occlusion index is %u, and the total # of occlusion "
"query data %zu",
eventId, it->second, m_OcclusionResults.size());
return m_OcclusionResults[it->second];
}
bool PrimitiveDiscarded(uint32_t eid, uint32_t primId)
{
auto it = m_OcclusionQueries.find(make_rdcpair<uint32_t, uint32_t>(eid, primId));
if(it == m_OcclusionQueries.end())
return false;
return m_OcclusionResults[it->second] == 0;
}
bool PostDraw(uint32_t eid, ID3D12GraphicsCommandListX *cmd) { return false; }
void PostRedraw(uint32_t eid, ID3D12GraphicsCommandListX *cmd) {}
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) {}
private:
ID3D12PipelineState *CreateDiscardedFragmentPipeline(const D3D12RenderState &state, uint32_t eid)
{
WrappedID3D12PipelineState *origPSO =
m_pDevice->GetResourceManager()->GetCurrentAs<WrappedID3D12PipelineState>(state.pipe);
if(origPSO == NULL)
{
RDCERR("Failed to retrieve original PSO for pixel history.");
return NULL;
}
D3D12_EXPANDED_PIPELINE_STATE_STREAM_DESC pipeDesc;
origPSO->Fill(pipeDesc);
ModifyPSOForStencilIncrement(eid, pipeDesc, true);
pipeDesc.DepthStencilState.StencilEnable = FALSE;
ID3D12PipelineState *pso = NULL;
HRESULT hr = m_pDevice->CreatePipeState(pipeDesc, &pso);
if(FAILED(hr))
{
RDCERR("Failed to create PSO for pixel history.");
return NULL;
}
m_PSOsToDestroy.push_back(pso);
return pso;
}
std::map<rdcpair<uint32_t, uint32_t>, uint32_t> m_OcclusionQueries;
rdcarray<uint64_t> m_OcclusionResults;
rdcarray<ID3D12PipelineState *> m_PSOsToDestroy;
};
bool D3D12DebugManager::PixelHistorySetupResources(D3D12PixelHistoryResources &resources,
WrappedID3D12Resource *targetImage,
const D3D12_RESOURCE_DESC &desc,
uint32_t numEvents)
{
D3D12MarkerRegion region(
m_pDevice->GetQueue()->GetReal(),
StringFormat::Fmt("PixelHistorySetupResources %ux%ux%u %s %ux MSAA", desc.Width, desc.Height,
desc.DepthOrArraySize, ToStr(desc.Format).c_str(), desc.SampleDesc.Count));
ResourceFormat targetFmt = MakeResourceFormat(desc.Format);
ID3D12Resource *colorImage;
ID3D12Resource *dsImage;
ID3D12Resource *dstBuffer;
HRESULT hr = S_OK;
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;
D3D12_RESOURCE_DESC imageDesc = desc;
imageDesc.Format = DXGI_FORMAT_R32G32B32A32_FLOAT;
imageDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
imageDesc.Flags = D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET;
hr = m_pDevice->CreateCommittedResource(&heapProps, D3D12_HEAP_FLAG_NONE, &imageDesc,
D3D12_RESOURCE_STATE_RENDER_TARGET, NULL,
__uuidof(ID3D12Resource), (void **)&colorImage);
m_pDevice->CheckHRESULT(hr);
if(FAILED(hr))
{
RDCERR("Failed to create scratch render target for pixel history: %s", ToStr(hr).c_str());
return false;
}
colorImage->SetName(L"Pixel History Color Image");
D3D12_RENDER_TARGET_VIEW_DESC rtvDesc = {};
rtvDesc.Format = imageDesc.Format;
rtvDesc.ViewDimension = imageDesc.SampleDesc.Count > 1 ? D3D12_RTV_DIMENSION_TEXTURE2DMS
: D3D12_RTV_DIMENSION_TEXTURE2D;
D3D12_CPU_DESCRIPTOR_HANDLE rtv = m_pDevice->GetDebugManager()->GetCPUHandle(PIXEL_HISTORY_RTV);
m_pDevice->CreateRenderTargetView(colorImage, &rtvDesc, rtv);
imageDesc.Format = DXGI_FORMAT_D32_FLOAT_S8X24_UINT;
imageDesc.Flags = D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL;
hr = m_pDevice->CreateCommittedResource(&heapProps, D3D12_HEAP_FLAG_NONE, &imageDesc,
D3D12_RESOURCE_STATE_DEPTH_WRITE, NULL,
__uuidof(ID3D12Resource), (void **)&dsImage);
m_pDevice->CheckHRESULT(hr);
if(FAILED(hr))
{
RDCERR("Failed to create scratch depth stencil for pixel history: %s", ToStr(hr).c_str());
SAFE_RELEASE(colorImage);
return false;
}
dsImage->SetName(L"Pixel History Depth Stencil");
D3D12_DEPTH_STENCIL_VIEW_DESC dsvDesc = {};
dsvDesc.Format = imageDesc.Format;
dsvDesc.ViewDimension = imageDesc.SampleDesc.Count > 1 ? D3D12_DSV_DIMENSION_TEXTURE2DMS
: D3D12_DSV_DIMENSION_TEXTURE2D;
D3D12_CPU_DESCRIPTOR_HANDLE dsv = m_pDevice->GetDebugManager()->GetCPUHandle(PIXEL_HISTORY_DSV);
m_pDevice->CreateDepthStencilView(dsImage, &dsvDesc, dsv);
// With a readback heap, buffers cannot be created with the UAV flag. As a workaround, a custom heap
// can be created with the same properties as a readback heap, and then the UAV flag is permitted.
D3D12_HEAP_PROPERTIES readbackHeapProps =
m_pDevice->GetCustomHeapProperties(0, D3D12_HEAP_TYPE_READBACK);
D3D12_RESOURCE_DESC bufDesc;
bufDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
bufDesc.Alignment = 0;
bufDesc.Height = 1;
bufDesc.DepthOrArraySize = 1;
bufDesc.MipLevels = 1;
bufDesc.Format = DXGI_FORMAT_UNKNOWN;
bufDesc.SampleDesc.Count = 1;
bufDesc.SampleDesc.Quality = 0;
bufDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
bufDesc.Flags = D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS;
bufDesc.Width = AlignUp((uint32_t)(numEvents * sizeof(D3D12EventInfo)), 4096U);
hr = m_pDevice->CreateCommittedResource(&readbackHeapProps, D3D12_HEAP_FLAG_NONE, &bufDesc,
D3D12_RESOURCE_STATE_COPY_DEST, NULL,
__uuidof(ID3D12Resource), (void **)&dstBuffer);
m_pDevice->CheckHRESULT(hr);
if(FAILED(hr))
{
RDCERR("Failed to create readback buffer for pixel history: %s", ToStr(hr).c_str());
SAFE_RELEASE(colorImage);
SAFE_RELEASE(dsImage);
return false;
}
dstBuffer->SetName(L"Pixel History DstBuffer");
resources.colorImage = colorImage;
resources.colorDescriptor = GetWrapped(rtv);
resources.dsImage = dsImage;
resources.dsDescriptor = GetWrapped(dsv);
resources.dstBuffer = dstBuffer;
return true;
}
bool D3D12DebugManager::PixelHistoryDestroyResources(D3D12PixelHistoryResources &r)
{
SAFE_RELEASE(r.colorImage);
r.colorDescriptor = NULL;
SAFE_RELEASE(r.dsImage);
r.dsDescriptor = NULL;
SAFE_RELEASE(r.dstBuffer);
return true;
}
namespace
{
bool CreateOcclusionPool(WrappedID3D12Device *device, uint32_t poolSize, ID3D12QueryHeap **ppQueryHeap)
{
D3D12MarkerRegion region(device->GetQueue()->GetReal(),
StringFormat::Fmt("CreateQueryHeap %u", poolSize));
D3D12_QUERY_HEAP_DESC queryDesc = {};
queryDesc.Count = poolSize;
queryDesc.Type = D3D12_QUERY_HEAP_TYPE_OCCLUSION;
HRESULT hr = device->CreateQueryHeap(&queryDesc, __uuidof(ID3D12QueryHeap), (void **)ppQueryHeap);
if(FAILED(hr))
{
RDCERR("Failed to create query heap for pixel history HRESULT: %s", ToStr(hr).c_str());
return false;
}
return true;
}
bool IsDirectWrite(ResourceUsage usage)
{
return ((usage >= ResourceUsage::VS_RWResource && usage <= ResourceUsage::CS_RWResource) ||
usage == ResourceUsage::CopyDst || usage == ResourceUsage::Copy ||
usage == ResourceUsage::Resolve || usage == ResourceUsage::ResolveDst ||
usage == ResourceUsage::GenMips);
}
void FillInColor(ResourceFormat fmt, const D3D12PixelHistoryValue &value, ModificationValue &mod)
{
FloatVector v4 = DecodeFormattedComponents(fmt, value.color);
memcpy(mod.col.floatValue.data(), &v4, sizeof(v4));
}
void FillInColor(ResourceFormat fmt, const byte *pValue, ModificationValue &mod)
{
FloatVector v4 = DecodeFormattedComponents(fmt, pValue);
memcpy(mod.col.floatValue.data(), &v4, sizeof(v4));
}
void ConvertAndFillInColor(ResourceFormat srcFmt, ResourceFormat outFmt,
const D3D12PixelHistoryValue &value, ModificationValue &mod)
{
FloatVector v4 = DecodeFormattedComponents(srcFmt, value.color);
// To properly handle some cases of component bounds, roundtrip through encoding again
uint8_t tempColor[32];
EncodeFormattedComponents(outFmt, v4, (byte *)tempColor);
v4 = DecodeFormattedComponents(outFmt, tempColor);
memcpy(mod.col.floatValue.data(), &v4, sizeof(v4));
}
float GetDepthValue(DXGI_FORMAT depthFormat, const D3D12PixelHistoryValue &value)
{
FloatVector v4 = DecodeFormattedComponents(MakeResourceFormat(depthFormat), (byte *)&value.depth);
return v4.x;
}
float GetDepthValue(DXGI_FORMAT depthFormat, const byte *pValue)
{
FloatVector v4 = DecodeFormattedComponents(MakeResourceFormat(depthFormat), pValue);
return v4.x;
}
}
rdcarray<PixelModification> D3D12Replay::PixelHistory(rdcarray<EventUsage> events,
ResourceId target, uint32_t x, uint32_t y,
const Subresource &sub, CompType typeCast)
{
rdcarray<PixelModification> history;
if(events.empty())
return history;
D3D12ResourceManager *rm = m_pDevice->GetResourceManager();
WrappedID3D12Resource *pResource = rm->GetCurrentAs<WrappedID3D12Resource>(target);
D3D12_RESOURCE_DESC resDesc = pResource->GetDesc();
if(resDesc.Format == DXGI_FORMAT_UNKNOWN)
return history;
rdcstr regionName = StringFormat::Fmt(
"PixelHistory: pixel: (%u, %u) on %s subresource (%u, %u, %u) cast to %s with %zu events", x, y,
ToStr(target).c_str(), sub.mip, sub.slice, sub.sample, ToStr(typeCast).c_str(), events.size());
RDCDEBUG("%s", regionName.c_str());
D3D12MarkerRegion region(m_pDevice->GetQueue()->GetReal(), regionName);
uint32_t sampleIdx = sub.sample;
SCOPED_TIMER("D3D12DebugManager::PixelHistory");
if(sampleIdx > (uint32_t)resDesc.SampleDesc.Count)
sampleIdx = 0;
uint32_t sampleMask = ~0U;
if(sampleIdx < 32)
sampleMask = 1U << sampleIdx;
bool multisampled = (resDesc.SampleDesc.Count > 1);
if(sampleIdx == ~0U || !multisampled)
sampleIdx = 0;
ID3D12QueryHeap *pOcclusionQueryHeap = NULL;
if(!CreateOcclusionPool(m_pDevice, (uint32_t)events.size(), &pOcclusionQueryHeap))
{
return history;
}
// If the resource desc format is typeless, replace it with a typed format
if(IsTypelessFormat(resDesc.Format))
resDesc.Format = GetTypedFormat(resDesc.Format, typeCast);
// TODO: perhaps should allocate most resources after D3D12OcclusionCallback, since we will
// get a smaller subset of events that passed the occlusion query.
D3D12PixelHistoryResources resources = {};
if(!GetDebugManager()->PixelHistorySetupResources(resources, pResource, resDesc,
(uint32_t)events.size()))
{
SAFE_RELEASE(pOcclusionQueryHeap);
return history;
}
D3D12PixelHistoryShaderCache *shaderCache = new D3D12PixelHistoryShaderCache(
m_pDevice, m_PixelHistory.PrimitiveIDPS, m_PixelHistory.PrimitiveIDPSDxil,
m_PixelHistory.FixedColorPS, m_PixelHistory.FixedColorPSDxil);
D3D12PixelHistoryCallbackInfo callbackInfo = {};
callbackInfo.targetImage = pResource;
callbackInfo.targetDesc = resDesc;
callbackInfo.targetSubresource = sub;
callbackInfo.compType = typeCast;
callbackInfo.x = x;
callbackInfo.y = y;
callbackInfo.sampleMask = sampleMask;
callbackInfo.colorImage = resources.colorImage;
callbackInfo.colorDescriptor = resources.colorDescriptor;
callbackInfo.dsImage = resources.dsImage;
callbackInfo.dsDescriptor = resources.dsDescriptor;
callbackInfo.dstBuffer = resources.dstBuffer;
D3D12OcclusionCallback occlCb(m_pDevice, shaderCache, callbackInfo, pOcclusionQueryHeap, events);
{
D3D12MarkerRegion occlRegion(m_pDevice->GetQueue()->GetReal(), "D3D12OcclusionCallback");
m_pDevice->ReplayLog(0, events.back().eventId, eReplay_Full);
m_pDevice->FlushLists(true);
occlCb.FetchOcclusionResults();
}
// Gather all draw events that could have written to pixel for another replay pass,
// to determine if these draws failed for some reason (for ex., depth test).
rdcarray<uint32_t> modEvents;
rdcarray<uint32_t> drawEvents;
for(size_t ev = 0; ev < events.size(); ev++)
{
bool clear = (events[ev].usage == ResourceUsage::Clear);
bool directWrite = IsDirectWrite(events[ev].usage);
if(directWrite || clear)
{
modEvents.push_back(events[ev].eventId);
}
else
{
uint64_t occlData = occlCb.GetOcclusionResult((uint32_t)events[ev].eventId);
if(occlData > 0)
{
D3D12MarkerRegion::Set(m_pDevice->GetQueue()->GetReal(),
StringFormat::Fmt("%u has occl %llu", events[ev].eventId, occlData));
drawEvents.push_back(events[ev].eventId);
modEvents.push_back(events[ev].eventId);
}
}
}
D3D12ColorAndStencilCallback cb(m_pDevice, shaderCache, callbackInfo, modEvents);
{
D3D12MarkerRegion colorStencilRegion(m_pDevice->GetQueue()->GetReal(),
"D3D12ColorAndStencilCallback");
m_pDevice->ReplayLog(0, events.back().eventId, eReplay_Full);
m_pDevice->FlushLists(true);
}
// If there are any draw events, do another replay pass, in order to figure
// out which tests failed for each draw event.
D3D12TestsFailedCallback *tfCb = NULL;
if(drawEvents.size() > 0)
{
D3D12MarkerRegion testsRegion(m_pDevice->GetQueue()->GetReal(), "D3D12TestsFailedCallback");
ID3D12QueryHeap *pTfOcclusionQueryHeap = NULL;
if(!CreateOcclusionPool(m_pDevice, (uint32_t)drawEvents.size() * 6, &pTfOcclusionQueryHeap))
{
GetDebugManager()->PixelHistoryDestroyResources(resources);
SAFE_RELEASE(pOcclusionQueryHeap);
SAFE_DELETE(shaderCache);
return history;
}
tfCb = new D3D12TestsFailedCallback(m_pDevice, shaderCache, callbackInfo, pTfOcclusionQueryHeap,
drawEvents);
m_pDevice->ReplayLog(0, events.back().eventId, eReplay_Full);
m_pDevice->FlushLists(true);
tfCb->FetchOcclusionResults();
SAFE_RELEASE(pTfOcclusionQueryHeap);
}
for(size_t ev = 0; ev < events.size(); ev++)
{
uint32_t eventId = events[ev].eventId;
bool clear = (events[ev].usage == ResourceUsage::Clear);
bool directWrite = IsDirectWrite(events[ev].usage);
if(drawEvents.contains(events[ev].eventId) || clear || directWrite)
{
PixelModification mod;
RDCEraseEl(mod);
mod.eventId = eventId;
mod.directShaderWrite = directWrite;
mod.unboundPS = false;
if(!clear && !directWrite)
{
RDCASSERT(tfCb != NULL);
uint32_t flags = tfCb->GetEventFlags(eventId);
D3D12MarkerRegion::Set(m_pDevice->GetQueue()->GetReal(),
StringFormat::Fmt("%u has flags %x", eventId, flags));
if(flags & TestMustFail_DepthTesting)
mod.depthTestFailed = true;
if(flags & TestMustFail_Scissor)
mod.scissorClipped = true;
if(flags & TestMustFail_SampleMask)
mod.sampleMasked = true;
if(flags & UnboundFragmentShader)
mod.unboundPS = true;
D3D12UpdateTestsFailed(tfCb, eventId, flags, mod);
}
history.push_back(mod);
}
}
// Try to read memory back for stencil results
bytebuf eventData;
GetDebugManager()->GetBufferData(resources.dstBuffer, 0, 0, eventData);
const D3D12EventInfo *eventsInfo = (const D3D12EventInfo *)eventData.data();
std::map<uint32_t, uint32_t> eventsWithFrags;
std::map<uint32_t, ModificationValue> eventPremods;
ResourceFormat fmt = MakeResourceFormat(callbackInfo.targetDesc.Format);
if(typeCast != CompType::Typeless)
fmt.compType = typeCast;
for(size_t h = 0; h < history.size();)
{
PixelModification &mod = history[h];
int32_t eventIndex = cb.GetEventIndex(mod.eventId);
if(eventIndex == -1)
{
// There is no information, skip the event.
mod.preMod.SetInvalid();
mod.postMod.SetInvalid();
mod.shaderOut.SetInvalid();
h++;
continue;
}
const D3D12EventInfo &ei = eventsInfo[eventIndex];
if(multisampled)
{
// If the resource uses MSAA, the copy pixel already expands it to floats
// TODO: Need to verify this works as expected with uint/int MSAA targets
memcpy(mod.preMod.col.floatValue.data(), &ei.premod.color[0],
mod.preMod.col.floatValue.byteSize());
memcpy(mod.postMod.col.floatValue.data(), &ei.postmod.color[0],
mod.postMod.col.floatValue.byteSize());
}
else
{
FillInColor(fmt, ei.premod, mod.preMod);
FillInColor(fmt, ei.postmod, mod.postMod);
}
DXGI_FORMAT depthFormat = cb.GetDepthFormat(mod.eventId);
if(depthFormat != DXGI_FORMAT_UNKNOWN)
{
mod.preMod.stencil = ei.premod.stencil;
mod.postMod.stencil = ei.postmod.stencil;
if(multisampled)
{
mod.preMod.depth = ei.premod.depth.fdepth;
mod.postMod.depth = ei.postmod.depth.fdepth;
}
else
{
mod.preMod.depth = GetDepthValue(depthFormat, ei.premod);
mod.postMod.depth = GetDepthValue(depthFormat, ei.postmod);
}
}
int32_t frags = int32_t(ei.dsWithoutShaderDiscard[0]);
int32_t fragsClipped = int32_t(ei.dsWithShaderDiscard[0]);
mod.shaderOut.col.intValue[0] = frags;
mod.shaderOut.col.intValue[1] = fragsClipped;
bool someFragsClipped = (fragsClipped < frags);
mod.primitiveID = someFragsClipped;
if(frags > 0)
{
eventsWithFrags[mod.eventId] = frags;
eventPremods[mod.eventId] = mod.preMod;
}
for(int32_t f = 1; f < frags; f++)
{
history.insert(h + 1, mod);
}
for(int32_t f = 0; f < frags; f++)
history[h + f].fragIndex = f;
h += RDCMAX(1, frags);
RDCDEBUG(
"PixelHistory event id: %u, fixed shader stencilValue = %u, "
"original shader stencilValue = "
"%u",
mod.eventId, ei.dsWithoutShaderDiscard[0], ei.dsWithShaderDiscard[0]);
}
if(eventsWithFrags.size() > 0)
{
// Replay to get shader output value, post modification value and primitive ID for every fragment
D3D12PixelHistoryPerFragmentCallback perFragmentCB(m_pDevice, shaderCache, callbackInfo,
eventsWithFrags, eventPremods);
{
D3D12MarkerRegion perFragRegion(m_pDevice->GetQueue()->GetReal(),
"D3D12PixelHistoryPerFragmentCallback");
m_pDevice->ReplayLog(0, eventsWithFrags.rbegin()->first, eReplay_Full);
m_pDevice->FlushLists(true);
}
bytebuf fragData;
GetDebugManager()->GetBufferData(resources.dstBuffer, 0, 0, fragData);
const D3D12PerFragmentInfo *fragInfo = (const D3D12PerFragmentInfo *)fragData.data();
// Retrieve primitive ID values where fragment shader discarded some fragments. For these
// primitives we are going to perform an occlusion query to see if a primitive was discarded.
std::map<uint32_t, rdcarray<int32_t>> discardedPrimsEvents;
uint32_t primitivesToCheck = 0;
for(size_t h = 0; h < history.size(); h++)
{
uint32_t eid = history[h].eventId;
if(eventsWithFrags.find(eid) == eventsWithFrags.end())
continue;
uint32_t f = history[h].fragIndex;
bool someFragsClipped = (history[h].primitiveID == 1);
int32_t primId = fragInfo[perFragmentCB.GetEventOffset(eid) + f].primitiveID;
history[h].primitiveID = primId;
if(someFragsClipped)
{
discardedPrimsEvents[eid].push_back(primId);
primitivesToCheck++;
}
}
if(primitivesToCheck > 0)
{
D3D12MarkerRegion discardedRegion(m_pDevice->GetQueue()->GetReal(),
"D3D12PixelHistoryDiscardedFragmentsCallback");
ID3D12QueryHeap *pDiscardedFragsOcclusionQueryHeap = NULL;
if(!CreateOcclusionPool(m_pDevice, primitivesToCheck, &pDiscardedFragsOcclusionQueryHeap))
{
GetDebugManager()->PixelHistoryDestroyResources(resources);
SAFE_RELEASE(pOcclusionQueryHeap);
SAFE_DELETE(shaderCache);
return history;
}
D3D12PixelHistoryDiscardedFragmentsCallback discardedCb(m_pDevice, shaderCache, callbackInfo,
discardedPrimsEvents,
pDiscardedFragsOcclusionQueryHeap);
m_pDevice->ReplayLog(0, events.back().eventId, eReplay_Full);
m_pDevice->FlushLists(true);
discardedCb.FetchOcclusionResults();
SAFE_RELEASE(pDiscardedFragsOcclusionQueryHeap);
for(size_t h = 0; h < history.size(); h++)
{
history[h].shaderDiscarded =
discardedCb.PrimitiveDiscarded(history[h].eventId, history[h].primitiveID);
}
}
uint32_t discardOffset = 0;
ResourceFormat shaderOutFormat = MakeResourceFormat(DXGI_FORMAT_R32G32B32A32_FLOAT);
for(size_t h = 0; h < history.size(); h++)
{
uint32_t eid = history[h].eventId;
uint32_t f = history[h].fragIndex;
// Reset discard offset if this is a new event.
if(h > 0 && (eid != history[h - 1].eventId))
discardOffset = 0;
if(eventsWithFrags.find(eid) != eventsWithFrags.end())
{
if(history[h].shaderDiscarded)
{
discardOffset++;
// Copy previous post-mod value if its not the first event
if(h > 0)
history[h].postMod = history[h - 1].postMod;
continue;
}
uint32_t offset = perFragmentCB.GetEventOffset(eid) + f - discardOffset;
if(multisampled)
memcpy(history[h].shaderOut.col.floatValue.data(), &fragInfo[offset].shaderOut.color[0],
history[h].shaderOut.col.floatValue.byteSize());
else
FillInColor(shaderOutFormat, fragInfo[offset].shaderOut, history[h].shaderOut);
if(multisampled)
history[h].shaderOut.depth = fragInfo[offset].shaderOut.depth.fdepth;
else
history[h].shaderOut.depth =
GetDepthValue(DXGI_FORMAT_D32_FLOAT_S8X24_UINT, fragInfo[offset].shaderOut);
if((h < history.size() - 1) && (history[h].eventId == history[h + 1].eventId))
{
// Get post-modification value if this is not the last fragment for the event.
ConvertAndFillInColor(shaderOutFormat, fmt, fragInfo[offset].postMod, history[h].postMod);
// MSAA depth is expanded out to floats in the compute shader
if(multisampled)
history[h].postMod.depth = fragInfo[offset].postMod.depth.fdepth;
else
history[h].postMod.depth =
GetDepthValue(DXGI_FORMAT_D32_FLOAT_S8X24_UINT, fragInfo[offset].postMod);
history[h].postMod.stencil = -2;
}
// If it is not the first fragment for the event, set the preMod to the
// postMod of the previous fragment.
if(h > 0 && (history[h].eventId == history[h - 1].eventId))
{
history[h].preMod = history[h - 1].postMod;
}
}
// Check the depth value between premod/shaderout against the known test if we have valid
// depth values, as we don't have per-fragment depth test information.
if(history[h].preMod.depth >= 0.0f && history[h].shaderOut.depth >= 0.0f && tfCb &&
tfCb->HasEventFlags(history[h].eventId))
{
uint32_t flags = tfCb->GetEventFlags(history[h].eventId);
flags &= 0x7 << DepthTest_Shift;
DXGI_FORMAT dfmt = cb.GetDepthFormat(eid);
float shadDepth = history[h].shaderOut.depth;
// Quantize depth to match before comparing
if(dfmt == DXGI_FORMAT_D24_UNORM_S8_UINT || dfmt == DXGI_FORMAT_X24_TYPELESS_G8_UINT ||
dfmt == DXGI_FORMAT_R24_UNORM_X8_TYPELESS || dfmt == DXGI_FORMAT_R24G8_TYPELESS)
{
shadDepth = float(uint32_t(float(shadDepth * 0xffffff))) / float(0xffffff);
}
else if(dfmt == DXGI_FORMAT_D16_UNORM || dfmt == DXGI_FORMAT_R16_TYPELESS ||
dfmt == DXGI_FORMAT_R16_UNORM)
{
shadDepth = float(uint32_t(float(shadDepth * 0xffff))) / float(0xffff);
}
bool passed = true;
if(flags == DepthTest_Equal)
passed = (shadDepth == history[h].preMod.depth);
else if(flags == DepthTest_NotEqual)
passed = (shadDepth != history[h].preMod.depth);
else if(flags == DepthTest_Less)
passed = (shadDepth < history[h].preMod.depth);
else if(flags == DepthTest_LessEqual)
passed = (shadDepth <= history[h].preMod.depth);
else if(flags == DepthTest_Greater)
passed = (shadDepth > history[h].preMod.depth);
else if(flags == DepthTest_GreaterEqual)
passed = (shadDepth >= history[h].preMod.depth);
if(!passed)
history[h].depthTestFailed = true;
rdcpair<float, float> depthBounds = tfCb->GetEventDepthBounds(history[h].eventId);
if((history[h].preMod.depth < depthBounds.first ||
history[h].preMod.depth > depthBounds.second) &&
depthBounds.second > depthBounds.first)
history[h].depthBoundsFailed = true;
}
}
}
SAFE_DELETE(tfCb);
SAFE_RELEASE(pOcclusionQueryHeap);
GetDebugManager()->PixelHistoryDestroyResources(resources);
SAFE_DELETE(shaderCache);
return history;
}