From 73c604d2f0fd5e690406af5ef3703727315ddbf1 Mon Sep 17 00:00:00 2001 From: Steve Karolewics Date: Sun, 5 Nov 2023 15:13:45 -0800 Subject: [PATCH] Add D3D12 test failed callback for pixel history This callback determines which tests passed or failed for a given draw during pixel history. The set of tests mirrors the Vulkan implementation, with some adjustments for D3D12 capabilities. The code for updating PixelModification structs during pixel history is included as well since they're related. --- renderdoc/driver/d3d12/d3d12_pixelhistory.cpp | 637 ++++++++++++++++++ 1 file changed, 637 insertions(+) diff --git a/renderdoc/driver/d3d12/d3d12_pixelhistory.cpp b/renderdoc/driver/d3d12/d3d12_pixelhistory.cpp index 78c60235cc..2fa31e71c8 100644 --- a/renderdoc/driver/d3d12/d3d12_pixelhistory.cpp +++ b/renderdoc/driver/d3d12/d3d12_pixelhistory.cpp @@ -121,6 +121,36 @@ struct D3D12PipelineReplacements 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 { @@ -1107,6 +1137,613 @@ struct D3D12ColorAndStencilCallback : public D3D12PixelHistoryCallback std::map 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 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; + + // 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; + } + + 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(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(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 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 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(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 = + DXBC::DXBCContainer::CheckForDXIL(pipeDesc.VS.pShaderBytecode, pipeDesc.VS.BytecodeLength); + + ID3DBlob *FixedColorPS = m_ShaderCache->GetFixedColorShader(dxil); + 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(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(eventId, test), index)); + + cmd->BeginQuery(m_OcclusionQueryHeap, D3D12_QUERY_TYPE_OCCLUSION, index); + ::ReplayDraw(cmd, *m_pDevice->GetAction(eventId)); + + cmd->EndQuery(m_OcclusionQueryHeap, D3D12_QUERY_TYPE_OCCLUSION, index); + } + + rdcarray m_Events; + // Key is event ID, value is the flags for that event. + std::map m_EventFlags; + // Key is a pair + std::map, ID3D12PipelineState *> m_PipeCache; + // Key: pair + // value: the index where occlusion query is in m_OcclusionResults + std::map, uint32_t> m_OcclusionQueries; + std::map m_HasEarlyFragments; + rdcarray 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); + } +} + bool D3D12DebugManager::PixelHistorySetupResources(D3D12PixelHistoryResources &resources, WrappedID3D12Resource *targetImage, const D3D12_RESOURCE_DESC &desc,