diff --git a/docs/python_api/renderdoc/pipelines/d3d12.rst b/docs/python_api/renderdoc/pipelines/d3d12.rst index c84e9b25a..97aa4804b 100644 --- a/docs/python_api/renderdoc/pipelines/d3d12.rst +++ b/docs/python_api/renderdoc/pipelines/d3d12.rst @@ -77,6 +77,12 @@ Output Merger .. autoclass:: D3D12BlendState :members: +Predication +----------- + +.. autoclass:: D3D12Predication + :members: + Resource States --------------- diff --git a/qrenderdoc/Windows/PipelineState/D3D12PipelineStateViewer.cpp b/qrenderdoc/Windows/PipelineState/D3D12PipelineStateViewer.cpp index 81399ad99..908ab743c 100644 --- a/qrenderdoc/Windows/PipelineState/D3D12PipelineStateViewer.cpp +++ b/qrenderdoc/Windows/PipelineState/D3D12PipelineStateViewer.cpp @@ -191,6 +191,9 @@ D3D12PipelineStateViewer::D3D12PipelineStateViewer(ICaptureContext &ctx, for(QToolButton *b : sigButtons) QObject::connect(b, &QToolButton::clicked, this, &D3D12PipelineStateViewer::rootSigView_clicked); + QObject::connect(ui->predicateView, &QToolButton::clicked, this, + &D3D12PipelineStateViewer::predicateView_clicked); + for(RDLabel *b : shaderLabels) { b->setAutoFillBackground(true); @@ -207,6 +210,13 @@ D3D12PipelineStateViewer::D3D12PipelineStateViewer(ICaptureContext &ctx, b->setMinimumSizeHint(QSize(100, 0)); } + { + ui->predicate->setAutoFillBackground(true); + ui->predicate->setBackgroundRole(QPalette::ToolTipBase); + ui->predicate->setForegroundRole(QPalette::ToolTipText); + ui->predicate->setMinimumSizeHint(QSize(250, 0)); + } + QObject::connect(m_ComputeDebugSelector, &ComputeDebugSelector::beginDebug, this, &D3D12PipelineStateViewer::computeDebugSelector_beginDebug); @@ -1165,6 +1175,8 @@ void D3D12PipelineStateViewer::clearState() ui->stencils->clear(); + ui->predicateGroup->setVisible(false); + ui->computeDebugSelector->setEnabled(false); } @@ -2075,6 +2087,23 @@ void D3D12PipelineStateViewer::setState() .arg(ToQStr(state.rasterizer.state.shadingRateCombiners.second, GraphicsAPI::D3D12))); ui->shadingRateImage->setText(ToQStr(state.rasterizer.state.shadingRateImage)); + //////////////////////////////////////////////// + // Predication + + if(state.predication.resourceId == ResourceId()) + { + ui->predicateGroup->setVisible(false); + } + else + { + ui->predicateGroup->setVisible(true); + ui->predicate->setText( + tr("%1 + %2 byte offset") + .arg(ToQStr(state.predication.resourceId)) + .arg(Formatter::HumanFormat(state.predication.offset, Formatter::OffsetSize))); + ui->predicateSkipIfZero->setText(state.predication.skipIfZero ? lit("== 0") : lit("!= 0")); + } + //////////////////////////////////////////////// // Output Merger @@ -2636,6 +2665,15 @@ void D3D12PipelineStateViewer::rootSigView_clicked() m_Ctx.AddDockWindow(view->Widget(), DockReference::AddTo, this); } +void D3D12PipelineStateViewer::predicateView_clicked() +{ + IBufferViewer *viewer = m_Ctx.ViewBuffer(m_Ctx.CurD3D12PipelineState()->predication.offset, ~0ULL, + m_Ctx.CurD3D12PipelineState()->predication.resourceId, + lit("ulong predicateValue")); + + m_Ctx.AddDockWindow(viewer->Widget(), DockReference::AddTo, this); +} + void D3D12PipelineStateViewer::shaderSave_clicked() { const D3D12Pipe::Shader *stage = stageForSender(qobject_cast(QObject::sender())); diff --git a/qrenderdoc/Windows/PipelineState/D3D12PipelineStateViewer.h b/qrenderdoc/Windows/PipelineState/D3D12PipelineStateViewer.h index 407d19de0..2500ce445 100644 --- a/qrenderdoc/Windows/PipelineState/D3D12PipelineStateViewer.h +++ b/qrenderdoc/Windows/PipelineState/D3D12PipelineStateViewer.h @@ -75,6 +75,7 @@ private slots: // manual slots void shaderView_clicked(); void rootSigView_clicked(); + void predicateView_clicked(); void shaderSave_clicked(); void resource_itemActivated(RDTreeWidgetItem *item, int column); void cbuffer_itemActivated(RDTreeWidgetItem *item, int column); diff --git a/qrenderdoc/Windows/PipelineState/D3D12PipelineStateViewer.ui b/qrenderdoc/Windows/PipelineState/D3D12PipelineStateViewer.ui index adf015e8d..ea54329d0 100644 --- a/qrenderdoc/Windows/PipelineState/D3D12PipelineStateViewer.ui +++ b/qrenderdoc/Windows/PipelineState/D3D12PipelineStateViewer.ui @@ -2780,7 +2780,81 @@ - + + + + Predication + + + + + + QFrame::Box + + + 4 + + + + + + + PointingHandCursor + + + Open Predicate Buffer + + + View + + + + :/action.png:/action.png + + + Qt::ToolButtonTextBesideIcon + + + true + + + + + + + Actions skipped when value: + + + + + + + + 12 + + + + == 0 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + @@ -2835,7 +2909,7 @@ - + diff --git a/renderdoc/api/replay/d3d12_pipestate.h b/renderdoc/api/replay/d3d12_pipestate.h index 833086c50..87f9e1949 100644 --- a/renderdoc/api/replay/d3d12_pipestate.h +++ b/renderdoc/api/replay/d3d12_pipestate.h @@ -925,6 +925,38 @@ struct RootSignature rdcarray staticSamplers; }; +DOCUMENT("Describes the current state of predicated rendering."); +struct Predication +{ + DOCUMENT(""); + Predication() = default; + Predication(const Predication &) = default; + Predication &operator=(const Predication &) = default; + + DOCUMENT(R"(The :class:`ResourceId` of the buffer used for predicate data. + +If this is unset, predication is not enabled. + +:type: ResourceId +)"); + ResourceId resourceId; + + DOCUMENT(R"(The offset in the buffer where the value is read. + +:type: int +)"); + uint64_t offset = 0; + + DOCUMENT(R"(``True`` if a value of zero in the buffer would lead to draws/dispatches being skipped when +predication is enabled. + +If ``False`` then a zero in the buffer would lead to them being performed as normal. + +:type: bool +)"); + bool skipIfZero = false; +}; + DOCUMENT("The full current D3D12 pipeline state."); struct State { @@ -1017,6 +1049,12 @@ struct State )"); OM outputMerger; + DOCUMENT(R"(The predicated rendering state. + +:type: D3D12Predication +)"); + Predication predication; + DOCUMENT(R"(The resource states for the currently live resources. :type: List[D3D12ResourceData] @@ -1044,4 +1082,5 @@ DECLARE_REFLECTION_STRUCT(D3D12Pipe::RootTableRange); DECLARE_REFLECTION_STRUCT(D3D12Pipe::RootParam); DECLARE_REFLECTION_STRUCT(D3D12Pipe::StaticSampler); DECLARE_REFLECTION_STRUCT(D3D12Pipe::RootSignature); +DECLARE_REFLECTION_STRUCT(D3D12Pipe::Predication); DECLARE_REFLECTION_STRUCT(D3D12Pipe::State); diff --git a/renderdoc/driver/d3d12/d3d12_command_list.h b/renderdoc/driver/d3d12/d3d12_command_list.h index 6c12583b9..ad1ab743b 100644 --- a/renderdoc/driver/d3d12/d3d12_command_list.h +++ b/renderdoc/driver/d3d12/d3d12_command_list.h @@ -185,6 +185,20 @@ private: // the list is reset, and each time the list is submitted the queue takes an additional reference rdcarray m_RayDispatches; + struct OutstandingQuery + { + ID3D12QueryHeap *heap; + D3D12_QUERY_TYPE Type; + UINT Index; + + bool operator==(const OutstandingQuery &q) const + { + return heap == q.heap && Type == q.Type && Index == q.Index; + } + }; + + rdcarray m_OutstandingQueries; + CaptureState &m_State; WrappedID3D12DebugCommandList m_WrappedDebug; diff --git a/renderdoc/driver/d3d12/d3d12_command_list_wrap.cpp b/renderdoc/driver/d3d12/d3d12_command_list_wrap.cpp index e1c420e41..c70b5e7ac 100644 --- a/renderdoc/driver/d3d12/d3d12_command_list_wrap.cpp +++ b/renderdoc/driver/d3d12/d3d12_command_list_wrap.cpp @@ -72,6 +72,11 @@ bool WrappedID3D12GraphicsCommandList::Serialise_Close(SerialiserType &ser) for(int i = 0; i < markerCount; i++) D3D12MarkerRegion::End(list); + for(OutstandingQuery &q : m_OutstandingQueries) + Unwrap(list)->EndQuery(Unwrap(q.heap), q.Type, q.Index); + + m_OutstandingQueries.clear(); + if(m_Cmd->m_ActionCallback) m_Cmd->m_ActionCallback->PreCloseCommandList(list); @@ -2840,10 +2845,19 @@ bool WrappedID3D12GraphicsCommandList::Serialise_BeginQuery(SerialiserType &ser, { if(m_Cmd->InRerecordRange(m_Cmd->m_LastCmdListID)) { + // don't replay query calls if we're just doing one event, it doesn't do anything + if(m_Cmd->m_FirstEventID == 1) + { + Unwrap(m_Cmd->RerecordCmdList(m_Cmd->m_LastCmdListID)) + ->BeginQuery(Unwrap(pQueryHeap), Type, Index); + + m_OutstandingQueries.push_back({pQueryHeap, Type, Index}); + } } } else { + Unwrap(pCommandList)->BeginQuery(Unwrap(pQueryHeap), Type, Index); } } @@ -2884,14 +2898,33 @@ bool WrappedID3D12GraphicsCommandList::Serialise_EndQuery(SerialiserType &ser, { m_Cmd->m_LastCmdListID = GetResID(pCommandList); + WrappedID3D12QueryHeap *queryHeap = (WrappedID3D12QueryHeap *)pQueryHeap; + + // D3D12 requires queries to remain within a command buffer so we don't have to worry about not + // having seen the corresponding begin if(IsActiveReplaying(m_State)) { if(m_Cmd->InRerecordRange(m_Cmd->m_LastCmdListID)) { + // don't replay query calls if we're doing partial replays, it doesn't do anything + if(m_Cmd->m_FirstEventID == 1) + { + Unwrap(m_Cmd->RerecordCmdList(m_Cmd->m_LastCmdListID)) + ->EndQuery(Unwrap(pQueryHeap), Type, Index); + + m_OutstandingQueries.removeOne({pQueryHeap, Type, Index}); + } } } else { + Unwrap(pCommandList)->EndQuery(Unwrap(pQueryHeap), Type, Index); + + // during replay store which queries are issued in the capture itself, so we know which ones + // we can do a 'real' resolve of and which ones must be faked from initial contents if they + // refer to queries from previous frames. + // see the comment in ResolveQueryData for more information + queryHeap->SetQueryValid(Index, Type); } } @@ -2913,6 +2946,16 @@ void WrappedID3D12GraphicsCommandList::EndQuery(ID3D12QueryHeap *pQueryHeap, D3D m_ListRecord->AddChunk(scope.Get(m_ListRecord->cmdInfo->alloc)); m_ListRecord->MarkResourceFrameReferenced(GetResID(pQueryHeap), eFrameRef_Read); + + // during capture store which queries have been issued so we know which ones we can resolve for initial contents + WrappedID3D12QueryHeap *queryHeap = (WrappedID3D12QueryHeap *)pQueryHeap; + AddSubmissionASBuildCallback( + false, + [queryHeap, Index, Type]() { + queryHeap->SetQueryValid(Index, Type); + return true; + }, + NULL); } } @@ -2936,14 +2979,63 @@ bool WrappedID3D12GraphicsCommandList::Serialise_ResolveQueryData( { m_Cmd->m_LastCmdListID = GetResID(pCommandList); + WrappedID3D12QueryHeap *queryHeap = (WrappedID3D12QueryHeap *)pQueryHeap; + if(IsActiveReplaying(m_State)) { if(m_Cmd->InRerecordRange(m_Cmd->m_LastCmdListID)) { + // let the query heap decide which indices to resolve normally and which to fake from the + // stored buffer + queryHeap->ResolveValidQueryData(m_Cmd->RerecordCmdList(m_Cmd->m_LastCmdListID), Type, + StartIndex, NumQueries, pDestinationBuffer, + AlignedDestinationBufferOffset); } } else { + // don't resolve queries during load, since we can't know for certain at record time whether + // or not a query will be valid (it could have been queried in a previous frame so not valid + // to resolve right now, and without knowing the submission order ahead of time we can't + // always know if a re-record in this capture will happen before this resolve). + // + // there are cases we can know this is safe, but we can't detect all cases where it's unsafe, e.g: + // + // [previous frame during capture]: + // EndEvent(Index) + // + // [on replay during captured frame]: + // listA->EndEvent(Index) + // listB->ResolveQueryData(Index) + // + // if listA is submitted first we can resolve normally on listB, but if listB were submitted first + // we'd need to fake or skip the resolve. + // + // What we do is skip resolving during load, then all queries that are ever resolved will have + // some kind of data. This case above would still return the 'wrong' data as we'd do a normal + // resolve, when in fact we should fake the resolve to get last frame's data, but at least we + // won't hit a device lost. In future we could detect this after load once we know the submission order. + // queryHeap->ResolveQueryData(pCommandList, Type, StartIndex, NumQueries, pDestinationBuffer, + // AlignedDestinationBufferOffset); + + { + m_Cmd->AddEvent(); + + ActionDescription action; + + action.copyDestination = GetResID(pDestinationBuffer); + action.copyDestinationSubresource = 0; + + action.flags |= ActionFlags::Resolve; + + m_Cmd->AddAction(action); + + D3D12ActionTreeNode &actionNode = m_Cmd->GetActionStack().back()->children.back(); + + actionNode.resourceUsage.push_back( + make_rdcpair(GetResID(pDestinationBuffer), + EventUsage(actionNode.action.eventId, ResourceUsage::ResolveDst))); + } } } @@ -2992,7 +3084,37 @@ bool WrappedID3D12GraphicsCommandList::Serialise_SetPredication(SerialiserType & { m_Cmd->m_LastCmdListID = GetResID(pCommandList); - // don't replay predication at all + bool stateUpdate = false; + + if(IsActiveReplaying(m_State)) + { + if(m_Cmd->InRerecordRange(m_Cmd->m_LastCmdListID)) + { + Unwrap(m_Cmd->RerecordCmdList(m_Cmd->m_LastCmdListID)) + ->SetPredication(Unwrap(pBuffer), AlignedBufferOffset, Operation); + + stateUpdate = true; + } + else if(!m_Cmd->IsPartialCmdList(m_Cmd->m_LastCmdListID)) + { + stateUpdate = true; + } + } + else + { + Unwrap(pCommandList)->SetPredication(Unwrap(pBuffer), AlignedBufferOffset, Operation); + + stateUpdate = true; + } + + if(stateUpdate) + { + D3D12RenderState &state = m_Cmd->m_BakedCmdListInfo[m_Cmd->m_LastCmdListID].state; + + state.predication.buffer = GetResID(pBuffer); + state.predication.offset = AlignedBufferOffset; + state.predication.op = Operation; + } } return true; diff --git a/renderdoc/driver/d3d12/d3d12_device_wrap.cpp b/renderdoc/driver/d3d12/d3d12_device_wrap.cpp index 84293cf77..e2976af85 100644 --- a/renderdoc/driver/d3d12/d3d12_device_wrap.cpp +++ b/renderdoc/driver/d3d12/d3d12_device_wrap.cpp @@ -1840,7 +1840,7 @@ bool WrappedID3D12Device::Serialise_CreateQueryHeap(SerialiserType &ser, } else { - ret = new WrappedID3D12QueryHeap(pQueryHeap, ret, this); + ret = new WrappedID3D12QueryHeap(pQueryHeap, ret, Descriptor, this); } AddResource(pQueryHeap, ResourceType::Query, "Query Heap"); @@ -1864,7 +1864,7 @@ HRESULT WrappedID3D12Device::CreateQueryHeap(const D3D12_QUERY_HEAP_DESC *pDesc, if(SUCCEEDED(ret)) { - WrappedID3D12QueryHeap *wrapped = new WrappedID3D12QueryHeap(ResourceId(), real, this); + WrappedID3D12QueryHeap *wrapped = new WrappedID3D12QueryHeap(ResourceId(), real, *pDesc, this); if(IsCaptureMode(m_State)) { @@ -1879,6 +1879,8 @@ HRESULT WrappedID3D12Device::CreateQueryHeap(const D3D12_QUERY_HEAP_DESC *pDesc, wrapped->SetResourceRecord(record); record->AddChunk(scope.Get()); + + GetResourceManager()->MarkDirtyResource(wrapped->GetResourceID()); } *ppvHeap = (ID3D12QueryHeap *)wrapped; diff --git a/renderdoc/driver/d3d12/d3d12_initstate.cpp b/renderdoc/driver/d3d12/d3d12_initstate.cpp index 093d64d88..0e6b1c392 100644 --- a/renderdoc/driver/d3d12/d3d12_initstate.cpp +++ b/renderdoc/driver/d3d12/d3d12_initstate.cpp @@ -102,6 +102,55 @@ bool D3D12ResourceManager::Prepare_InitialState(ID3D12DeviceChild *res) SetInitialContents(heap->GetResourceID(), initContents); return true; } + else if(type == Resource_QueryHeap) + { + WrappedID3D12QueryHeap *heap = (WrappedID3D12QueryHeap *)res; + + D3D12_RESOURCE_DESC desc; + + desc.Alignment = 0; + desc.DepthOrArraySize = 1; + desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; + desc.Flags = D3D12_RESOURCE_FLAG_NONE; + desc.Format = DXGI_FORMAT_UNKNOWN; + desc.Height = 1; + desc.Width = heap->GetResolveBufferSize(); + desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; + desc.MipLevels = 1; + desc.SampleDesc.Count = 1; + desc.SampleDesc.Quality = 0; + + HRESULT hr = S_OK; + + ID3D12Resource *copyDst = NULL; + hr = m_Device->CreateInitialStateBuffer(desc, ©Dst); + + if(FAILED(hr)) + { + RDResult error; + SET_ERROR_RESULT(error, ResultCode::OutOfMemory, + "Couldn't create query heap readback buffer: HRESULT: %s", ToStr(hr).c_str()); + m_Device->ReportFatalError(error); + return false; + } + + ID3D12GraphicsCommandList *list = Unwrap(m_Device->GetInitialStateList()); + + heap->SaveValidQueries(list, copyDst); + + if(D3D12_Debug_SingleSubmitFlushing()) + { + m_Device->CloseInitialStateList(); + m_Device->ExecuteLists(NULL, true); + m_Device->FlushLists(true); + } + + D3D12InitialContents initContents(D3D12InitialContents::Copy, copyDst); + initContents.resourceType = Resource_QueryHeap; + + SetInitialContents(GetResID(res), initContents); + return true; + } else if(type == Resource_Resource || type == Resource_Heap) { WrappedID3D12Resource *wrappedResource = (WrappedID3D12Resource *)res; @@ -630,6 +679,14 @@ uint64_t D3D12ResourceManager::GetSize_InitialState(ResourceId id, const D3D12In // add a little extra room for fixed overhead return 64 + data.numDescriptors * descriptorSerSize; } + else if(data.resourceType == Resource_QueryHeap) + { + ID3D12Resource *buf = (ID3D12Resource *)data.resource; + + uint64_t ret = WriteSerialiser::GetChunkAlignment() + 64; + + return ret + uint64_t(buf ? buf->GetDesc().Width : 0); + } else if(data.resourceType == Resource_Resource) { ID3D12Resource *buf = (ID3D12Resource *)data.resource; @@ -911,7 +968,7 @@ bool D3D12ResourceManager::Serialise_InitialState(SerialiserType &ser, ResourceI SetInitialContents(id, D3D12InitialContents(copyheap)); } } - else if(type == Resource_Resource || type == Resource_Heap) + else if(type == Resource_Resource || type == Resource_Heap || type == Resource_QueryHeap) { byte *ResourceContents = NULL; uint64_t ContentsLength = 0; @@ -926,6 +983,8 @@ bool D3D12ResourceManager::Serialise_InitialState(SerialiserType &ser, ResourceI liveRes = (ID3D12Resource *)live; if(type == Resource_Heap) liveRes = ((WrappedID3D12Heap *)live)->GetUnwrappedWholeMemBuffer(); + if(type == Resource_QueryHeap) + liveRes = NULL; } SparseBinds *sparseBinds = NULL; @@ -998,14 +1057,13 @@ bool D3D12ResourceManager::Serialise_InitialState(SerialiserType &ser, ResourceI // only map on replay if we haven't encountered any errors so far if(IsReplayingAndReading() && !ser.IsErrored()) { - D3D12_RESOURCE_DESC resDesc = liveRes->GetDesc(); - D3D12_HEAP_PROPERTIES heapProps = {}; - if(!m_Device->IsSparseResource(id)) + + if(type != Resource_QueryHeap && !m_Device->IsSparseResource(id)) liveRes->GetHeapProperties(&heapProps, NULL); const bool isCPUCopyHeap = - heapProps.Type == D3D12_HEAP_TYPE_CUSTOM && + type != Resource_QueryHeap && heapProps.Type == D3D12_HEAP_TYPE_CUSTOM && (heapProps.CPUPageProperty == D3D12_CPU_PAGE_PROPERTY_WRITE_BACK || heapProps.CPUPageProperty == D3D12_CPU_PAGE_PROPERTY_WRITE_COMBINE) && heapProps.MemoryPoolPreference == D3D12_MEMORY_POOL_L0; @@ -1113,6 +1171,17 @@ bool D3D12ResourceManager::Serialise_InitialState(SerialiserType &ser, ResourceI if(IsReplayingAndReading() && mappedBuffer) { + // for query heaps we can't "apply" the initial contents so we have to pass the buffer over to + // the heap itself to own. + if(type == Resource_QueryHeap) + { + WrappedID3D12QueryHeap *queryHeap = (WrappedID3D12QueryHeap *)GetResource(id); + + queryHeap->SetResultBuffer(mappedBuffer); + + return true; + } + D3D12InitialContents initContents(D3D12InitialContents::Copy, type); initContents.resourceType = type; initContents.resource = mappedBuffer; @@ -1827,6 +1896,10 @@ void D3D12ResourceManager::Create_InitialState(ResourceId id, ID3D12DeviceChild // don't create 'default' AS contents as it's not possible. ASs must be written before being // used by definition } + else if(type == Resource_QueryHeap) + { + // query heap initial contents are handed over on serialise, and don't need to be created + } else { RDCERR("Unexpected type needing an initial state created: %d", type); diff --git a/renderdoc/driver/d3d12/d3d12_overlay.cpp b/renderdoc/driver/d3d12/d3d12_overlay.cpp index eaf652a8e..8e65e85f4 100644 --- a/renderdoc/driver/d3d12/d3d12_overlay.cpp +++ b/renderdoc/driver/d3d12/d3d12_overlay.cpp @@ -1116,6 +1116,7 @@ ResourceId D3D12Replay::RenderOverlay(ResourceId texid, FloatVector clearCol, De D3D12RenderState prev = rs; + rs.predication = {}; rs.pipe = GetResID(pso); rs.rts.resize(1); rs.rts[0] = *GetWrapped(rtv); @@ -1354,6 +1355,8 @@ ResourceId D3D12Replay::RenderOverlay(ResourceId texid, FloatVector clearCol, De if(!list) return ResourceId(); + Unwrap(list)->SetPredication(NULL, 0, D3D12_PREDICATION_OP_EQUAL_ZERO); + for(size_t i = 0; i < rts.size(); i++) { const D3D12Descriptor &desc = rts[i]; @@ -1502,6 +1505,7 @@ ResourceId D3D12Replay::RenderOverlay(ResourceId texid, FloatVector clearCol, De rs.ApplyState(m_pDevice, list); list->OMSetRenderTargets(1, &rtv, TRUE, NULL); + list->SetPredication(NULL, 0, D3D12_PREDICATION_OP_EQUAL_ZERO); D3D12_VIEWPORT viewport = rs.views[0]; list->RSSetViewports(1, &viewport); diff --git a/renderdoc/driver/d3d12/d3d12_replay.cpp b/renderdoc/driver/d3d12/d3d12_replay.cpp index 46f95ea74..7d4638ca0 100644 --- a/renderdoc/driver/d3d12/d3d12_replay.cpp +++ b/renderdoc/driver/d3d12/d3d12_replay.cpp @@ -1697,6 +1697,14 @@ void D3D12Replay::SavePipelineState(uint32_t eventId) } } + ///////////////////////////////////////////////// + // Predication + ///////////////////////////////////////////////// + + state.predication.resourceId = rs.predication.buffer; + state.predication.offset = rs.predication.offset; + state.predication.skipIfZero = rs.predication.op == D3D12_PREDICATION_OP_EQUAL_ZERO; + // resource states { const std::map &states = m_pDevice->GetSubresourceStates(); diff --git a/renderdoc/driver/d3d12/d3d12_resources.cpp b/renderdoc/driver/d3d12/d3d12_resources.cpp index f37dcbec6..b005b0468 100644 --- a/renderdoc/driver/d3d12/d3d12_resources.cpp +++ b/renderdoc/driver/d3d12/d3d12_resources.cpp @@ -1683,3 +1683,91 @@ D3D12_UNORDERED_ACCESS_VIEW_DESC MakeUAVDesc(const D3D12_RESOURCE_DESC &desc) return ret; } + +UINT64 WrappedID3D12QueryHeap::GetResolveDataSize() const +{ + switch(m_Type) + { + case D3D12_QUERY_HEAP_TYPE_OCCLUSION: return sizeof(UINT64); break; + case D3D12_QUERY_HEAP_TYPE_TIMESTAMP: return sizeof(UINT64); break; + case D3D12_QUERY_HEAP_TYPE_PIPELINE_STATISTICS: + return sizeof(D3D12_QUERY_DATA_PIPELINE_STATISTICS); + break; + case D3D12_QUERY_HEAP_TYPE_SO_STATISTICS: return sizeof(D3D12_QUERY_DATA_SO_STATISTICS); break; + case D3D12_QUERY_HEAP_TYPE_VIDEO_DECODE_STATISTICS: + // return sizeof(D3D12_QUERY_DATA_VIDEO_DECODE_STATISTICS); + return sizeof(UINT64) * 3; + break; + case D3D12_QUERY_HEAP_TYPE_COPY_QUEUE_TIMESTAMP: return sizeof(UINT64); break; + case D3D12_QUERY_HEAP_TYPE_PIPELINE_STATISTICS1: + return sizeof(D3D12_QUERY_DATA_PIPELINE_STATISTICS1); + break; + } + + return sizeof(UINT64); +} + +UINT64 WrappedID3D12QueryHeap::GetResolveBufferSize() const +{ + return m_Valid.size() * GetResolveDataSize(); +} + +void WrappedID3D12QueryHeap::SaveValidQueries(ID3D12GraphicsCommandList *unwrappedList, + ID3D12Resource *unwrappedDestBuf) +{ + UINT64 stride = GetResolveDataSize(); + + // find contiguous ranges, for sensible applications there should only be a few ranges rather than scattered queries. + for(UINT i = 0; i < m_Valid.size();) + { + // if we found a valid query, get the size of the range + if(m_Valid[i] != InvalidQueryType) + { + UINT start = i; + + // find the range of queries of the same type - even in an 'occlusion' query heap there could + // be a mix of binary and non-binary occlusion + for(; i < m_Valid.size(); i++) + if(m_Valid[i] != m_Valid[start]) + break; + + UINT num = i - start; + + unwrappedList->ResolveQueryData(GetReal(), m_Valid[start], start, num, unwrappedDestBuf, + stride * start); + } + else + { + i++; + } + } +} + +void WrappedID3D12QueryHeap::ResolveValidQueryData(ID3D12GraphicsCommandList *list, + D3D12_QUERY_TYPE Type, UINT StartIndex, + UINT NumQueries, ID3D12Resource *destBuf, + UINT64 destOffs) +{ + UINT64 stride = GetResolveDataSize(); + + // find contiguous ranges, for sensible applications there should only be a few ranges rather than scattered queries. + for(UINT i = StartIndex, end = StartIndex + NumQueries; i < end;) + { + // doesn't matter if the query is valid or not we will still need to resolve it + UINT start = i; + + for(; i < m_Valid.size(); i++) + if(m_Valid[i] != m_Valid[start]) + break; + + UINT num = i - start; + + // if the query range is valid, resolve it normally + if(m_Valid[start] != InvalidQueryType) + Unwrap(list)->ResolveQueryData(GetReal(), m_Valid[start], start, num, Unwrap(destBuf), + destOffs + stride * (start - StartIndex)); + else if(m_SavedResults) + Unwrap(list)->CopyBufferRegion(Unwrap(destBuf), destOffs + stride * (start - StartIndex), + Unwrap(m_SavedResults), stride * start, stride * num); + } +} diff --git a/renderdoc/driver/d3d12/d3d12_resources.h b/renderdoc/driver/d3d12/d3d12_resources.h index 7ca5b067c..69d7af6e3 100644 --- a/renderdoc/driver/d3d12/d3d12_resources.h +++ b/renderdoc/driver/d3d12/d3d12_resources.h @@ -1416,6 +1416,23 @@ public: class WrappedID3D12QueryHeap : public WrappedDeviceChild12 { + D3D12_QUERY_HEAP_TYPE m_Type = D3D12_QUERY_HEAP_TYPE_OCCLUSION; + + static const D3D12_QUERY_TYPE InvalidQueryType = (D3D12_QUERY_TYPE)~0U; + + // during capture, this stores which queries have ever been issued. Any queries that have been + // issued can be resolved at initial contents time so we have their results for data resolves. + // + // during replay this stores which queries are issued in the capture itself, so we know which ones + // can use a 'real' resolve and which ones must be faked with a buffer query + // + // in both cases it's initialised as all ~0U and is set to the query type each time + rdcarray m_Valid; + + // on replay only, this is the 'initial contents' which is owned here instead of in the usual + // place since we can't apply this just once + ID3D12Resource *m_SavedResults = NULL; + public: ALLOCATE_WITH_WRAPPED_POOL(WrappedID3D12QueryHeap); @@ -1424,11 +1441,28 @@ public: TypeEnum = Resource_QueryHeap, }; - WrappedID3D12QueryHeap(ResourceId id, ID3D12QueryHeap *real, WrappedID3D12Device *device) + WrappedID3D12QueryHeap(ResourceId id, ID3D12QueryHeap *real, const D3D12_QUERY_HEAP_DESC &desc, + WrappedID3D12Device *device) : WrappedDeviceChild12(id, real, device) { + m_Valid.fill(desc.Count, InvalidQueryType); + m_Type = desc.Type; } - virtual ~WrappedID3D12QueryHeap() { Shutdown(); } + virtual ~WrappedID3D12QueryHeap() + { + SAFE_RELEASE(m_SavedResults); + Shutdown(); + } + + void SetQueryValid(UINT idx, D3D12_QUERY_TYPE type) { m_Valid[idx] = type; } + + UINT64 GetResolveDataSize() const; + UINT64 GetResolveBufferSize() const; + void SaveValidQueries(ID3D12GraphicsCommandList *unwrappedList, ID3D12Resource *unwrappedDestBuf); + + void SetResultBuffer(ID3D12Resource *buf) { m_SavedResults = buf; } + void ResolveValidQueryData(ID3D12GraphicsCommandList *list, D3D12_QUERY_TYPE Type, UINT StartIndex, + UINT NumQueries, ID3D12Resource *destBuf, UINT64 destOffs); }; class D3D12AccelerationStructure; diff --git a/renderdoc/driver/d3d12/d3d12_state.cpp b/renderdoc/driver/d3d12/d3d12_state.cpp index 38569f345..d3d305e9e 100644 --- a/renderdoc/driver/d3d12/d3d12_state.cpp +++ b/renderdoc/driver/d3d12/d3d12_state.cpp @@ -222,6 +222,10 @@ void D3D12RenderState::ApplyState(WrappedID3D12Device *dev, ID3D12GraphicsComman cmd->SetPipelineState(pipeState); } + if(predication.buffer != ResourceId()) + cmd->SetPredication(GetResourceManager()->GetResAs(predication.buffer), + predication.offset, predication.op); + if(stateobj != ResourceId()) cmd->SetPipelineState1(GetResourceManager()->GetResAs(stateobj)); diff --git a/renderdoc/driver/d3d12/d3d12_state.h b/renderdoc/driver/d3d12/d3d12_state.h index a1a9b2e95..5ba8da1ae 100644 --- a/renderdoc/driver/d3d12/d3d12_state.h +++ b/renderdoc/driver/d3d12/d3d12_state.h @@ -183,6 +183,13 @@ struct D3D12RenderState }; rdcarray streamouts; + struct Predication + { + ResourceId buffer; + UINT64 offset; + D3D12_PREDICATION_OP op; + } predication; + struct RootSignature { ResourceId rootsig; diff --git a/renderdoc/replay/renderdoc_serialise.inl b/renderdoc/replay/renderdoc_serialise.inl index dc74e9316..14d4b9903 100644 --- a/renderdoc/replay/renderdoc_serialise.inl +++ b/renderdoc/replay/renderdoc_serialise.inl @@ -1644,6 +1644,16 @@ void DoSerialise(SerialiserType &ser, D3D12Pipe::RootSignature &el) SIZE_CHECK(56); } +template +void DoSerialise(SerialiserType &ser, D3D12Pipe::Predication &el) +{ + SERIALISE_MEMBER(resourceId); + SERIALISE_MEMBER(offset); + SERIALISE_MEMBER(skipIfZero); + + SIZE_CHECK(24); +} + template void DoSerialise(SerialiserType &ser, D3D12Pipe::State &el) { @@ -1668,9 +1678,11 @@ void DoSerialise(SerialiserType &ser, D3D12Pipe::State &el) SERIALISE_MEMBER(outputMerger); + SERIALISE_MEMBER(predication); + SERIALISE_MEMBER(resourceStates); - SIZE_CHECK(776); + SIZE_CHECK(800); } #pragma endregion D3D12 pipeline state