diff --git a/renderdoc/driver/d3d12/d3d12_command_queue.h b/renderdoc/driver/d3d12/d3d12_command_queue.h index 718da9d41..f367b90fc 100644 --- a/renderdoc/driver/d3d12/d3d12_command_queue.h +++ b/renderdoc/driver/d3d12/d3d12_command_queue.h @@ -105,7 +105,7 @@ class WrappedID3D12CommandQueue : public ID3D12CommandQueue, HWND m_pPresentHWND = NULL; ResourceId m_ResourceID; - D3D12ResourceRecord *m_QueueRecord; + D3D12ResourceRecord *m_QueueRecord, *m_CreationRecord; CaptureState &m_State; @@ -148,6 +148,7 @@ public: ResourceId GetResourceID() { return m_ResourceID; } ID3D12CommandQueue *GetReal() { return m_pReal; } D3D12ResourceRecord *GetResourceRecord() { return m_QueueRecord; } + D3D12ResourceRecord *GetCreationRecord() { return m_CreationRecord; } WrappedID3D12Device *GetWrappedDevice() { return m_pDevice; } const rdcarray &GetCmdLists() { return m_CmdListRecords; } D3D12DrawcallTreeNode &GetParentDrawcall() { return m_Cmd.m_ParentDrawcall; } diff --git a/renderdoc/driver/d3d12/d3d12_commands.cpp b/renderdoc/driver/d3d12/d3d12_commands.cpp index 39283af76..c279717e9 100644 --- a/renderdoc/driver/d3d12/d3d12_commands.cpp +++ b/renderdoc/driver/d3d12/d3d12_commands.cpp @@ -339,6 +339,7 @@ WrappedID3D12CommandQueue::WrappedID3D12CommandQueue(ID3D12CommandQueue *real, m_ResourceID = ResourceIDGen::GetNewUniqueID(); m_QueueRecord = NULL; + m_CreationRecord = NULL; m_Cmd.m_pDevice = m_pDevice; @@ -349,6 +350,15 @@ WrappedID3D12CommandQueue::WrappedID3D12CommandQueue(ID3D12CommandQueue *real, m_QueueRecord->DataInSerialiser = false; m_QueueRecord->InternalResource = true; m_QueueRecord->Length = 0; + + // a bit of a hack, we make a parallel resource record with the same lifetime as the command + // queue. It will hold onto our create chunk and not get thrown away as we clear and re-fill + // submissions into the queue record itself. We'll pull it into the capture by marking the + // queues as referenced. + m_CreationRecord = + m_pDevice->GetResourceManager()->AddResourceRecord(ResourceIDGen::GetNewUniqueID()); + m_CreationRecord->type = Resource_CommandQueue; + m_CreationRecord->InternalResource = true; } m_pDevice->GetResourceManager()->AddCurrentResource(GetResourceID(), this); @@ -360,6 +370,9 @@ WrappedID3D12CommandQueue::~WrappedID3D12CommandQueue() { SAFE_DELETE(m_FrameReader); + if(m_CreationRecord) + m_CreationRecord->Delete(m_pDevice->GetResourceManager()); + if(m_QueueRecord) m_QueueRecord->Delete(m_pDevice->GetResourceManager()); m_pDevice->GetResourceManager()->ReleaseCurrentResource(GetResourceID()); diff --git a/renderdoc/driver/d3d12/d3d12_device.cpp b/renderdoc/driver/d3d12/d3d12_device.cpp index 039486eb2..4afeb18ad 100644 --- a/renderdoc/driver/d3d12/d3d12_device.cpp +++ b/renderdoc/driver/d3d12/d3d12_device.cpp @@ -1812,11 +1812,16 @@ void WrappedID3D12Device::StartFrameCapture(void *dev, void *wnd) m_HeaderChunk = scope.Get(); } - // keep all queues alive during the capture, by adding a refcount - for(auto it = m_Queues.begin(); it != m_Queues.end(); ++it) - (*it)->AddRef(); - m_State = CaptureState::ActiveCapturing; + + // keep all queues alive during the capture, by adding a refcount. Also reference the creation + // record so that it's pulled in as initialisation chunks. + for(auto it = m_Queues.begin(); it != m_Queues.end(); ++it) + { + (*it)->AddRef(); + GetResourceManager()->MarkResourceFrameReferenced((*it)->GetCreationRecord()->GetResourceID(), + eFrameRef_Read); + } } GetResourceManager()->MarkResourceFrameReferenced(m_ResourceID, eFrameRef_Read); diff --git a/renderdoc/driver/d3d12/d3d12_device_wrap.cpp b/renderdoc/driver/d3d12/d3d12_device_wrap.cpp index a04f9e634..981bffe7b 100644 --- a/renderdoc/driver/d3d12/d3d12_device_wrap.cpp +++ b/renderdoc/driver/d3d12/d3d12_device_wrap.cpp @@ -114,7 +114,7 @@ HRESULT WrappedID3D12Device::CreateCommandQueue(const D3D12_COMMAND_QUEUE_DESC * SCOPED_SERIALISE_CHUNK(D3D12Chunk::Device_CreateCommandQueue); Serialise_CreateCommandQueue(ser, pDesc, riid, (void **)&wrapped); - m_DeviceRecord->AddChunk(scope.Get()); + wrapped->GetCreationRecord()->AddChunk(scope.Get()); } else { @@ -141,7 +141,11 @@ HRESULT WrappedID3D12Device::CreateCommandQueue(const D3D12_COMMAND_QUEUE_DESC * // while capturing don't allow any queues to be freed, by adding another refcount, since we // gather any commands submitted to them at the end of the capture. if(capframe) + { + GetResourceManager()->MarkResourceFrameReferenced( + wrapped->GetCreationRecord()->GetResourceID(), eFrameRef_Read); wrapped->AddRef(); + } *ppCommandQueue = (ID3D12CommandQueue *)wrapped; } diff --git a/util/test/demos/d3d12/d3d12_resource_lifetimes.cpp b/util/test/demos/d3d12/d3d12_resource_lifetimes.cpp index 316b0fc7f..a942a3f51 100644 --- a/util/test/demos/d3d12/d3d12_resource_lifetimes.cpp +++ b/util/test/demos/d3d12/d3d12_resource_lifetimes.cpp @@ -397,11 +397,17 @@ float4 main(v2f IN) : SV_Target0 descheap = NULL; }; + ID3D12ResourcePtr rtvtex = MakeTexture(DXGI_FORMAT_R8G8B8A8_UNORM_SRGB, screenWidth, screenHeight) + .RTV() + .InitialState(D3D12_RESOURCE_STATE_RENDER_TARGET); + ID3D12ResourcePtr cb = SetupBuf(); ID3D12ResourcePtr img = SetupImg(); ID3D12DescriptorHeapPtr descheap = SetupDescHeap(cb, img); while(Running()) { + D3D12_CPU_DESCRIPTOR_HANDLE offrtv = MakeRTV(rtvtex).CreateCPU(1); + D3D12_CPU_DESCRIPTOR_HANDLE rtv; // acquire and clear the backbuffer @@ -457,6 +463,45 @@ float4 main(v2f IN) : SV_Target0 TrashDescHeap(descheap); } + // use a temporary queue + { + GPUSync(); + + ID3D12CommandQueuePtr tempQueue; + + { + D3D12_COMMAND_QUEUE_DESC desc = {}; + desc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT; + dev->CreateCommandQueue(&desc, __uuidof(ID3D12CommandQueue), (void **)&tempQueue); + } + + ID3D12GraphicsCommandListPtr cmd = GetCommandBuffer(); + + Reset(cmd); + + ClearRenderTargetView(cmd, offrtv, {0.6f, 0.5f, 0.4f, 1.0f}); + + OMSetRenderTargets(cmd, {offrtv}, {}); + + cmd->Close(); + + ID3D12CommandList *submit = cmd.GetInterfacePtr(); + + tempQueue->ExecuteCommandLists(1, &submit); + + // manually insert this into freeCommandBuffers since our normal lifetime management doesn't + // handle submissino on other queues. + freeCommandBuffers.push_back(cmd); + + m_GPUSyncCounter++; + + CHECK_HR(tempQueue->Signal(m_GPUSyncFence, m_GPUSyncCounter)); + CHECK_HR(m_GPUSyncFence->SetEventOnCompletion(m_GPUSyncCounter, m_GPUSyncHandle)); + WaitForSingleObject(m_GPUSyncHandle, 10000); + + tempQueue = NULL; + } + // create resources mid-frame and use then trash them { cb = SetupBuf(); diff --git a/util/test/tests/D3D11/D3D11_Resource_Lifetimes.py b/util/test/tests/D3D11/D3D11_Resource_Lifetimes.py index cb760fcd4..8d4b0de2a 100644 --- a/util/test/tests/D3D11/D3D11_Resource_Lifetimes.py +++ b/util/test/tests/D3D11/D3D11_Resource_Lifetimes.py @@ -4,6 +4,13 @@ import rdtest class D3D11_Resource_Lifetimes(rdtest.TestCase): demos_test_name = 'D3D11_Resource_Lifetimes' + demos_frame_cap = 200 def check_capture(self): self.check_final_backbuffer() + + # Check for resource leaks + if len(self.controller.GetResources()) > 75: + raise rdtest.TestFailureException( + "Too many resources found: {}".format(len(self.controller.GetResources()))) + diff --git a/util/test/tests/D3D12/D3D12_Resource_Lifetimes.py b/util/test/tests/D3D12/D3D12_Resource_Lifetimes.py index d3ec4dbfd..dba07d4ff 100644 --- a/util/test/tests/D3D12/D3D12_Resource_Lifetimes.py +++ b/util/test/tests/D3D12/D3D12_Resource_Lifetimes.py @@ -4,6 +4,12 @@ import rdtest class D3D12_Resource_Lifetimes(rdtest.TestCase): demos_test_name = 'D3D12_Resource_Lifetimes' + demos_frame_cap = 200 def check_capture(self): self.check_final_backbuffer() + + # Check for resource leaks + if len(self.controller.GetResources()) > 75: + raise rdtest.TestFailureException( + "Too many resources found: {}".format(len(self.controller.GetResources()))) diff --git a/util/test/tests/GL/GL_Resource_Lifetimes.py b/util/test/tests/GL/GL_Resource_Lifetimes.py index b2ab5155f..d7a702cfe 100644 --- a/util/test/tests/GL/GL_Resource_Lifetimes.py +++ b/util/test/tests/GL/GL_Resource_Lifetimes.py @@ -4,6 +4,13 @@ import rdtest class GL_Resource_Lifetimes(rdtest.TestCase): demos_test_name = 'GL_Resource_Lifetimes' + demos_frame_cap = 200 def check_capture(self): self.check_final_backbuffer() + + # Check for resource leaks + if len(self.controller.GetResources()) > 75: + raise rdtest.TestFailureException( + "Too many resources found: {}".format(len(self.controller.GetResources()))) + diff --git a/util/test/tests/Vulkan/VK_Resource_Lifetimes.py b/util/test/tests/Vulkan/VK_Resource_Lifetimes.py index ed88f5883..ebcba2e46 100644 --- a/util/test/tests/Vulkan/VK_Resource_Lifetimes.py +++ b/util/test/tests/Vulkan/VK_Resource_Lifetimes.py @@ -4,6 +4,13 @@ import rdtest class VK_Resource_Lifetimes(rdtest.TestCase): demos_test_name = 'VK_Resource_Lifetimes' + demos_frame_cap = 200 def check_capture(self): self.check_final_backbuffer() + + # Check for resource leaks + if len(self.controller.GetResources()) > 75: + raise rdtest.TestFailureException( + "Too many resources found: {}".format(len(self.controller.GetResources()))) +