Files
renderdoc/renderdoc/driver/d3d12/d3d12_device.cpp
T

3512 lines
105 KiB
C++

/******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2019-2020 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.
******************************************************************************/
#include "d3d12_device.h"
#include <algorithm>
#include "core/core.h"
#include "driver/dxgi/dxgi_common.h"
#include "driver/dxgi/dxgi_wrapped.h"
#include "driver/ihv/amd/amd_rgp.h"
#include "driver/ihv/amd/official/DXExt/AmdExtD3D.h"
#include "jpeg-compressor/jpge.h"
#include "maths/formatpacking.h"
#include "serialise/rdcfile.h"
#include "strings/string_utils.h"
#include "d3d12_command_list.h"
#include "d3d12_command_queue.h"
#include "d3d12_rendertext.h"
#include "d3d12_replay.h"
#include "d3d12_resources.h"
#include "d3d12_shader_cache.h"
WRAPPED_POOL_INST(WrappedID3D12Device);
Threading::CriticalSection WrappedID3D12Device::m_DeviceWrappersLock;
std::map<ID3D12Device *, WrappedID3D12Device *> WrappedID3D12Device::m_DeviceWrappers;
void WrappedID3D12Device::RemoveQueue(WrappedID3D12CommandQueue *queue)
{
m_Queues.removeOne(queue);
}
rdcstr WrappedID3D12Device::GetChunkName(uint32_t idx)
{
if((SystemChunk)idx < SystemChunk::FirstDriverChunk)
return ToStr((SystemChunk)idx);
return ToStr((D3D12Chunk)idx);
}
D3D12DebugManager *WrappedID3D12Device::GetDebugManager()
{
return m_Replay->GetDebugManager();
}
ULONG STDMETHODCALLTYPE DummyID3D12InfoQueue::AddRef()
{
m_pDevice->AddRef();
return 1;
}
ULONG STDMETHODCALLTYPE DummyID3D12InfoQueue::Release()
{
m_pDevice->Release();
return 1;
}
ULONG STDMETHODCALLTYPE DummyID3D12DebugDevice::AddRef()
{
m_pDevice->AddRef();
return 1;
}
ULONG STDMETHODCALLTYPE DummyID3D12DebugDevice::Release()
{
m_pDevice->Release();
return 1;
}
HRESULT STDMETHODCALLTYPE DummyID3D12DebugDevice::QueryInterface(REFIID riid, void **ppvObject)
{
if(riid == __uuidof(ID3D12InfoQueue) || riid == __uuidof(ID3D12DebugDevice) ||
riid == __uuidof(ID3D12Device) || riid == __uuidof(ID3D12Device1) ||
riid == __uuidof(ID3D12Device2) || riid == __uuidof(ID3D12Device3) ||
riid == __uuidof(ID3D12Device4) || riid == __uuidof(ID3D12Device5) ||
riid == __uuidof(ID3D12Device6) || riid == __uuidof(ID3D12Device7) ||
riid == __uuidof(ID3D12Device8))
return m_pDevice->QueryInterface(riid, ppvObject);
if(riid == __uuidof(IUnknown))
{
*ppvObject = (IUnknown *)(ID3D12DebugDevice *)this;
AddRef();
return S_OK;
}
WarnUnknownGUID("ID3D12DebugDevice", riid);
return E_NOINTERFACE;
}
ULONG STDMETHODCALLTYPE WrappedID3D12DebugDevice::AddRef()
{
m_pDevice->AddRef();
return 1;
}
ULONG STDMETHODCALLTYPE WrappedID3D12DebugDevice::Release()
{
m_pDevice->Release();
return 1;
}
HRESULT STDMETHODCALLTYPE WrappedID3D12DebugDevice::QueryInterface(REFIID riid, void **ppvObject)
{
if(riid == __uuidof(ID3D12InfoQueue) || riid == __uuidof(ID3D12DebugDevice) ||
riid == __uuidof(ID3D12Device) || riid == __uuidof(ID3D12Device1) ||
riid == __uuidof(ID3D12Device2) || riid == __uuidof(ID3D12Device3) ||
riid == __uuidof(ID3D12Device4) || riid == __uuidof(ID3D12Device5) ||
riid == __uuidof(ID3D12Device6) || riid == __uuidof(ID3D12Device7) ||
riid == __uuidof(ID3D12Device8))
return m_pDevice->QueryInterface(riid, ppvObject);
if(riid == __uuidof(IUnknown))
{
*ppvObject = (IUnknown *)(ID3D12DebugDevice *)this;
AddRef();
return S_OK;
}
WarnUnknownGUID("ID3D12DebugDevice", riid);
return m_pDebug->QueryInterface(riid, ppvObject);
}
HRESULT STDMETHODCALLTYPE WrappedDownlevelDevice::QueryInterface(REFIID riid, void **ppvObject)
{
return m_pDevice.QueryInterface(riid, ppvObject);
}
ULONG STDMETHODCALLTYPE WrappedDownlevelDevice::AddRef()
{
return m_pDevice.AddRef();
}
ULONG STDMETHODCALLTYPE WrappedDownlevelDevice::Release()
{
return m_pDevice.Release();
}
HRESULT STDMETHODCALLTYPE WrappedDownlevelDevice::QueryVideoMemoryInfo(
UINT NodeIndex, DXGI_MEMORY_SEGMENT_GROUP MemorySegmentGroup,
_Out_ DXGI_QUERY_VIDEO_MEMORY_INFO *pVideoMemoryInfo)
{
return m_pDevice.QueryVideoMemoryInfo(NodeIndex, MemorySegmentGroup, pVideoMemoryInfo);
}
HRESULT STDMETHODCALLTYPE WrappedDRED::QueryInterface(REFIID riid, void **ppvObject)
{
return m_pDevice.QueryInterface(riid, ppvObject);
}
ULONG STDMETHODCALLTYPE WrappedDRED::AddRef()
{
return m_pDevice.AddRef();
}
ULONG STDMETHODCALLTYPE WrappedDRED::Release()
{
return m_pDevice.Release();
}
HRESULT STDMETHODCALLTYPE WrappedDREDSettings::QueryInterface(REFIID riid, void **ppvObject)
{
return m_pDevice.QueryInterface(riid, ppvObject);
}
ULONG STDMETHODCALLTYPE WrappedDREDSettings::AddRef()
{
return m_pDevice.AddRef();
}
ULONG STDMETHODCALLTYPE WrappedDREDSettings::Release()
{
return m_pDevice.Release();
}
WrappedID3D12Device::WrappedID3D12Device(ID3D12Device *realDevice, D3D12InitParams params,
bool enabledDebugLayer)
: m_RefCounter(realDevice, false),
m_SoftRefCounter(NULL, false),
m_pDevice(realDevice),
m_debugLayerEnabled(enabledDebugLayer),
m_WrappedDownlevel(*this),
m_DRED(*this),
m_DREDSettings(*this)
{
if(RenderDoc::Inst().GetCrashHandler())
RenderDoc::Inst().GetCrashHandler()->RegisterMemoryRegion(this, sizeof(WrappedID3D12Device));
m_SectionVersion = D3D12InitParams::CurrentVersion;
m_Replay = new D3D12Replay(this);
m_StructuredFile = &m_StoredStructuredData;
RDCEraseEl(m_D3D12Opts);
RDCEraseEl(m_D3D12Opts1);
RDCEraseEl(m_D3D12Opts2);
RDCEraseEl(m_D3D12Opts3);
m_pDevice1 = NULL;
m_pDevice2 = NULL;
m_pDevice3 = NULL;
m_pDevice4 = NULL;
m_pDevice5 = NULL;
m_pDevice6 = NULL;
m_pDownlevel = NULL;
if(m_pDevice)
{
m_pDevice->QueryInterface(__uuidof(ID3D12Device1), (void **)&m_pDevice1);
m_pDevice->QueryInterface(__uuidof(ID3D12Device2), (void **)&m_pDevice2);
m_pDevice->QueryInterface(__uuidof(ID3D12Device3), (void **)&m_pDevice3);
m_pDevice->QueryInterface(__uuidof(ID3D12Device4), (void **)&m_pDevice4);
m_pDevice->QueryInterface(__uuidof(ID3D12Device5), (void **)&m_pDevice5);
m_pDevice->QueryInterface(__uuidof(ID3D12Device6), (void **)&m_pDevice6);
m_pDevice->QueryInterface(__uuidof(ID3D12DeviceRemovedExtendedData), (void **)&m_DRED.m_pReal);
m_pDevice->QueryInterface(__uuidof(ID3D12DeviceRemovedExtendedDataSettings),
(void **)&m_DREDSettings.m_pReal);
m_pDevice->QueryInterface(__uuidof(ID3D12DeviceDownlevel), (void **)&m_pDownlevel);
for(size_t i = 0; i < ARRAY_COUNT(m_DescriptorIncrements); i++)
m_DescriptorIncrements[i] =
m_pDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE(i));
HRESULT hr = S_OK;
hr = m_pDevice->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS, &m_D3D12Opts,
sizeof(m_D3D12Opts));
RDCASSERTEQUAL(hr, S_OK);
hr = m_pDevice->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS1, &m_D3D12Opts1,
sizeof(m_D3D12Opts1));
RDCASSERTEQUAL(hr, S_OK);
hr = m_pDevice->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS2, &m_D3D12Opts2,
sizeof(m_D3D12Opts2));
RDCASSERTEQUAL(hr, S_OK);
hr = m_pDevice->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS3, &m_D3D12Opts3,
sizeof(m_D3D12Opts3));
RDCASSERTEQUAL(hr, S_OK);
}
// refcounters implicitly construct with one reference, but we don't start with any soft
// references.
m_SoftRefCounter.Release();
m_InternalRefcount = 0;
m_Alive = true;
m_DummyInfoQueue.m_pDevice = this;
m_DummyDebug.m_pDevice = this;
m_WrappedDebug.m_pDevice = this;
threadSerialiserTLSSlot = Threading::AllocateTLSSlot();
tempMemoryTLSSlot = Threading::AllocateTLSSlot();
m_HeaderChunk = NULL;
m_Alloc = m_DataUploadAlloc = NULL;
m_DataUploadList = NULL;
m_GPUSyncFence = NULL;
m_GPUSyncHandle = NULL;
m_GPUSyncCounter = 0;
initStateCurBatch = 0;
initStateCurList = NULL;
m_InitParams = params;
if(RenderDoc::Inst().IsReplayApp())
{
m_State = CaptureState::LoadingReplaying;
if(realDevice)
{
m_ResourceList = new std::map<ResourceId, WrappedID3D12Resource1 *>();
m_PipelineList = new rdcarray<WrappedID3D12PipelineState *>();
}
m_FrameCaptureRecord = NULL;
ResourceIDGen::SetReplayResourceIDs();
}
else
{
m_State = CaptureState::BackgroundCapturing;
if(m_pDevice)
{
typedef HRESULT(WINAPI * PFN_CREATE_DXGI_FACTORY)(REFIID, void **);
PFN_CREATE_DXGI_FACTORY createFunc = (PFN_CREATE_DXGI_FACTORY)GetProcAddress(
GetModuleHandleA("dxgi.dll"), "CreateDXGIFactory1");
IDXGIFactory1 *tmpFactory = NULL;
HRESULT hr = createFunc(__uuidof(IDXGIFactory1), (void **)&tmpFactory);
if(FAILED(hr))
{
RDCERR("Couldn't create DXGI factory! HRESULT: %s", ToStr(hr).c_str());
}
if(tmpFactory)
{
IDXGIAdapter *pDXGIAdapter = NULL;
hr = EnumAdapterByLuid(tmpFactory, m_pDevice->GetAdapterLuid(), &pDXGIAdapter);
if(FAILED(hr))
{
RDCERR("Couldn't get DXGI adapter by LUID from D3D12 device");
}
else
{
DXGI_ADAPTER_DESC desc = {};
pDXGIAdapter->GetDesc(&desc);
m_InitParams.AdapterDesc = desc;
GPUVendor vendor = GPUVendorFromPCIVendor(desc.VendorId);
rdcstr descString = GetDriverVersion(desc);
RDCLOG("New D3D12 device created: %s / %s", ToStr(vendor).c_str(), descString.c_str());
SAFE_RELEASE(pDXGIAdapter);
}
}
SAFE_RELEASE(tmpFactory);
}
}
m_ResourceManager = new D3D12ResourceManager(m_State, this);
// create a temporary and grab its resource ID
m_ResourceID = ResourceIDGen::GetNewUniqueID();
m_DeviceRecord = NULL;
m_Queue = NULL;
if(!RenderDoc::Inst().IsReplayApp())
{
m_DeviceRecord = GetResourceManager()->AddResourceRecord(m_ResourceID);
m_DeviceRecord->type = Resource_Device;
m_DeviceRecord->DataInSerialiser = false;
m_DeviceRecord->InternalResource = true;
m_DeviceRecord->Length = 0;
m_FrameCaptureRecord = GetResourceManager()->AddResourceRecord(ResourceIDGen::GetNewUniqueID());
m_FrameCaptureRecord->DataInSerialiser = false;
m_FrameCaptureRecord->InternalResource = true;
m_FrameCaptureRecord->Length = 0;
RenderDoc::Inst().AddDeviceFrameCapturer((ID3D12Device *)this, this);
}
m_pInfoQueue = NULL;
m_WrappedDebug.m_pDebug = NULL;
m_WrappedDebug.m_pDebug1 = NULL;
m_WrappedDebug.m_pDebug2 = NULL;
if(m_pDevice)
{
m_pDevice->QueryInterface(__uuidof(ID3D12InfoQueue), (void **)&m_pInfoQueue);
m_pDevice->QueryInterface(__uuidof(ID3D12DebugDevice), (void **)&m_WrappedDebug.m_pDebug);
m_pDevice->QueryInterface(__uuidof(ID3D12DebugDevice1), (void **)&m_WrappedDebug.m_pDebug1);
m_pDevice->QueryInterface(__uuidof(ID3D12DebugDevice2), (void **)&m_WrappedDebug.m_pDebug2);
}
if(m_pInfoQueue)
{
if(RenderDoc::Inst().GetCaptureOptions().debugOutputMute)
m_pInfoQueue->SetMuteDebugOutput(true);
UINT size = m_pInfoQueue->GetStorageFilterStackSize();
while(size > 1)
{
m_pInfoQueue->ClearStorageFilter();
size = m_pInfoQueue->GetStorageFilterStackSize();
}
size = m_pInfoQueue->GetRetrievalFilterStackSize();
while(size > 1)
{
m_pInfoQueue->ClearRetrievalFilter();
size = m_pInfoQueue->GetRetrievalFilterStackSize();
}
m_pInfoQueue->ClearStoredMessages();
if(RenderDoc::Inst().IsReplayApp())
{
m_pInfoQueue->SetMuteDebugOutput(false);
D3D12_MESSAGE_ID mute[] = {
// the runtime cries foul when you use normal APIs in expected ways (for simple markers)
D3D12_MESSAGE_ID_CORRUPTED_PARAMETER2,
// super spammy, mostly just perf warning, and impossible to fix for our cases
D3D12_MESSAGE_ID_CLEARRENDERTARGETVIEW_MISMATCHINGCLEARVALUE,
D3D12_MESSAGE_ID_CLEARDEPTHSTENCILVIEW_MISMATCHINGCLEARVALUE,
// caused often by an over-declaration in the root signature to match between
// different shaders, and in some descriptors are entirely skipped. We rely on
// the user to get this right - if the error is non-fatal, any real problems
// will be potentially highlighted in the pipeline view
D3D12_MESSAGE_ID_INVALID_DESCRIPTOR_HANDLE,
D3D12_MESSAGE_ID_COMMAND_LIST_DESCRIPTOR_TABLE_NOT_SET,
// message about a NULL range to map for reading/writing the whole resource
// which is "inefficient" but for our use cases it's almost always what we mean.
D3D12_MESSAGE_ID_MAP_INVALID_NULLRANGE,
// D3D12_MESSAGE_ID_COMMAND_LIST_STATIC_DESCRIPTOR_RESOURCE_DIMENSION_MISMATCH
// message about mismatched SRV dimensions, which it seems to get wrong with the
// dummy NULL descriptors on the texture sampling code
D3D12_MESSAGE_ID_COMMAND_LIST_STATIC_DESCRIPTOR_RESOURCE_DIMENSION_MISMATCH,
};
D3D12_INFO_QUEUE_FILTER filter = {};
filter.DenyList.NumIDs = ARRAY_COUNT(mute);
filter.DenyList.pIDList = mute;
m_pInfoQueue->AddStorageFilterEntries(&filter);
}
}
else
{
RDCDEBUG("Couldn't get ID3D12InfoQueue.");
}
{
SCOPED_LOCK(m_DeviceWrappersLock);
m_DeviceWrappers[m_pDevice] = this;
}
if(!RenderDoc::Inst().IsReplayApp())
{
FirstFrame(NULL);
}
}
WrappedID3D12Device::~WrappedID3D12Device()
{
{
SCOPED_LOCK(m_DeviceWrappersLock);
m_DeviceWrappers.erase(m_pDevice);
}
RenderDoc::Inst().RemoveDeviceFrameCapturer((ID3D12Device *)this);
if(!m_InternalCmds.pendingcmds.empty())
ExecuteLists(m_Queue);
if(!m_InternalCmds.submittedcmds.empty())
FlushLists(true);
for(size_t i = 0; i < m_InternalCmds.freecmds.size(); i++)
SAFE_RELEASE(m_InternalCmds.freecmds[i]);
for(size_t i = 0; i < m_QueueFences.size(); i++)
{
GPUSync(m_Queues[i], m_QueueFences[i]);
SAFE_RELEASE(m_QueueFences[i]);
}
for(auto it = m_UploadBuffers.begin(); it != m_UploadBuffers.end(); ++it)
{
SAFE_RELEASE(it->second);
}
m_Replay->DestroyResources();
DestroyInternalResources();
SAFE_RELEASE(m_Queue);
if(m_DeviceRecord)
{
RDCASSERT(m_DeviceRecord->GetRefCount() == 1);
m_DeviceRecord->Delete(GetResourceManager());
}
if(m_FrameCaptureRecord)
{
RDCASSERT(m_FrameCaptureRecord->GetRefCount() == 1);
m_FrameCaptureRecord->Delete(GetResourceManager());
}
m_ResourceManager->Shutdown();
SAFE_DELETE(m_ResourceManager);
SAFE_RELEASE(m_DRED.m_pReal);
SAFE_RELEASE(m_DREDSettings.m_pReal);
SAFE_RELEASE(m_pDownlevel);
SAFE_RELEASE(m_pDevice6);
SAFE_RELEASE(m_pDevice5);
SAFE_RELEASE(m_pDevice4);
SAFE_RELEASE(m_pDevice3);
SAFE_RELEASE(m_pDevice2);
SAFE_RELEASE(m_pDevice1);
SAFE_RELEASE(m_pInfoQueue);
SAFE_RELEASE(m_WrappedDebug.m_pDebug);
SAFE_RELEASE(m_WrappedDebug.m_pDebug1);
SAFE_RELEASE(m_WrappedDebug.m_pDebug2);
SAFE_RELEASE(m_pDevice);
for(size_t i = 0; i < m_ThreadSerialisers.size(); i++)
delete m_ThreadSerialisers[i];
for(size_t i = 0; i < m_ThreadTempMem.size(); i++)
{
delete[] m_ThreadTempMem[i]->memory;
delete m_ThreadTempMem[i];
}
SAFE_DELETE(m_ResourceList);
SAFE_DELETE(m_PipelineList);
delete m_Replay;
if(RenderDoc::Inst().GetCrashHandler())
RenderDoc::Inst().GetCrashHandler()->UnregisterMemoryRegion(this);
}
WrappedID3D12Device *WrappedID3D12Device::Create(ID3D12Device *realDevice, D3D12InitParams params,
bool enabledDebugLayer)
{
{
SCOPED_LOCK(m_DeviceWrappersLock);
auto it = m_DeviceWrappers.find(realDevice);
if(it != m_DeviceWrappers.end())
{
it->second->AddRef();
return it->second;
}
}
return new WrappedID3D12Device(realDevice, params, enabledDebugLayer);
}
HRESULT WrappedID3D12Device::QueryInterface(REFIID riid, void **ppvObject)
{
// DEFINE_GUID(IID_IDirect3DDevice9, 0xd0223b96, 0xbf7a, 0x43fd, 0x92, 0xbd, 0xa4, 0x3b, 0xd,
// 0x82, 0xb9, 0xeb);
static const GUID IDirect3DDevice9_uuid = {
0xd0223b96, 0xbf7a, 0x43fd, {0x92, 0xbd, 0xa4, 0x3b, 0xd, 0x82, 0xb9, 0xeb}};
// ID3D10Device UUID {9B7E4C0F-342C-4106-A19F-4F2704F689F0}
static const GUID ID3D10Device_uuid = {
0x9b7e4c0f, 0x342c, 0x4106, {0xa1, 0x9f, 0x4f, 0x27, 0x04, 0xf6, 0x89, 0xf0}};
// RenderDoc UUID {A7AA6116-9C8D-4BBA-9083-B4D816B71B78}
static const GUID IRenderDoc_uuid = {
0xa7aa6116, 0x9c8d, 0x4bba, {0x90, 0x83, 0xb4, 0xd8, 0x16, 0xb7, 0x1b, 0x78}};
HRESULT hr = S_OK;
if(riid == __uuidof(IUnknown))
{
*ppvObject = (IUnknown *)(ID3D12Device *)this;
AddRef();
return S_OK;
}
else if(riid == __uuidof(IDXGIDevice))
{
hr = m_pDevice->QueryInterface(riid, ppvObject);
if(SUCCEEDED(hr))
{
IDXGIDevice *real = (IDXGIDevice *)(*ppvObject);
*ppvObject = (IDXGIDevice *)(new WrappedIDXGIDevice4(real, this));
return S_OK;
}
else
{
*ppvObject = NULL;
return hr;
}
}
else if(riid == __uuidof(IDXGIDevice1))
{
hr = m_pDevice->QueryInterface(riid, ppvObject);
if(SUCCEEDED(hr))
{
IDXGIDevice1 *real = (IDXGIDevice1 *)(*ppvObject);
*ppvObject = (IDXGIDevice1 *)(new WrappedIDXGIDevice4(real, this));
return S_OK;
}
else
{
*ppvObject = NULL;
return hr;
}
}
else if(riid == __uuidof(IDXGIDevice2))
{
hr = m_pDevice->QueryInterface(riid, ppvObject);
if(SUCCEEDED(hr))
{
IDXGIDevice2 *real = (IDXGIDevice2 *)(*ppvObject);
*ppvObject = (IDXGIDevice2 *)(new WrappedIDXGIDevice4(real, this));
return S_OK;
}
else
{
*ppvObject = NULL;
return hr;
}
}
else if(riid == __uuidof(IDXGIDevice3))
{
hr = m_pDevice->QueryInterface(riid, ppvObject);
if(SUCCEEDED(hr))
{
IDXGIDevice3 *real = (IDXGIDevice3 *)(*ppvObject);
*ppvObject = (IDXGIDevice3 *)(new WrappedIDXGIDevice4(real, this));
return S_OK;
}
else
{
*ppvObject = NULL;
return hr;
}
}
else if(riid == __uuidof(ID3D12Device))
{
AddRef();
*ppvObject = (ID3D12Device *)this;
return S_OK;
}
else if(riid == __uuidof(ID3D12Device1))
{
if(m_pDevice1)
{
AddRef();
*ppvObject = (ID3D12Device1 *)this;
return S_OK;
}
else
{
return E_NOINTERFACE;
}
}
else if(riid == __uuidof(ID3D12Device2))
{
if(m_pDevice2)
{
AddRef();
*ppvObject = (ID3D12Device2 *)this;
return S_OK;
}
else
{
return E_NOINTERFACE;
}
}
else if(riid == __uuidof(ID3D12Device3))
{
if(m_pDevice3)
{
AddRef();
*ppvObject = (ID3D12Device3 *)this;
return S_OK;
}
else
{
return E_NOINTERFACE;
}
}
else if(riid == __uuidof(ID3D12Device4))
{
if(m_pDevice4)
{
AddRef();
*ppvObject = (ID3D12Device4 *)this;
return S_OK;
}
else
{
return E_NOINTERFACE;
}
}
else if(riid == __uuidof(ID3D12Device5))
{
if(m_pDevice5)
{
AddRef();
*ppvObject = (ID3D12Device5 *)this;
return S_OK;
}
else
{
return E_NOINTERFACE;
}
}
else if(riid == __uuidof(ID3D12Device6))
{
if(m_pDevice6)
{
AddRef();
*ppvObject = (ID3D12Device6 *)this;
return S_OK;
}
else
{
return E_NOINTERFACE;
}
}
else if(riid == __uuidof(ID3D12Device7))
{
return E_NOINTERFACE;
}
else if(riid == __uuidof(ID3D12Device8))
{
return E_NOINTERFACE;
}
else if(riid == __uuidof(ID3D12DeviceDownlevel))
{
if(m_pDownlevel)
{
*ppvObject = &m_WrappedDownlevel;
AddRef();
return S_OK;
}
else
{
return E_NOINTERFACE;
}
}
else if(riid == __uuidof(ID3D12InfoQueue))
{
RDCWARN(
"Returning a dummy ID3D12InfoQueue that does nothing. This ID3D12InfoQueue will not work!");
*ppvObject = (ID3D12InfoQueue *)&m_DummyInfoQueue;
m_DummyInfoQueue.AddRef();
return S_OK;
}
else if(riid == __uuidof(ID3D12DebugDevice))
{
// we queryinterface for this at startup, so if it's present we can
// return our wrapper
if(m_WrappedDebug.m_pDebug)
{
AddRef();
*ppvObject = (ID3D12DebugDevice *)&m_WrappedDebug;
return S_OK;
}
else
{
RDCWARN(
"Returning a dummy ID3D12DebugDevice that does nothing. This ID3D12DebugDevice will not "
"work!");
*ppvObject = (ID3D12DebugDevice *)&m_DummyDebug;
m_DummyDebug.AddRef();
return S_OK;
}
}
else if(riid == __uuidof(ID3D12DebugDevice1))
{
// we queryinterface for this at startup, so if it's present we can
// return our wrapper
if(m_WrappedDebug.m_pDebug1)
{
AddRef();
*ppvObject = (ID3D12DebugDevice1 *)&m_WrappedDebug;
return S_OK;
}
else
{
RDCWARN(
"Returning a dummy ID3D12DebugDevice1 that does nothing. This ID3D12DebugDevice1 will "
"not "
"work!");
*ppvObject = (ID3D12DebugDevice1 *)&m_DummyDebug;
m_DummyDebug.AddRef();
return S_OK;
}
}
else if(riid == __uuidof(ID3D12DebugDevice2))
{
// we queryinterface for this at startup, so if it's present we can
// return our wrapper
if(m_WrappedDebug.m_pDebug1)
{
AddRef();
*ppvObject = (ID3D12DebugDevice2 *)&m_WrappedDebug;
return S_OK;
}
else
{
RDCWARN(
"Returning a dummy ID3D12DebugDevice2 that does nothing. This ID3D12DebugDevice2 will "
"not "
"work!");
*ppvObject = (ID3D12DebugDevice2 *)&m_DummyDebug;
m_DummyDebug.AddRef();
return S_OK;
}
}
else if(riid == __uuidof(ID3D12DeviceRemovedExtendedData))
{
if(m_DRED.m_pReal)
{
AddRef();
*ppvObject = (ID3D12DeviceRemovedExtendedData *)&m_DRED;
return S_OK;
}
else
{
return E_NOINTERFACE;
}
}
else if(riid == __uuidof(ID3D12DeviceRemovedExtendedDataSettings))
{
if(m_DREDSettings.m_pReal)
{
AddRef();
*ppvObject = (ID3D12DeviceRemovedExtendedDataSettings *)&m_DREDSettings;
return S_OK;
}
else
{
return E_NOINTERFACE;
}
}
else if(riid == IRenderDoc_uuid)
{
AddRef();
*ppvObject = (IUnknown *)this;
return S_OK;
}
else
{
WarnUnknownGUID("ID3D12Device", riid);
}
return m_RefCounter.QueryInterface(riid, ppvObject);
}
ID3D12Resource *WrappedID3D12Device::GetUploadBuffer(uint64_t chunkOffset, uint64_t byteSize)
{
ID3D12Resource *buf = m_UploadBuffers[chunkOffset];
if(buf != NULL)
return buf;
D3D12_RESOURCE_DESC soBufDesc;
soBufDesc.Alignment = 0;
soBufDesc.DepthOrArraySize = 1;
soBufDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
soBufDesc.Flags = D3D12_RESOURCE_FLAG_NONE;
soBufDesc.Format = DXGI_FORMAT_UNKNOWN;
soBufDesc.Height = 1;
soBufDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
soBufDesc.MipLevels = 1;
soBufDesc.SampleDesc.Count = 1;
soBufDesc.SampleDesc.Quality = 0;
soBufDesc.Width = byteSize;
D3D12_HEAP_PROPERTIES heapProps;
heapProps.Type = D3D12_HEAP_TYPE_UPLOAD;
heapProps.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
heapProps.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;
heapProps.CreationNodeMask = 1;
heapProps.VisibleNodeMask = 1;
HRESULT hr = CreateCommittedResource(&heapProps, D3D12_HEAP_FLAG_NONE, &soBufDesc,
D3D12_RESOURCE_STATE_GENERIC_READ, NULL,
__uuidof(ID3D12Resource), (void **)&buf);
m_UploadBuffers[chunkOffset] = buf;
RDCASSERT(hr == S_OK, hr, S_OK, byteSize);
return buf;
}
void WrappedID3D12Device::ApplyInitialContents()
{
initStateCurBatch = 0;
initStateCurList = NULL;
GetResourceManager()->ApplyInitialContents();
// close the final list
if(initStateCurList)
{
D3D12MarkerRegion::End(initStateCurList);
initStateCurList->Close();
}
initStateCurBatch = 0;
initStateCurList = NULL;
}
void WrappedID3D12Device::AddCaptureSubmission()
{
if(IsActiveCapturing(m_State))
{
// 15 is quite a lot of submissions.
const int expectedMaxSubmissions = 15;
RenderDoc::Inst().SetProgress(CaptureProgress::FrameCapture, FakeProgress(m_SubmitCounter, 15));
m_SubmitCounter++;
}
}
void WrappedID3D12Device::CheckForDeath()
{
if(!m_Alive)
return;
if(m_RefCounter.GetRefCount() == 0)
{
RDCASSERT(m_SoftRefCounter.GetRefCount() >= m_InternalRefcount);
// MEGA HACK
if(m_SoftRefCounter.GetRefCount() <= m_InternalRefcount || IsReplayMode(m_State))
{
m_Alive = false;
delete this;
}
}
}
void WrappedID3D12Device::FirstFrame(IDXGISwapper *swapper)
{
// if we have to capture the first frame, begin capturing immediately
if(IsBackgroundCapturing(m_State) && RenderDoc::Inst().ShouldTriggerCapture(0))
{
RenderDoc::Inst().StartFrameCapture((ID3D12Device *)this, swapper ? swapper->GetHWND() : NULL);
m_AppControlledCapture = false;
m_CapturedFrames.back().frameNumber = 0;
}
}
void WrappedID3D12Device::ApplyBarriers(rdcarray<D3D12_RESOURCE_BARRIER> &barriers)
{
SCOPED_LOCK(m_ResourceStatesLock);
GetResourceManager()->ApplyBarriers(barriers, m_ResourceStates);
}
void WrappedID3D12Device::ReleaseSwapchainResources(IDXGISwapper *swapper, UINT QueueCount,
IUnknown *const *ppPresentQueue,
IUnknown **unwrappedQueues)
{
if(ppPresentQueue)
{
for(UINT i = 0; i < QueueCount; i++)
{
WrappedID3D12CommandQueue *wrappedQ = (WrappedID3D12CommandQueue *)ppPresentQueue[i];
RDCASSERT(WrappedID3D12CommandQueue::IsAlloc(wrappedQ));
unwrappedQueues[i] = wrappedQ->GetReal();
}
}
for(int i = 0; i < swapper->GetNumBackbuffers(); i++)
{
ID3D12Resource *res = (ID3D12Resource *)swapper->GetBackbuffers()[i];
if(!res)
continue;
WrappedID3D12Resource1 *wrapped = (WrappedID3D12Resource1 *)res;
wrapped->ReleaseInternalRef();
SAFE_RELEASE(wrapped);
}
auto it = m_SwapChains.find(swapper);
if(it != m_SwapChains.end())
{
for(int i = 0; i < swapper->GetNumBackbuffers(); i++)
FreeRTV(it->second.rtvs[i]);
m_SwapChains.erase(it);
}
}
void WrappedID3D12Device::NewSwapchainBuffer(IUnknown *backbuffer)
{
ID3D12Resource *pRes = (ID3D12Resource *)backbuffer;
if(pRes)
{
WrappedID3D12Resource1 *wrapped = (WrappedID3D12Resource1 *)pRes;
wrapped->AddInternalRef();
}
}
template <typename SerialiserType>
bool WrappedID3D12Device::Serialise_WrapSwapchainBuffer(SerialiserType &ser, IDXGISwapper *swapper,
DXGI_FORMAT bufferFormat, UINT Buffer,
IUnknown *realSurface)
{
WrappedID3D12Resource1 *pRes = (WrappedID3D12Resource1 *)realSurface;
SERIALISE_ELEMENT(Buffer);
SERIALISE_ELEMENT_LOCAL(SwapbufferID, GetResID(pRes)).TypedAs("ID3D12Resource *"_lit);
SERIALISE_ELEMENT_LOCAL(BackbufferDescriptor, pRes->GetDesc());
SERIALISE_CHECK_READ_ERRORS();
if(IsReplayingAndReading())
{
ID3D12Resource *fakeBB = NULL;
DXGI_FORMAT SwapbufferFormat = BackbufferDescriptor.Format;
// DXGI swap chain back buffers can be freely cast as a special-case.
// translate the format to a typeless format to allow for this.
// the original type is stored separately below
BackbufferDescriptor.Format = GetTypelessFormat(BackbufferDescriptor.Format);
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;
// create in common, which is the same as present
hr = m_pDevice->CreateCommittedResource(&heapProps, D3D12_HEAP_FLAG_NONE, &BackbufferDescriptor,
D3D12_RESOURCE_STATE_COMMON, NULL,
__uuidof(ID3D12Resource), (void **)&fakeBB);
AddResource(SwapbufferID, ResourceType::SwapchainImage, "Swapchain Image");
if(FAILED(hr))
{
RDCERR("Failed to create fake back buffer, HRESULT: %s", ToStr(hr).c_str());
}
else
{
WrappedID3D12Resource1 *wrapped = new WrappedID3D12Resource1(fakeBB, this);
fakeBB = wrapped;
m_ResourceNames[SwapbufferID] = "Swap Chain Buffer";
fakeBB->SetName(L"Swap Chain Buffer");
GetResourceManager()->AddLiveResource(SwapbufferID, fakeBB);
m_BackbufferFormat[wrapped->GetResourceID()] = SwapbufferFormat;
SubresourceStateVector &states = m_ResourceStates[wrapped->GetResourceID()];
states = {D3D12_RESOURCE_STATE_PRESENT};
}
}
return true;
}
IUnknown *WrappedID3D12Device::WrapSwapchainBuffer(IDXGISwapper *swapper, DXGI_FORMAT bufferFormat,
UINT buffer, IUnknown *realSurface)
{
ID3D12Resource *pRes = NULL;
ID3D12Resource *query = NULL;
realSurface->QueryInterface(__uuidof(ID3D12Resource), (void **)&query);
if(query)
query->Release();
if(GetResourceManager()->HasWrapper(query))
{
pRes = (ID3D12Resource *)GetResourceManager()->GetWrapper(query);
pRes->AddRef();
realSurface->Release();
}
else if(WrappedID3D12Resource1::IsAlloc(query))
{
// this could be possible if we're doing downlevel presenting
pRes = query;
}
else
{
pRes = new WrappedID3D12Resource1((ID3D12Resource *)realSurface, this);
ResourceId id = GetResID(pRes);
// there shouldn't be a resource record for this texture as it wasn't created via
// Create*Resource
RDCASSERT(id != ResourceId() && !GetResourceManager()->HasResourceRecord(id));
if(IsCaptureMode(m_State))
{
D3D12ResourceRecord *record = GetResourceManager()->AddResourceRecord(id);
record->type = Resource_Resource;
record->DataInSerialiser = false;
record->Length = 0;
WrappedID3D12Resource1 *wrapped = (WrappedID3D12Resource1 *)pRes;
wrapped->SetResourceRecord(record);
WriteSerialiser &ser = GetThreadSerialiser();
SCOPED_SERIALISE_CHUNK(D3D12Chunk::CreateSwapBuffer);
Serialise_WrapSwapchainBuffer(ser, swapper, bufferFormat, buffer, pRes);
record->AddChunk(scope.Get());
{
SCOPED_LOCK(m_ResourceStatesLock);
SubresourceStateVector &states = m_ResourceStates[id];
states = {D3D12_RESOURCE_STATE_PRESENT};
}
}
}
if(IsCaptureMode(m_State))
{
D3D12_RENDER_TARGET_VIEW_DESC rtvDesc;
rtvDesc.Format = GetSRGBFormat(bufferFormat);
rtvDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2D;
rtvDesc.Texture2D.MipSlice = 0;
rtvDesc.Texture2D.PlaneSlice = 0;
D3D12_CPU_DESCRIPTOR_HANDLE rtv = AllocRTV();
if(rtv.ptr != 0)
CreateRenderTargetView(pRes, NULL, rtv);
FreeRTV(m_SwapChains[swapper].rtvs[buffer]);
m_SwapChains[swapper].rtvs[buffer] = rtv;
ID3DDevice *swapQ = swapper->GetD3DDevice();
RDCASSERT(WrappedID3D12CommandQueue::IsAlloc(swapQ));
m_SwapChains[swapper].queue = (WrappedID3D12CommandQueue *)swapQ;
}
return pRes;
}
IDXGIResource *WrappedID3D12Device::WrapExternalDXGIResource(IDXGIResource *res)
{
SCOPED_LOCK(m_WrapDeduplicateLock);
ID3D12Resource *d3d12res;
res->QueryInterface(__uuidof(ID3D12Resource), (void **)&d3d12res);
if(GetResourceManager()->HasWrapper(d3d12res))
{
ID3D12DeviceChild *wrapper = GetResourceManager()->GetWrapper(d3d12res);
IDXGIResource *ret = NULL;
wrapper->QueryInterface(__uuidof(IDXGIResource), (void **)&ret);
res->Release();
return ret;
}
void *voidRes = (void *)res;
OpenSharedHandleInternal(D3D12Chunk::Device_ExternalDXGIResource, __uuidof(IDXGIResource),
&voidRes);
return (IDXGIResource *)voidRes;
}
void WrappedID3D12Device::Map(ID3D12Resource *Resource, UINT Subresource)
{
MapState map;
map.res = Resource;
map.subres = Subresource;
D3D12_RESOURCE_DESC desc = Resource->GetDesc();
D3D12_HEAP_PROPERTIES heapProps;
Resource->GetHeapProperties(&heapProps, NULL);
// ignore maps of readback resources, these cannot ever reach the GPU because the resource is
// stuck in COPY_DEST state.
if(heapProps.Type == D3D12_HEAP_TYPE_READBACK)
return;
m_pDevice->GetCopyableFootprints(&desc, Subresource, 1, 0, NULL, NULL, NULL, &map.totalSize);
{
SCOPED_LOCK(m_MapsLock);
m_Maps.push_back(map);
}
}
void WrappedID3D12Device::Unmap(ID3D12Resource *Resource, UINT Subresource, byte *mapPtr,
const D3D12_RANGE *pWrittenRange)
{
MapState map = {};
D3D12_HEAP_PROPERTIES heapProps;
Resource->GetHeapProperties(&heapProps, NULL);
// ignore maps of readback resources, these cannot ever reach the GPU because the resource is
// stuck in COPY_DEST state.
if(heapProps.Type == D3D12_HEAP_TYPE_READBACK)
return;
{
SCOPED_LOCK(m_MapsLock);
map.res = Resource;
map.subres = Subresource;
int32_t idx = m_Maps.indexOf(map);
if(idx < 0)
return;
map = m_Maps.takeAt(idx);
}
bool capframe = false;
{
SCOPED_READLOCK(m_CapTransitionLock);
capframe = IsActiveCapturing(m_State);
}
D3D12_RANGE range = {0, (SIZE_T)map.totalSize};
if(pWrittenRange)
range = *pWrittenRange;
if(capframe && range.End > range.Begin)
MapDataWrite(Resource, Subresource, mapPtr, range);
}
template <typename SerialiserType>
bool WrappedID3D12Device::Serialise_MapDataWrite(SerialiserType &ser, ID3D12Resource *Resource,
UINT Subresource, byte *MappedData,
D3D12_RANGE range)
{
SERIALISE_ELEMENT(Resource);
SERIALISE_ELEMENT(Subresource);
// tracks if we've already uploaded the data to a persistent buffer and don't need to re-serialise
// pointlessly
bool alreadyuploaded = false;
if(IsReplayingAndReading() && Resource)
{
ResourceId origid = GetResourceManager()->GetOriginalID(GetResID(Resource));
if(m_UploadResourceIds.find(origid) != m_UploadResourceIds.end())
{
// while loading, we still have yet to create the buffer so it's not already uploaded. Once
// we've loaded, it is.
alreadyuploaded = IsActiveReplaying(m_State);
}
}
MappedData += range.Begin;
// we serialise MappedData manually, so that when we don't need it we just skip instead of
// actually allocating and memcpy'ing the buffer.
ScopedDeserialiseArray<SerialiserType, byte *> deserialise_map(ser, &MappedData,
range.End - range.Begin);
SerialiserFlags flags = SerialiserFlags::AllocateMemory;
if(alreadyuploaded)
{
// set the array to explicitly NULL (so it doesn't crash on deserialise) and don't allocate.
// This will cause it to be skipped
MappedData = NULL;
flags = SerialiserFlags::NoFlags;
}
ser.Serialise("MappedData"_lit, MappedData, range.End - range.Begin, flags);
SERIALISE_ELEMENT(range);
uint64_t rangeSize = range.End - range.Begin;
SERIALISE_CHECK_READ_ERRORS();
// don't do anything if end <= begin because the range is empty.
if(IsReplayingAndReading() && Resource && range.End > range.Begin)
{
D3D12CommandData &cmd = *m_Queue->GetCommandData();
if(IsLoading(m_State))
cmd.AddUsage(GetResID(Resource), ResourceUsage::CPUWrite);
ResourceId origid = GetResourceManager()->GetOriginalID(GetResID(Resource));
if(m_UploadResourceIds.find(origid) != m_UploadResourceIds.end())
{
ID3D12Resource *uploadBuf = GetUploadBuffer(cmd.m_CurChunkOffset, rangeSize);
SetObjName(uploadBuf,
StringFormat::Fmt("Map data write, %llu bytes for %s/%u @ %llu", rangeSize,
ToStr(origid).c_str(), Subresource, cmd.m_CurChunkOffset));
// during loading, fill out the buffer itself
if(IsLoading(m_State))
{
D3D12_RANGE maprange = {0, 0};
void *dst = NULL;
HRESULT hr = uploadBuf->Map(Subresource, &maprange, &dst);
if(SUCCEEDED(hr))
{
maprange.Begin = 0;
maprange.End = SIZE_T(rangeSize);
memcpy(dst, MappedData, maprange.End);
uploadBuf->Unmap(Subresource, &maprange);
}
else
{
RDCERR("Failed to map resource on replay HRESULT: %s", ToStr(hr).c_str());
}
}
// then afterwards just execute a list to copy the result
m_DataUploadList->Reset(m_DataUploadAlloc, NULL);
m_DataUploadList->CopyBufferRegion(Resource, range.Begin, uploadBuf, 0,
range.End - range.Begin);
m_DataUploadList->Close();
ID3D12CommandList *l = m_DataUploadList;
GetQueue()->ExecuteCommandLists(1, &l);
GPUSync();
}
else
{
byte *dst = NULL;
D3D12_RANGE nopRange = {0, 0};
HRESULT hr = Resource->Map(Subresource, &nopRange, (void **)&dst);
if(SUCCEEDED(hr))
{
memcpy(dst + range.Begin, MappedData, size_t(range.End - range.Begin));
Resource->Unmap(Subresource, &range);
}
else
{
RDCERR("Failed to map resource on replay HRESULT: %s", ToStr(hr).c_str());
}
}
}
return true;
}
template bool WrappedID3D12Device::Serialise_MapDataWrite(ReadSerialiser &ser,
ID3D12Resource *Resource, UINT Subresource,
byte *MappedData, D3D12_RANGE range);
template bool WrappedID3D12Device::Serialise_MapDataWrite(WriteSerialiser &ser,
ID3D12Resource *Resource, UINT Subresource,
byte *MappedData, D3D12_RANGE range);
void WrappedID3D12Device::MapDataWrite(ID3D12Resource *Resource, UINT Subresource, byte *mapPtr,
D3D12_RANGE range)
{
CACHE_THREAD_SERIALISER();
SCOPED_SERIALISE_CHUNK(D3D12Chunk::Resource_Unmap);
Serialise_MapDataWrite(ser, Resource, Subresource, mapPtr, range);
m_FrameCaptureRecord->AddChunk(scope.Get());
GetResourceManager()->MarkResourceFrameReferenced(GetResID(Resource), eFrameRef_PartialWrite);
}
template <typename SerialiserType>
bool WrappedID3D12Device::Serialise_WriteToSubresource(SerialiserType &ser, ID3D12Resource *Resource,
UINT Subresource, const D3D12_BOX *pDstBox,
const void *pSrcData, UINT SrcRowPitch,
UINT SrcDepthPitch)
{
SERIALISE_ELEMENT(Resource);
SERIALISE_ELEMENT(Subresource);
SERIALISE_ELEMENT_OPT(pDstBox);
uint64_t dataSize = 0;
if(ser.IsWriting())
{
D3D12_RESOURCE_DESC desc = Resource->GetDesc();
// for buffers the data size is just the width of the box/resource
if(desc.Dimension == D3D12_RESOURCE_DIMENSION_BUFFER)
{
if(pDstBox)
dataSize = RDCMIN(desc.Width, UINT64(pDstBox->right - pDstBox->left));
else
dataSize = desc.Width;
}
else
{
UINT width = UINT(desc.Width);
UINT height = desc.Height;
// only 3D textures have a depth, array slices are separate subresources.
UINT depth = (desc.Dimension == D3D12_RESOURCE_DIMENSION_TEXTURE3D ? desc.DepthOrArraySize : 1);
// if we have a box, use its dimensions
if(pDstBox)
{
width = RDCMIN(width, pDstBox->right - pDstBox->left);
height = RDCMIN(height, pDstBox->bottom - pDstBox->top);
depth = RDCMIN(depth, pDstBox->back - pDstBox->front);
}
if(IsBlockFormat(desc.Format))
height = RDCMAX(1U, AlignUp4(height) / 4);
else if(IsYUVPlanarFormat(desc.Format))
height = GetYUVNumRows(desc.Format, height);
dataSize = 0;
// if we're copying multiple slices, all but the last one consume SrcDepthPitch bytes
if(depth > 1)
dataSize += SrcDepthPitch * (depth - 1);
// similarly if we're copying multiple rows (possibly block-sized rows) in the final slice
if(height > 1)
dataSize += SrcRowPitch * (height - 1);
// lastly, the final row (or block row) consumes just a tightly packed amount of data
dataSize += GetRowPitch(width, desc.Format, 0);
}
}
SERIALISE_ELEMENT_ARRAY(pSrcData, dataSize);
SERIALISE_ELEMENT(dataSize).Hidden();
SERIALISE_ELEMENT(SrcRowPitch);
SERIALISE_ELEMENT(SrcDepthPitch);
SERIALISE_CHECK_READ_ERRORS();
if(IsReplayingAndReading() && Resource)
{
D3D12CommandData &cmd = *m_Queue->GetCommandData();
if(IsLoading(m_State))
cmd.AddUsage(GetResID(Resource), ResourceUsage::CPUWrite);
ResourceId origid = GetResourceManager()->GetOriginalID(GetResID(Resource));
if(m_UploadResourceIds.find(origid) != m_UploadResourceIds.end())
{
ID3D12Resource *uploadBuf = GetUploadBuffer(cmd.m_CurChunkOffset, dataSize);
// during reading, fill out the buffer itself
if(IsLoading(m_State))
{
D3D12_RANGE range = {0, 0};
void *dst = NULL;
HRESULT hr = uploadBuf->Map(Subresource, &range, &dst);
if(SUCCEEDED(hr))
{
memcpy(dst, pSrcData, (size_t)dataSize);
range.Begin = 0;
range.End = (size_t)dataSize;
uploadBuf->Unmap(Subresource, &range);
}
else
{
RDCERR("Failed to map resource on replay HRESULT: %s", ToStr(hr).c_str());
}
}
// then afterwards just execute a list to copy the result
m_DataUploadList->Reset(m_DataUploadAlloc, NULL);
UINT64 copySize = dataSize;
if(pDstBox)
copySize = RDCMIN(copySize, UINT64(pDstBox->right - pDstBox->left));
m_DataUploadList->CopyBufferRegion(Resource, pDstBox ? pDstBox->left : 0, uploadBuf, 0,
copySize);
m_DataUploadList->Close();
ID3D12CommandList *l = m_DataUploadList;
GetQueue()->ExecuteCommandLists(1, &l);
GPUSync();
}
else
{
HRESULT hr = Resource->Map(Subresource, NULL, NULL);
if(SUCCEEDED(hr))
{
Resource->WriteToSubresource(Subresource, pDstBox, pSrcData, SrcRowPitch, SrcDepthPitch);
Resource->Unmap(Subresource, NULL);
}
else
{
RDCERR("Failed to map resource on replay HRESULT: %s", ToStr(hr).c_str());
}
}
}
return true;
}
template bool WrappedID3D12Device::Serialise_WriteToSubresource(
ReadSerialiser &ser, ID3D12Resource *Resource, UINT Subresource, const D3D12_BOX *pDstBox,
const void *pSrcData, UINT SrcRowPitch, UINT SrcDepthPitch);
template bool WrappedID3D12Device::Serialise_WriteToSubresource(
WriteSerialiser &ser, ID3D12Resource *Resource, UINT Subresource, const D3D12_BOX *pDstBox,
const void *pSrcData, UINT SrcRowPitch, UINT SrcDepthPitch);
void WrappedID3D12Device::WriteToSubresource(ID3D12Resource *Resource, UINT Subresource,
const D3D12_BOX *pDstBox, const void *pSrcData,
UINT SrcRowPitch, UINT SrcDepthPitch)
{
bool capframe = false;
{
SCOPED_READLOCK(m_CapTransitionLock);
capframe = IsActiveCapturing(m_State);
}
if(capframe)
{
CACHE_THREAD_SERIALISER();
SCOPED_SERIALISE_CHUNK(D3D12Chunk::Resource_WriteToSubresource);
Serialise_WriteToSubresource(ser, Resource, Subresource, pDstBox, pSrcData, SrcRowPitch,
SrcDepthPitch);
m_FrameCaptureRecord->AddChunk(scope.Get());
GetResourceManager()->MarkResourceFrameReferenced(GetResID(Resource), eFrameRef_PartialWrite);
}
}
HRESULT WrappedID3D12Device::Present(ID3D12GraphicsCommandList *pOverlayCommandList,
IDXGISwapper *swapper, UINT SyncInterval, UINT Flags)
{
if((Flags & DXGI_PRESENT_TEST) != 0)
return S_OK;
if(IsBackgroundCapturing(m_State))
RenderDoc::Inst().Tick();
m_FrameCounter++; // first present becomes frame #1, this function is at the end of the frame
bool activeWindow = RenderDoc::Inst().IsActiveWindow((ID3D12Device *)this, swapper->GetHWND());
m_LastSwap = swapper;
if(IsBackgroundCapturing(m_State))
{
uint32_t overlay = RenderDoc::Inst().GetOverlayBits();
if(overlay & eRENDERDOC_Overlay_Enabled)
{
SwapPresentInfo &swapInfo = m_SwapChains[swapper];
D3D12_CPU_DESCRIPTOR_HANDLE rtv = swapInfo.rtvs[swapper->GetLastPresentedBuffer()];
if(rtv.ptr)
{
m_TextRenderer->SetOutputDimensions(swapper->GetWidth(), swapper->GetHeight(),
swapper->GetFormat());
ID3D12GraphicsCommandList *list = pOverlayCommandList;
bool submitlist = false;
if(!list)
{
list = GetNewList();
submitlist = true;
}
// buffer will be in common for presentation, transition to render target
D3D12_RESOURCE_BARRIER barrier = {};
barrier.Transition.pResource =
(ID3D12Resource *)swapper->GetBackbuffers()[swapper->GetLastPresentedBuffer()];
barrier.Transition.Subresource = 0;
barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_COMMON;
barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET;
list->ResourceBarrier(1, &barrier);
list->OMSetRenderTargets(1, &rtv, FALSE, NULL);
int flags = activeWindow ? RenderDoc::eOverlay_ActiveWindow : 0;
rdcstr overlayText =
RenderDoc::Inst().GetOverlayText(RDCDriver::D3D12, m_FrameCounter, flags);
m_TextRenderer->RenderText(list, 0.0f, 0.0f, overlayText);
// transition backbuffer back again
std::swap(barrier.Transition.StateBefore, barrier.Transition.StateAfter);
list->ResourceBarrier(1, &barrier);
if(submitlist)
{
list->Close();
ExecuteLists(swapInfo.queue);
FlushLists(false, swapInfo.queue);
}
}
}
}
RenderDoc::Inst().AddActiveDriver(RDCDriver::D3D12, true);
// serialise the present call, even for inactive windows
if(IsActiveCapturing(m_State))
{
SERIALISE_TIME_CALL();
ID3D12Resource *backbuffer = NULL;
if(swapper != NULL)
backbuffer = (ID3D12Resource *)swapper->GetBackbuffers()[swapper->GetLastPresentedBuffer()];
CACHE_THREAD_SERIALISER();
SCOPED_SERIALISE_CHUNK(D3D12Chunk::Swapchain_Present);
Serialise_Present(ser, backbuffer, SyncInterval, Flags);
m_FrameCaptureRecord->AddChunk(scope.Get());
}
if(!activeWindow)
return S_OK;
// kill any current capture that isn't application defined
if(IsActiveCapturing(m_State) && !m_AppControlledCapture)
RenderDoc::Inst().EndFrameCapture((ID3D12Device *)this, swapper->GetHWND());
if(IsBackgroundCapturing(m_State) && RenderDoc::Inst().ShouldTriggerCapture(m_FrameCounter))
{
RenderDoc::Inst().StartFrameCapture((ID3D12Device *)this, swapper->GetHWND());
m_AppControlledCapture = false;
m_CapturedFrames.back().frameNumber = m_FrameCounter;
}
return S_OK;
}
template <typename SerialiserType>
bool WrappedID3D12Device::Serialise_Present(SerialiserType &ser, ID3D12Resource *PresentedImage,
UINT SyncInterval, UINT Flags)
{
SERIALISE_ELEMENT_LOCAL(PresentedBackbuffer, GetResID(PresentedImage))
.TypedAs("ID3D12Resource *"_lit);
// we don't do anything with these parameters, they're just here to store
// them for user benefits
SERIALISE_ELEMENT(SyncInterval);
SERIALISE_ELEMENT(Flags);
SERIALISE_CHECK_READ_ERRORS();
if(IsReplayingAndReading() && IsLoading(m_State))
{
D3D12CommandData &cmd = *m_Queue->GetCommandData();
cmd.AddEvent();
DrawcallDescription draw;
draw.name = StringFormat::Fmt("Present(%s)", ToStr(PresentedBackbuffer).c_str());
draw.flags |= DrawFlags::Present;
cmd.m_LastPresentedImage = PresentedBackbuffer;
draw.copyDestination = PresentedBackbuffer;
cmd.AddDrawcall(draw, true);
}
return true;
}
template bool WrappedID3D12Device::Serialise_Present(ReadSerialiser &ser,
ID3D12Resource *PresentedImage,
UINT SyncInterval, UINT Flags);
template bool WrappedID3D12Device::Serialise_Present(WriteSerialiser &ser,
ID3D12Resource *PresentedImage,
UINT SyncInterval, UINT Flags);
template <typename SerialiserType>
bool WrappedID3D12Device::Serialise_CaptureScope(SerialiserType &ser)
{
SERIALISE_ELEMENT_LOCAL(frameNumber, m_CapturedFrames.back().frameNumber);
SERIALISE_CHECK_READ_ERRORS();
if(IsReplayMode(m_State))
{
GetReplay()->WriteFrameRecord().frameInfo.frameNumber = frameNumber;
RDCEraseEl(GetReplay()->WriteFrameRecord().frameInfo.stats);
}
return true;
}
template <typename SerialiserType>
bool WrappedID3D12Device::Serialise_BeginCaptureFrame(SerialiserType &ser)
{
rdcarray<D3D12_RESOURCE_BARRIER> barriers;
{
SCOPED_LOCK(m_ResourceStatesLock); // not needed on replay, but harmless also
GetResourceManager()->SerialiseResourceStates(ser, barriers, m_ResourceStates);
}
SERIALISE_CHECK_READ_ERRORS();
if(IsReplayingAndReading() && !barriers.empty())
{
// apply initial resource states
ID3D12GraphicsCommandList *list = GetNewList();
list->ResourceBarrier((UINT)barriers.size(), &barriers[0]);
list->Close();
ExecuteLists();
FlushLists();
}
return true;
}
template bool WrappedID3D12Device::Serialise_BeginCaptureFrame(ReadSerialiser &ser);
template bool WrappedID3D12Device::Serialise_BeginCaptureFrame(WriteSerialiser &ser);
void WrappedID3D12Device::EndCaptureFrame()
{
WriteSerialiser &ser = GetThreadSerialiser();
ser.SetDrawChunk();
SCOPED_SERIALISE_CHUNK(SystemChunk::CaptureEnd);
// here for compatibility reasons, this used to store the presented Resource.
SERIALISE_ELEMENT_LOCAL(PresentedBackbuffer, ResourceId()).Hidden();
m_FrameCaptureRecord->AddChunk(scope.Get());
}
void WrappedID3D12Device::StartFrameCapture(void *dev, void *wnd)
{
if(!IsBackgroundCapturing(m_State))
return;
RDCLOG("Starting capture");
m_CaptureTimer.Restart();
m_AppControlledCapture = true;
m_SubmitCounter = 0;
FrameDescription frame;
frame.frameNumber = ~0U;
frame.captureTime = Timing::GetUnixTimestamp();
RDCEraseEl(frame.stats);
m_CapturedFrames.push_back(frame);
GetDebugMessages();
m_DebugMessages.clear();
GetResourceManager()->ClearReferencedResources();
// need to do all this atomically so that no other commands
// will check to see if they need to markdirty or markpendingdirty
// and go into the frame record.
{
SCOPED_WRITELOCK(m_CapTransitionLock);
initStateCurBatch = 0;
initStateCurList = NULL;
GetResourceManager()->PrepareInitialContents();
if(initStateCurList)
{
// close the final list
initStateCurList->Close();
}
initStateCurBatch = 0;
initStateCurList = NULL;
ExecuteLists(NULL, true);
FlushLists();
RDCDEBUG("Attempting capture");
m_FrameCaptureRecord->DeleteChunks();
// fetch and discard debug messages so we don't serialise any messages of our own.
(void)GetDebugMessages();
{
CACHE_THREAD_SERIALISER();
SCOPED_SERIALISE_CHUNK(SystemChunk::CaptureBegin);
Serialise_BeginCaptureFrame(ser);
// need to hold onto this as it must come right after the capture chunk,
// before any command lists
m_HeaderChunk = scope.Get();
}
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);
}
m_RefQueues = m_Queues;
m_RefBuffers = WrappedID3D12Resource1::AddRefBuffersBeforeCapture(GetResourceManager());
}
GetResourceManager()->MarkResourceFrameReferenced(m_ResourceID, eFrameRef_Read);
}
bool WrappedID3D12Device::EndFrameCapture(void *dev, void *wnd)
{
if(!IsActiveCapturing(m_State))
return true;
IDXGISwapper *swapper = NULL;
SwapPresentInfo swapInfo = {};
if(wnd)
{
for(auto it = m_SwapChains.begin(); it != m_SwapChains.end(); ++it)
{
if(it->first->GetHWND() == wnd)
{
swapper = it->first;
swapInfo = it->second;
break;
}
}
if(swapper == NULL)
{
RDCERR("Output window %p provided for frame capture corresponds with no known swap chain", wnd);
return false;
}
}
RDCLOG("Finished capture, Frame %u", m_CapturedFrames.back().frameNumber);
ID3D12Resource *backbuffer = NULL;
if(swapper == NULL)
{
swapper = m_LastSwap;
swapInfo = m_SwapChains[swapper];
}
if(swapper != NULL)
backbuffer = (ID3D12Resource *)swapper->GetBackbuffers()[swapper->GetLastPresentedBuffer()];
rdcarray<WrappedID3D12CommandQueue *> queues;
// transition back to IDLE and readback initial states atomically
{
SCOPED_WRITELOCK(m_CapTransitionLock);
EndCaptureFrame();
queues = m_Queues;
bool ContainsExecuteIndirect = false;
for(auto it = queues.begin(); it != queues.end(); ++it)
ContainsExecuteIndirect |= (*it)->GetResourceRecord()->ContainsExecuteIndirect;
if(ContainsExecuteIndirect)
WrappedID3D12Resource1::RefBuffers(GetResourceManager());
m_State = CaptureState::BackgroundCapturing;
GPUSync();
{
SCOPED_LOCK(m_MapsLock);
for(auto it = m_Maps.begin(); it != m_Maps.end(); ++it)
GetWrapped(it->res)->FreeShadow();
}
}
const uint32_t maxSize = 2048;
RenderDoc::FramePixels fp;
// gather backbuffer screenshot
if(backbuffer != NULL)
{
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.Alignment = 0;
bufDesc.DepthOrArraySize = 1;
bufDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
bufDesc.Flags = D3D12_RESOURCE_FLAG_NONE;
bufDesc.Format = DXGI_FORMAT_UNKNOWN;
bufDesc.Height = 1;
bufDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
bufDesc.MipLevels = 1;
bufDesc.SampleDesc.Count = 1;
bufDesc.SampleDesc.Quality = 0;
bufDesc.Width = 1;
D3D12_RESOURCE_DESC desc = backbuffer->GetDesc();
D3D12_PLACED_SUBRESOURCE_FOOTPRINT layout = {};
m_pDevice->GetCopyableFootprints(&desc, 0, 1, 0, &layout, NULL, NULL, &bufDesc.Width);
ID3D12Resource *copyDst = NULL;
HRESULT hr = m_pDevice->CreateCommittedResource(&heapProps, D3D12_HEAP_FLAG_NONE, &bufDesc,
D3D12_RESOURCE_STATE_COPY_DEST, NULL,
__uuidof(ID3D12Resource), (void **)&copyDst);
if(SUCCEEDED(hr))
{
ID3D12GraphicsCommandList *list = Unwrap(GetNewList());
D3D12_RESOURCE_BARRIER barrier = {};
// we know there's only one subresource, and it will be in PRESENT state
barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
barrier.Transition.pResource = Unwrap(backbuffer);
barrier.Transition.Subresource = 0;
barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_PRESENT;
barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_COPY_SOURCE;
list->ResourceBarrier(1, &barrier);
// copy to readback buffer
D3D12_TEXTURE_COPY_LOCATION dst, src;
src.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
src.pResource = Unwrap(backbuffer);
src.SubresourceIndex = 0;
dst.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT;
dst.pResource = copyDst;
dst.PlacedFootprint = layout;
list->CopyTextureRegion(&dst, 0, 0, 0, &src, NULL);
// transition back
std::swap(barrier.Transition.StateBefore, barrier.Transition.StateAfter);
list->ResourceBarrier(1, &barrier);
list->Close();
ExecuteLists(NULL, true);
FlushLists();
byte *data = NULL;
hr = copyDst->Map(0, NULL, (void **)&data);
if(SUCCEEDED(hr) && data)
{
fp.len = (uint32_t)bufDesc.Width;
fp.data = new uint8_t[fp.len];
memcpy(fp.data, data, fp.len);
ResourceFormat fmt = MakeResourceFormat(desc.Format);
fp.width = (uint32_t)desc.Width;
fp.height = (uint32_t)desc.Height;
fp.pitch = layout.Footprint.RowPitch;
fp.stride = fmt.compByteWidth * fmt.compCount;
fp.bpc = fmt.compByteWidth;
fp.bgra = fmt.BGRAOrder();
fp.max_width = maxSize;
fp.pitch_requirement = 8;
switch(fmt.type)
{
case ResourceFormatType::R10G10B10A2:
fp.stride = 4;
fp.buf1010102 = true;
break;
case ResourceFormatType::R5G6B5:
fp.stride = 2;
fp.buf565 = true;
break;
case ResourceFormatType::R5G5B5A1:
fp.stride = 2;
fp.buf5551 = true;
break;
default: break;
}
copyDst->Unmap(0, NULL);
}
else
{
RDCERR("Couldn't map readback buffer: HRESULT: %s", ToStr(hr).c_str());
}
SAFE_RELEASE(copyDst);
}
else
{
RDCERR("Couldn't create readback buffer: HRESULT: %s", ToStr(hr).c_str());
}
}
RDCFile *rdc =
RenderDoc::Inst().CreateRDC(RDCDriver::D3D12, m_CapturedFrames.back().frameNumber, fp);
StreamWriter *captureWriter = NULL;
if(rdc)
{
SectionProperties props;
// Compress with LZ4 so that it's fast
props.flags = SectionFlags::LZ4Compressed;
props.version = m_SectionVersion;
props.type = SectionType::FrameCapture;
captureWriter = rdc->WriteSection(props);
}
else
{
captureWriter = new StreamWriter(StreamWriter::InvalidStream);
}
{
WriteSerialiser ser(captureWriter, Ownership::Stream);
ser.SetChunkMetadataRecording(GetThreadSerialiser().GetChunkMetadataRecording());
ser.SetUserData(GetResourceManager());
m_InitParams.usedDXIL = m_UsedDXIL;
if(m_UsedDXIL)
{
RDCLOG("Capture used DXIL");
}
{
SCOPED_SERIALISE_CHUNK(SystemChunk::DriverInit, sizeof(D3D12InitParams));
SERIALISE_ELEMENT(m_InitParams);
}
RDCDEBUG("Inserting Resource Serialisers");
GetResourceManager()->InsertReferencedChunks(ser);
GetResourceManager()->InsertInitialContentsChunks(ser);
RDCDEBUG("Creating Capture Scope");
GetResourceManager()->Serialise_InitialContentsNeeded(ser);
{
SCOPED_SERIALISE_CHUNK(SystemChunk::CaptureScope, 16);
Serialise_CaptureScope(ser);
}
m_HeaderChunk->Write(ser);
// don't need to lock access to m_CmdListRecords as we are no longer
// in capframe (the transition is thread-protected) so nothing will be
// pushed to the vector
std::map<int64_t, Chunk *> recordlist;
for(auto it = queues.begin(); it != queues.end(); ++it)
{
WrappedID3D12CommandQueue *q = *it;
const rdcarray<D3D12ResourceRecord *> &cmdListRecords = q->GetCmdLists();
RDCDEBUG("Flushing %u command list records from queue %s", (uint32_t)cmdListRecords.size(),
ToStr(q->GetResourceID()).c_str());
for(size_t i = 0; i < cmdListRecords.size(); i++)
{
uint32_t prevSize = (uint32_t)recordlist.size();
cmdListRecords[i]->Insert(recordlist);
// prevent complaints in release that prevSize is unused
(void)prevSize;
RDCDEBUG("Adding %u chunks to file serialiser from command list %s",
(uint32_t)recordlist.size() - prevSize,
ToStr(cmdListRecords[i]->GetResourceID()).c_str());
}
q->GetResourceRecord()->Insert(recordlist);
}
m_FrameCaptureRecord->Insert(recordlist);
RDCDEBUG("Flushing %u chunks to file serialiser from context record",
(uint32_t)recordlist.size());
float num = float(recordlist.size());
float idx = 0.0f;
for(auto it = recordlist.begin(); it != recordlist.end(); ++it)
{
RenderDoc::Inst().SetProgress(CaptureProgress::SerialiseFrameContents, idx / num);
idx += 1.0f;
it->second->Write(ser);
}
RDCDEBUG("Done");
}
RDCLOG("Captured D3D12 frame with %f MB capture section in %f seconds",
double(captureWriter->GetOffset()) / (1024.0 * 1024.0),
m_CaptureTimer.GetMilliseconds() / 1000.0);
RenderDoc::Inst().FinishCaptureWriting(rdc, m_CapturedFrames.back().frameNumber);
m_HeaderChunk->Delete();
m_HeaderChunk = NULL;
for(auto it = queues.begin(); it != queues.end(); ++it)
(*it)->ClearAfterCapture();
// remove the references held during capture, potentially releasing the queue/buffer.
for(WrappedID3D12CommandQueue *q : m_RefQueues)
q->Release();
for(ID3D12Resource *r : m_RefBuffers)
r->Release();
GetResourceManager()->MarkUnwrittenResources();
GetResourceManager()->ClearReferencedResources();
GetResourceManager()->FreeInitialContents();
return true;
}
bool WrappedID3D12Device::DiscardFrameCapture(void *dev, void *wnd)
{
if(!IsActiveCapturing(m_State))
return true;
RDCLOG("Discarding frame capture.");
RenderDoc::Inst().FinishCaptureWriting(NULL, m_CapturedFrames.back().frameNumber);
m_CapturedFrames.pop_back();
rdcarray<WrappedID3D12CommandQueue *> queues;
// transition back to IDLE and readback initial states atomically
{
SCOPED_WRITELOCK(m_CapTransitionLock);
m_State = CaptureState::BackgroundCapturing;
GPUSync();
{
SCOPED_LOCK(m_MapsLock);
for(auto it = m_Maps.begin(); it != m_Maps.end(); ++it)
GetWrapped(it->res)->FreeShadow();
}
queues = m_Queues;
}
m_HeaderChunk->Delete();
m_HeaderChunk = NULL;
for(auto it = queues.begin(); it != queues.end(); ++it)
(*it)->ClearAfterCapture();
// remove the reference held during capture, potentially releasing the queue.
for(WrappedID3D12CommandQueue *q : m_RefQueues)
q->Release();
for(ID3D12Resource *r : m_RefBuffers)
r->Release();
GetResourceManager()->MarkUnwrittenResources();
GetResourceManager()->ClearReferencedResources();
GetResourceManager()->FreeInitialContents();
return true;
}
void WrappedID3D12Device::ReleaseResource(ID3D12DeviceChild *res)
{
ResourceId id = GetResID(res);
{
SCOPED_LOCK(m_ResourceStatesLock);
m_ResourceStates.erase(id);
}
D3D12ResourceRecord *record = GetRecord(res);
if(record)
record->Delete(GetResourceManager());
// wrapped resources get released all the time, we don't want to
// try and slerp in a resource release. Just the explicit ones
if(IsReplayMode(m_State))
{
if(GetResourceManager()->HasLiveResource(id))
GetResourceManager()->EraseLiveResource(id);
return;
}
}
HRESULT WrappedID3D12Device::CreatePipeState(D3D12_EXPANDED_PIPELINE_STATE_STREAM_DESC &desc,
ID3D12PipelineState **state)
{
if(m_pDevice3)
{
D3D12_PACKED_PIPELINE_STATE_STREAM_DESC packedDesc = desc;
return CreatePipelineState(packedDesc.AsDescStream(), __uuidof(ID3D12PipelineState),
(void **)state);
}
if(desc.CS.BytecodeLength > 0)
{
D3D12_COMPUTE_PIPELINE_STATE_DESC compDesc;
compDesc.pRootSignature = desc.pRootSignature;
compDesc.CS = desc.CS;
compDesc.NodeMask = desc.NodeMask;
compDesc.CachedPSO = desc.CachedPSO;
compDesc.Flags = desc.Flags;
return CreateComputePipelineState(&compDesc, __uuidof(ID3D12PipelineState), (void **)state);
}
else
{
D3D12_GRAPHICS_PIPELINE_STATE_DESC graphicsDesc;
graphicsDesc.pRootSignature = desc.pRootSignature;
graphicsDesc.VS = desc.VS;
graphicsDesc.PS = desc.PS;
graphicsDesc.DS = desc.DS;
graphicsDesc.HS = desc.HS;
graphicsDesc.GS = desc.GS;
graphicsDesc.StreamOutput = desc.StreamOutput;
graphicsDesc.BlendState = desc.BlendState;
graphicsDesc.SampleMask = desc.SampleMask;
graphicsDesc.RasterizerState = desc.RasterizerState;
// graphicsDesc.DepthStencilState = desc.DepthStencilState;
{
graphicsDesc.DepthStencilState.DepthEnable = desc.DepthStencilState.DepthEnable;
graphicsDesc.DepthStencilState.DepthWriteMask = desc.DepthStencilState.DepthWriteMask;
graphicsDesc.DepthStencilState.DepthFunc = desc.DepthStencilState.DepthFunc;
graphicsDesc.DepthStencilState.StencilEnable = desc.DepthStencilState.StencilEnable;
graphicsDesc.DepthStencilState.StencilReadMask = desc.DepthStencilState.StencilReadMask;
graphicsDesc.DepthStencilState.StencilWriteMask = desc.DepthStencilState.StencilWriteMask;
graphicsDesc.DepthStencilState.FrontFace = desc.DepthStencilState.FrontFace;
graphicsDesc.DepthStencilState.BackFace = desc.DepthStencilState.BackFace;
// no DepthBoundsTestEnable
}
graphicsDesc.InputLayout = desc.InputLayout;
graphicsDesc.IBStripCutValue = desc.IBStripCutValue;
graphicsDesc.PrimitiveTopologyType = desc.PrimitiveTopologyType;
graphicsDesc.NumRenderTargets = desc.RTVFormats.NumRenderTargets;
memcpy(graphicsDesc.RTVFormats, desc.RTVFormats.RTFormats, 8 * sizeof(DXGI_FORMAT));
graphicsDesc.DSVFormat = desc.DSVFormat;
graphicsDesc.SampleDesc = desc.SampleDesc;
graphicsDesc.NodeMask = desc.NodeMask;
graphicsDesc.CachedPSO = desc.CachedPSO;
graphicsDesc.Flags = desc.Flags;
return CreateGraphicsPipelineState(&graphicsDesc, __uuidof(ID3D12PipelineState), (void **)state);
}
}
void WrappedID3D12Device::AddDebugMessage(MessageCategory c, MessageSeverity sv, MessageSource src,
rdcstr d)
{
D3D12CommandData &cmd = *m_Queue->GetCommandData();
DebugMessage msg;
msg.eventId = 0;
msg.messageID = 0;
msg.source = src;
msg.category = c;
msg.severity = sv;
msg.description = d;
if(IsActiveReplaying(m_State))
{
// look up the EID this drawcall came from
D3D12CommandData::DrawcallUse use(cmd.m_CurChunkOffset, 0);
auto it = std::lower_bound(cmd.m_DrawcallUses.begin(), cmd.m_DrawcallUses.end(), use);
RDCASSERT(it != cmd.m_DrawcallUses.end());
if(it != cmd.m_DrawcallUses.end())
msg.eventId = it->eventId;
else
RDCERR("Couldn't locate drawcall use for current chunk offset %llu", cmd.m_CurChunkOffset);
AddDebugMessage(msg);
}
else
{
cmd.m_EventMessages.push_back(msg);
}
}
void WrappedID3D12Device::AddDebugMessage(const DebugMessage &msg)
{
m_DebugMessages.push_back(msg);
}
rdcarray<DebugMessage> WrappedID3D12Device::GetDebugMessages()
{
rdcarray<DebugMessage> ret;
if(IsActiveReplaying(m_State))
{
// once we're active replaying, m_DebugMessages will contain all the messages from loading
// (either from the captured serialised messages, or from ourselves during replay).
ret.swap(m_DebugMessages);
return ret;
}
if(IsCaptureMode(m_State))
{
// add any manually-added messages before fetching those from the API
ret.swap(m_DebugMessages);
}
// during loading only try and fetch messages if we're doing that deliberately during replay
if(IsLoading(m_State) && !m_ReplayOptions.apiValidation)
return ret;
// during capture, and during loading if the option is enabled, we fetch messages from the API
if(!m_pInfoQueue)
return ret;
UINT64 numMessages = m_pInfoQueue->GetNumStoredMessagesAllowedByRetrievalFilter();
for(UINT64 i = 0; i < m_pInfoQueue->GetNumStoredMessagesAllowedByRetrievalFilter(); i++)
{
SIZE_T len = 0;
m_pInfoQueue->GetMessage(i, NULL, &len);
char *msgbuf = new char[len];
D3D12_MESSAGE *message = (D3D12_MESSAGE *)msgbuf;
m_pInfoQueue->GetMessage(i, message, &len);
DebugMessage msg;
msg.eventId = 0;
msg.source = MessageSource::API;
msg.category = MessageCategory::Miscellaneous;
msg.severity = MessageSeverity::Medium;
switch(message->Category)
{
case D3D12_MESSAGE_CATEGORY_APPLICATION_DEFINED:
msg.category = MessageCategory::Application_Defined;
break;
case D3D12_MESSAGE_CATEGORY_MISCELLANEOUS:
msg.category = MessageCategory::Miscellaneous;
break;
case D3D12_MESSAGE_CATEGORY_INITIALIZATION:
msg.category = MessageCategory::Initialization;
break;
case D3D12_MESSAGE_CATEGORY_CLEANUP: msg.category = MessageCategory::Cleanup; break;
case D3D12_MESSAGE_CATEGORY_COMPILATION: msg.category = MessageCategory::Compilation; break;
case D3D12_MESSAGE_CATEGORY_STATE_CREATION:
msg.category = MessageCategory::State_Creation;
break;
case D3D12_MESSAGE_CATEGORY_STATE_SETTING:
msg.category = MessageCategory::State_Setting;
break;
case D3D12_MESSAGE_CATEGORY_STATE_GETTING:
msg.category = MessageCategory::State_Getting;
break;
case D3D12_MESSAGE_CATEGORY_RESOURCE_MANIPULATION:
msg.category = MessageCategory::Resource_Manipulation;
break;
case D3D12_MESSAGE_CATEGORY_EXECUTION: msg.category = MessageCategory::Execution; break;
case D3D12_MESSAGE_CATEGORY_SHADER: msg.category = MessageCategory::Shaders; break;
default: RDCWARN("Unexpected message category: %d", message->Category); break;
}
switch(message->Severity)
{
case D3D12_MESSAGE_SEVERITY_CORRUPTION: msg.severity = MessageSeverity::High; break;
case D3D12_MESSAGE_SEVERITY_ERROR: msg.severity = MessageSeverity::High; break;
case D3D12_MESSAGE_SEVERITY_WARNING: msg.severity = MessageSeverity::Medium; break;
case D3D12_MESSAGE_SEVERITY_INFO: msg.severity = MessageSeverity::Low; break;
case D3D12_MESSAGE_SEVERITY_MESSAGE: msg.severity = MessageSeverity::Info; break;
default: RDCWARN("Unexpected message severity: %d", message->Severity); break;
}
msg.messageID = (uint32_t)message->ID;
msg.description = rdcstr(message->pDescription);
// during capture add all messages. Otherwise only add this message if it's different to the
// last one - due to our replay with real and cracked lists we get many duplicated messages
if(!IsLoading(m_State) || ret.empty() || !(ret.back() == msg))
ret.push_back(msg);
SAFE_DELETE_ARRAY(msgbuf);
}
// Docs are fuzzy on the thread safety of the info queue, but I'm going to assume it should only
// ever be accessed on one thread since it's tied to the device & immediate context.
// There doesn't seem to be a way to lock it for access and without that there's no way to know
// that a new message won't be added between the time you retrieve the last one and clearing the
// queue. There is also no way to pop a message that I can see, which would presumably be the
// best way if its member functions are thread safe themselves (if the queue is protected
// internally).
RDCASSERT(numMessages == m_pInfoQueue->GetNumStoredMessagesAllowedByRetrievalFilter());
m_pInfoQueue->ClearStoredMessages();
return ret;
}
template <typename SerialiserType>
bool WrappedID3D12Device::Serialise_SetShaderDebugPath(SerialiserType &ser,
ID3D12DeviceChild *pResource, const char *Path)
{
SERIALISE_ELEMENT(pResource);
SERIALISE_ELEMENT(Path);
SERIALISE_CHECK_READ_ERRORS();
if(IsReplayingAndReading() && pResource)
{
RDCERR("SetDebugInfoPath doesn't work as-is because it can't specify a shader specific path");
}
return true;
}
template bool WrappedID3D12Device::Serialise_SetShaderDebugPath(ReadSerialiser &ser,
ID3D12DeviceChild *pResource,
const char *Path);
template bool WrappedID3D12Device::Serialise_SetShaderDebugPath(WriteSerialiser &ser,
ID3D12DeviceChild *pResource,
const char *Path);
HRESULT WrappedID3D12Device::SetShaderDebugPath(ID3D12DeviceChild *pResource, const char *Path)
{
if(IsCaptureMode(m_State))
{
D3D12ResourceRecord *record = GetRecord(pResource);
if(record == NULL)
{
RDCERR("Setting shader debug path on object %p of type %d that has no resource record.",
pResource, IdentifyTypeByPtr(pResource));
return E_INVALIDARG;
}
{
WriteSerialiser &ser = GetThreadSerialiser();
SCOPED_SERIALISE_CHUNK(D3D12Chunk::SetShaderDebugPath);
Serialise_SetShaderDebugPath(ser, pResource, Path);
record->AddChunk(scope.Get());
}
}
return S_OK;
}
template <typename SerialiserType>
bool WrappedID3D12Device::Serialise_SetName(SerialiserType &ser, ID3D12DeviceChild *pResource,
const char *Name)
{
SERIALISE_ELEMENT(pResource);
SERIALISE_ELEMENT(Name);
SERIALISE_CHECK_READ_ERRORS();
if(IsReplayingAndReading() && pResource)
{
ResourceId origId = GetResourceManager()->GetOriginalID(GetResID(pResource));
m_ResourceNames[origId] = Name ? Name : "";
ResourceDescription &descr = GetReplay()->GetResourceDesc(origId);
descr.SetCustomName(Name ? Name : "");
AddResourceCurChunk(descr);
pResource->SetName(StringFormat::UTF82Wide(Name ? Name : "").c_str());
}
return true;
}
void WrappedID3D12Device::SetName(ID3D12DeviceChild *pResource, const char *Name)
{
// don't allow naming device contexts or command lists so we know this chunk
// is always on a pre-capture chunk.
if(IsCaptureMode(m_State) && !WrappedID3D12GraphicsCommandList::IsAlloc(pResource) &&
!WrappedID3D12CommandQueue::IsAlloc(pResource))
{
D3D12ResourceRecord *record = GetRecord(pResource);
if(record == NULL)
record = m_DeviceRecord;
{
WriteSerialiser &ser = GetThreadSerialiser();
SCOPED_SERIALISE_CHUNK(D3D12Chunk::SetName);
Serialise_SetName(ser, pResource, Name);
// don't serialise many SetName chunks to the
// object record, but we can't afford to drop any.
record->LockChunks();
while(record->HasChunks())
{
Chunk *end = record->GetLastChunk();
if(end->GetChunkType<D3D12Chunk>() == D3D12Chunk::SetName)
{
end->Delete();
record->PopChunk();
continue;
}
break;
}
record->UnlockChunks();
record->AddChunk(scope.Get());
}
}
}
HRESULT STDMETHODCALLTYPE WrappedID3D12Device::QueryVideoMemoryInfo(
UINT NodeIndex, DXGI_MEMORY_SEGMENT_GROUP MemorySegmentGroup,
_Out_ DXGI_QUERY_VIDEO_MEMORY_INFO *pVideoMemoryInfo)
{
return m_pDownlevel->QueryVideoMemoryInfo(NodeIndex, MemorySegmentGroup, pVideoMemoryInfo);
}
HRESULT STDMETHODCALLTYPE WrappedID3D12Device::SetBackgroundProcessingMode(
D3D12_BACKGROUND_PROCESSING_MODE Mode, D3D12_MEASUREMENTS_ACTION MeasurementsAction,
_In_opt_ HANDLE hEventToSignalUponCompletion, _Out_opt_ BOOL *pbFurtherMeasurementsDesired)
{
return m_pDevice6->SetBackgroundProcessingMode(
Mode, MeasurementsAction, hEventToSignalUponCompletion, pbFurtherMeasurementsDesired);
}
byte *WrappedID3D12Device::GetTempMemory(size_t s)
{
TempMem *mem = (TempMem *)Threading::GetTLSValue(tempMemoryTLSSlot);
if(mem && mem->size >= s)
return mem->memory;
// alloc or grow alloc
TempMem *newmem = mem;
if(!newmem)
newmem = new TempMem();
// free old memory, don't need to keep contents
if(newmem->memory)
delete[] newmem->memory;
// alloc new memory
newmem->size = s;
newmem->memory = new byte[s];
Threading::SetTLSValue(tempMemoryTLSSlot, (void *)newmem);
// if this is entirely new, save it for deletion on shutdown
if(!mem)
{
SCOPED_LOCK(m_ThreadTempMemLock);
m_ThreadTempMem.push_back(newmem);
}
return newmem->memory;
}
WriteSerialiser &WrappedID3D12Device::GetThreadSerialiser()
{
WriteSerialiser *ser = (WriteSerialiser *)Threading::GetTLSValue(threadSerialiserTLSSlot);
if(ser)
return *ser;
// slow path, but rare
ser = new WriteSerialiser(new StreamWriter(1024), Ownership::Stream);
uint32_t flags = WriteSerialiser::ChunkDuration | WriteSerialiser::ChunkTimestamp |
WriteSerialiser::ChunkThreadID;
if(RenderDoc::Inst().GetCaptureOptions().captureCallstacks)
flags |= WriteSerialiser::ChunkCallstack;
ser->SetChunkMetadataRecording(flags);
ser->SetUserData(GetResourceManager());
ser->SetVersion(D3D12InitParams::CurrentVersion);
Threading::SetTLSValue(threadSerialiserTLSSlot, (void *)ser);
{
SCOPED_LOCK(m_ThreadSerialisersLock);
m_ThreadSerialisers.push_back(ser);
}
return *ser;
}
D3D12_CPU_DESCRIPTOR_HANDLE WrappedID3D12Device::AllocRTV()
{
if(m_FreeRTVs.empty())
{
RDCERR("Not enough free RTVs for swapchain overlays!");
return D3D12_CPU_DESCRIPTOR_HANDLE();
}
D3D12_CPU_DESCRIPTOR_HANDLE ret = m_FreeRTVs.back();
m_FreeRTVs.pop_back();
m_UsedRTVs.push_back(ret);
return ret;
}
void WrappedID3D12Device::FreeRTV(D3D12_CPU_DESCRIPTOR_HANDLE handle)
{
if(handle.ptr == 0)
return;
int32_t idx = m_UsedRTVs.indexOf(handle);
if(idx >= 0)
{
m_UsedRTVs.erase(idx);
m_FreeRTVs.push_back(handle);
}
else
{
RDCERR("Unknown RTV %zu being freed", handle.ptr);
}
}
void WrappedID3D12Device::CreateInternalResources()
{
if(IsReplayMode(m_State))
{
// Initialise AMD extension, if possible
HMODULE mod = GetModuleHandleA("amdxc64.dll");
m_pAMDExtObject = NULL;
if(mod)
{
PFNAmdExtD3DCreateInterface pAmdExtD3dCreateFunc =
(PFNAmdExtD3DCreateInterface)GetProcAddress(mod, "AmdExtD3DCreateInterface");
if(pAmdExtD3dCreateFunc != NULL)
{
// Initialize extension object
pAmdExtD3dCreateFunc(m_pDevice, __uuidof(IAmdExtD3DFactory), (void **)&m_pAMDExtObject);
}
}
}
// we don't want replay-only shaders added in WrappedID3D12Shader to pollute the list of resources
WrappedID3D12Shader::InternalResources(true);
CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, __uuidof(ID3D12CommandAllocator),
(void **)&m_Alloc);
InternalRef();
CreateFence(0, D3D12_FENCE_FLAG_NONE, __uuidof(ID3D12Fence), (void **)&m_GPUSyncFence);
InternalRef();
m_GPUSyncHandle = ::CreateEvent(NULL, FALSE, FALSE, NULL);
GetResourceManager()->SetInternalResource(m_Alloc);
GetResourceManager()->SetInternalResource(m_GPUSyncFence);
CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, __uuidof(ID3D12CommandAllocator),
(void **)&m_DataUploadAlloc);
InternalRef();
GetResourceManager()->SetInternalResource(m_DataUploadAlloc);
CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, m_DataUploadAlloc, NULL,
__uuidof(ID3D12GraphicsCommandList), (void **)&m_DataUploadList);
InternalRef();
D3D12_DESCRIPTOR_HEAP_DESC desc;
desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
desc.NodeMask = 1;
desc.NumDescriptors = 1024;
desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
CreateDescriptorHeap(&desc, __uuidof(ID3D12DescriptorHeap), (void **)&m_RTVHeap);
InternalRef();
GetResourceManager()->SetInternalResource(m_RTVHeap);
D3D12_CPU_DESCRIPTOR_HANDLE handle;
if(m_RTVHeap)
{
handle = m_RTVHeap->GetCPUDescriptorHandleForHeapStart();
UINT step = GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
for(size_t i = 0; i < 1024; i++)
{
m_FreeRTVs.push_back(handle);
handle.ptr += step;
}
}
else
{
RDCERR("Failed to create RTV heap");
}
m_DataUploadList->Close();
m_GPUSyncCounter = 0;
if(m_ShaderCache == NULL)
m_ShaderCache = new D3D12ShaderCache();
if(m_TextRenderer == NULL)
m_TextRenderer = new D3D12TextRenderer(this);
m_Replay->CreateResources();
WrappedID3D12Shader::InternalResources(false);
}
void WrappedID3D12Device::DestroyInternalResources()
{
if(m_GPUSyncHandle == NULL)
return;
SAFE_RELEASE(m_pAMDExtObject);
ExecuteLists();
FlushLists(true);
SAFE_RELEASE(m_RTVHeap);
SAFE_DELETE(m_TextRenderer);
SAFE_DELETE(m_ShaderCache);
SAFE_RELEASE(m_DataUploadList);
SAFE_RELEASE(m_DataUploadAlloc);
SAFE_RELEASE(m_Alloc);
SAFE_RELEASE(m_GPUSyncFence);
CloseHandle(m_GPUSyncHandle);
}
void WrappedID3D12Device::GPUSync(ID3D12CommandQueue *queue, ID3D12Fence *fence)
{
m_GPUSyncCounter++;
if(queue == NULL)
queue = GetQueue();
if(fence == NULL)
fence = m_GPUSyncFence;
HRESULT hr = queue->Signal(fence, m_GPUSyncCounter);
fence->SetEventOnCompletion(m_GPUSyncCounter, m_GPUSyncHandle);
WaitForSingleObject(m_GPUSyncHandle, 10000);
RDCASSERTEQUAL(hr, S_OK);
hr = m_pDevice->GetDeviceRemovedReason();
RDCASSERTEQUAL(hr, S_OK);
}
void WrappedID3D12Device::GPUSyncAllQueues()
{
for(size_t i = 0; i < m_QueueFences.size(); i++)
GPUSync(m_Queues[i], m_QueueFences[i]);
}
ID3D12GraphicsCommandListX *WrappedID3D12Device::GetNewList()
{
ID3D12GraphicsCommandListX *ret = NULL;
if(!m_InternalCmds.freecmds.empty())
{
ret = m_InternalCmds.freecmds.back();
m_InternalCmds.freecmds.pop_back();
ret->Reset(m_Alloc, NULL);
}
else
{
ID3D12GraphicsCommandList *list = NULL;
HRESULT hr = CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, m_Alloc, NULL,
__uuidof(ID3D12GraphicsCommandList), (void **)&list);
InternalRef();
// safe to upcast because this is a wrapped object.
ret = (ID3D12GraphicsCommandListX *)list;
RDCASSERTEQUAL(hr, S_OK);
if(ret == NULL)
return NULL;
if(IsReplayMode(m_State))
{
GetResourceManager()->AddLiveResource(GetResID(ret), ret);
// add a reference here so that when we release our internal resources on destruction we don't
// free this too soon before the resource manager can. We still want to have it tracked as a
// resource in the manager though.
ret->AddRef();
}
}
m_InternalCmds.pendingcmds.push_back(ret);
return ret;
}
ID3D12GraphicsCommandListX *WrappedID3D12Device::GetInitialStateList()
{
if(initStateCurBatch >= initialStateMaxBatch)
{
CloseInitialStateList();
}
if(initStateCurList == NULL)
{
initStateCurList = GetNewList();
if(IsReplayMode(m_State))
{
D3D12MarkerRegion::Begin(initStateCurList,
"!!!!RenderDoc Internal: ApplyInitialContents batched list");
}
}
return initStateCurList;
}
void WrappedID3D12Device::CloseInitialStateList()
{
D3D12MarkerRegion::End(initStateCurList);
initStateCurList->Close();
initStateCurList = NULL;
initStateCurBatch = 0;
}
void WrappedID3D12Device::ExecuteList(ID3D12GraphicsCommandListX *list,
WrappedID3D12CommandQueue *queue, bool InFrameCaptureBoundary)
{
if(queue == NULL)
queue = GetQueue();
ID3D12CommandList *l = list;
queue->ExecuteCommandListsInternal(1, &l, InFrameCaptureBoundary, false);
MarkListExecuted(list);
}
void WrappedID3D12Device::MarkListExecuted(ID3D12GraphicsCommandListX *list)
{
m_InternalCmds.pendingcmds.removeOne(list);
m_InternalCmds.submittedcmds.push_back(list);
}
void WrappedID3D12Device::ExecuteLists(WrappedID3D12CommandQueue *queue, bool InFrameCaptureBoundary)
{
// nothing to do
if(m_InternalCmds.pendingcmds.empty())
return;
rdcarray<ID3D12CommandList *> cmds;
cmds.resize(m_InternalCmds.pendingcmds.size());
for(size_t i = 0; i < cmds.size(); i++)
cmds[i] = m_InternalCmds.pendingcmds[i];
if(queue == NULL)
queue = GetQueue();
queue->ExecuteCommandListsInternal((UINT)cmds.size(), &cmds[0], InFrameCaptureBoundary, false);
m_InternalCmds.submittedcmds.append(m_InternalCmds.pendingcmds);
m_InternalCmds.pendingcmds.clear();
}
void WrappedID3D12Device::FlushLists(bool forceSync, ID3D12CommandQueue *queue)
{
if(!m_InternalCmds.submittedcmds.empty() || forceSync)
{
GPUSync(queue);
if(!m_InternalCmds.submittedcmds.empty())
m_InternalCmds.freecmds.append(m_InternalCmds.submittedcmds);
m_InternalCmds.submittedcmds.clear();
if(m_InternalCmds.pendingcmds.empty())
m_Alloc->Reset();
}
}
const DrawcallDescription *WrappedID3D12Device::GetDrawcall(uint32_t eventId)
{
if(eventId >= m_Drawcalls.size())
return NULL;
return m_Drawcalls[eventId];
}
bool WrappedID3D12Device::ProcessChunk(ReadSerialiser &ser, D3D12Chunk context)
{
switch(context)
{
case D3D12Chunk::Device_CreateCommandQueue:
return Serialise_CreateCommandQueue(ser, NULL, IID(), NULL);
case D3D12Chunk::Device_CreateCommandAllocator:
return Serialise_CreateCommandAllocator(ser, D3D12_COMMAND_LIST_TYPE_DIRECT, IID(), NULL);
case D3D12Chunk::Device_CreateCommandList:
return Serialise_CreateCommandList(ser, 0, D3D12_COMMAND_LIST_TYPE_DIRECT, NULL, NULL, IID(),
NULL);
case D3D12Chunk::Device_CreateGraphicsPipeline:
return Serialise_CreateGraphicsPipelineState(ser, NULL, IID(), NULL);
case D3D12Chunk::Device_CreateComputePipeline:
return Serialise_CreateComputePipelineState(ser, NULL, IID(), NULL);
case D3D12Chunk::Device_CreateDescriptorHeap:
return Serialise_CreateDescriptorHeap(ser, NULL, IID(), NULL);
case D3D12Chunk::Device_CreateRootSignature:
return Serialise_CreateRootSignature(ser, 0, NULL, 0, IID(), NULL);
case D3D12Chunk::Device_CreateCommandSignature:
return Serialise_CreateCommandSignature(ser, NULL, NULL, IID(), NULL);
case D3D12Chunk::Device_CreateHeap: return Serialise_CreateHeap(ser, NULL, IID(), NULL); break;
case D3D12Chunk::Device_CreateCommittedResource:
return Serialise_CreateCommittedResource(ser, NULL, D3D12_HEAP_FLAG_NONE, NULL,
D3D12_RESOURCE_STATE_COMMON, NULL, IID(), NULL);
case D3D12Chunk::Device_CreatePlacedResource:
return Serialise_CreatePlacedResource(ser, NULL, 0, NULL, D3D12_RESOURCE_STATE_COMMON, NULL,
IID(), NULL);
case D3D12Chunk::Device_CreateReservedResource:
return Serialise_CreateReservedResource(ser, NULL, D3D12_RESOURCE_STATE_COMMON, NULL, IID(),
NULL);
case D3D12Chunk::Device_CreateQueryHeap:
return Serialise_CreateQueryHeap(ser, NULL, IID(), NULL);
case D3D12Chunk::Device_CreateFence:
return Serialise_CreateFence(ser, 0, D3D12_FENCE_FLAG_NONE, IID(), NULL);
case D3D12Chunk::SetName: return Serialise_SetName(ser, 0x0, "");
case D3D12Chunk::SetShaderDebugPath: return Serialise_SetShaderDebugPath(ser, NULL, NULL);
case D3D12Chunk::CreateSwapBuffer:
return Serialise_WrapSwapchainBuffer(ser, NULL, DXGI_FORMAT_UNKNOWN, 0, NULL);
case D3D12Chunk::Device_CreatePipelineState:
return Serialise_CreatePipelineState(ser, NULL, IID(), NULL);
// these functions are serialised as-if they are a real heap.
case D3D12Chunk::Device_CreateHeapFromAddress:
case D3D12Chunk::Device_CreateHeapFromFileMapping:
return Serialise_CreateHeap(ser, NULL, IID(), NULL);
case D3D12Chunk::Device_OpenSharedHandle:
return Serialise_OpenSharedHandle(ser, NULL, IID(), NULL);
case D3D12Chunk::Device_CreateCommandList1:
return Serialise_CreateCommandList1(ser, 0, D3D12_COMMAND_LIST_TYPE_DIRECT,
D3D12_COMMAND_LIST_FLAG_NONE, IID(), NULL);
case D3D12Chunk::Device_CreateCommittedResource1:
return Serialise_CreateCommittedResource1(ser, NULL, D3D12_HEAP_FLAG_NONE, NULL,
D3D12_RESOURCE_STATE_COMMON, NULL, NULL, IID(), NULL);
case D3D12Chunk::Device_CreateHeap1: return Serialise_CreateHeap1(ser, NULL, NULL, IID(), NULL);
case D3D12Chunk::Device_ExternalDXGIResource:
return Serialise_OpenSharedHandle(ser, NULL, IID(), NULL);
// in order to get a warning if we miss a case, we explicitly handle the list/queue chunks here.
// If we actually encounter one it's an error (we should hit CaptureBegin first and switch to
// D3D12CommandData::ProcessChunk)
case D3D12Chunk::Device_CreateConstantBufferView:
case D3D12Chunk::Device_CreateShaderResourceView:
case D3D12Chunk::Device_CreateUnorderedAccessView:
case D3D12Chunk::Device_CreateRenderTargetView:
case D3D12Chunk::Device_CreateDepthStencilView:
case D3D12Chunk::Device_CreateSampler:
case D3D12Chunk::Device_CopyDescriptors:
case D3D12Chunk::Device_CopyDescriptorsSimple:
case D3D12Chunk::Queue_ExecuteCommandLists:
case D3D12Chunk::Queue_Signal:
case D3D12Chunk::Queue_Wait:
case D3D12Chunk::Queue_UpdateTileMappings:
case D3D12Chunk::Queue_CopyTileMappings:
case D3D12Chunk::Queue_BeginEvent:
case D3D12Chunk::Queue_SetMarker:
case D3D12Chunk::Queue_EndEvent:
case D3D12Chunk::List_Close:
case D3D12Chunk::List_Reset:
case D3D12Chunk::List_ResourceBarrier:
case D3D12Chunk::List_BeginQuery:
case D3D12Chunk::List_EndQuery:
case D3D12Chunk::List_ResolveQueryData:
case D3D12Chunk::List_SetPredication:
case D3D12Chunk::List_DrawIndexedInstanced:
case D3D12Chunk::List_DrawInstanced:
case D3D12Chunk::List_Dispatch:
case D3D12Chunk::List_ExecuteIndirect:
case D3D12Chunk::List_ExecuteBundle:
case D3D12Chunk::List_CopyBufferRegion:
case D3D12Chunk::List_CopyTextureRegion:
case D3D12Chunk::List_CopyResource:
case D3D12Chunk::List_ResolveSubresource:
case D3D12Chunk::List_ClearRenderTargetView:
case D3D12Chunk::List_ClearDepthStencilView:
case D3D12Chunk::List_ClearUnorderedAccessViewUint:
case D3D12Chunk::List_ClearUnorderedAccessViewFloat:
case D3D12Chunk::List_DiscardResource:
case D3D12Chunk::List_IASetPrimitiveTopology:
case D3D12Chunk::List_IASetIndexBuffer:
case D3D12Chunk::List_IASetVertexBuffers:
case D3D12Chunk::List_SOSetTargets:
case D3D12Chunk::List_RSSetViewports:
case D3D12Chunk::List_RSSetScissorRects:
case D3D12Chunk::List_SetPipelineState:
case D3D12Chunk::List_SetDescriptorHeaps:
case D3D12Chunk::List_OMSetRenderTargets:
case D3D12Chunk::List_OMSetStencilRef:
case D3D12Chunk::List_OMSetBlendFactor:
case D3D12Chunk::List_SetGraphicsRootDescriptorTable:
case D3D12Chunk::List_SetGraphicsRootSignature:
case D3D12Chunk::List_SetGraphicsRoot32BitConstant:
case D3D12Chunk::List_SetGraphicsRoot32BitConstants:
case D3D12Chunk::List_SetGraphicsRootConstantBufferView:
case D3D12Chunk::List_SetGraphicsRootShaderResourceView:
case D3D12Chunk::List_SetGraphicsRootUnorderedAccessView:
case D3D12Chunk::List_SetComputeRootDescriptorTable:
case D3D12Chunk::List_SetComputeRootSignature:
case D3D12Chunk::List_SetComputeRoot32BitConstant:
case D3D12Chunk::List_SetComputeRoot32BitConstants:
case D3D12Chunk::List_SetComputeRootConstantBufferView:
case D3D12Chunk::List_SetComputeRootShaderResourceView:
case D3D12Chunk::List_SetComputeRootUnorderedAccessView:
case D3D12Chunk::List_CopyTiles:
case D3D12Chunk::List_AtomicCopyBufferUINT:
case D3D12Chunk::List_AtomicCopyBufferUINT64:
case D3D12Chunk::List_OMSetDepthBounds:
case D3D12Chunk::List_ResolveSubresourceRegion:
case D3D12Chunk::List_SetSamplePositions:
case D3D12Chunk::List_SetViewInstanceMask:
case D3D12Chunk::List_WriteBufferImmediate:
case D3D12Chunk::List_BeginRenderPass:
case D3D12Chunk::List_EndRenderPass:
case D3D12Chunk::List_RSSetShadingRate:
case D3D12Chunk::List_RSSetShadingRateImage:
case D3D12Chunk::PushMarker:
case D3D12Chunk::PopMarker:
case D3D12Chunk::SetMarker:
case D3D12Chunk::Resource_Unmap:
case D3D12Chunk::Resource_WriteToSubresource:
case D3D12Chunk::List_IndirectSubCommand:
case D3D12Chunk::Swapchain_Present:
case D3D12Chunk::List_ClearState:
RDCERR("Unexpected chunk while processing initialisation: %s", ToStr(context).c_str());
return false;
// no explicit default so that we have compiler warnings if a chunk isn't explicitly handled.
case D3D12Chunk::Max: break;
}
{
SystemChunk system = (SystemChunk)context;
if(system == SystemChunk::DriverInit)
{
D3D12InitParams InitParams;
SERIALISE_ELEMENT(InitParams);
SERIALISE_CHECK_READ_ERRORS();
}
else if(system == SystemChunk::InitialContentsList)
{
GetResourceManager()->CreateInitialContents(ser);
if(initStateCurList)
{
CloseInitialStateList();
ExecuteLists(NULL, true);
FlushLists();
}
SERIALISE_CHECK_READ_ERRORS();
}
else if(system == SystemChunk::InitialContents)
{
return GetResourceManager()->Serialise_InitialState(ser, ResourceId(), NULL, NULL);
}
else if(system == SystemChunk::CaptureScope)
{
return Serialise_CaptureScope(ser);
}
else if(system < SystemChunk::FirstDriverChunk)
{
RDCERR("Unexpected system chunk in capture data: %u", system);
ser.SkipCurrentChunk();
SERIALISE_CHECK_READ_ERRORS();
}
else
{
RDCERR("Unexpected chunk %s", ToStr(context).c_str());
return false;
}
}
return true;
}
ResourceDescription &WrappedID3D12Device::GetResourceDesc(ResourceId id)
{
return GetReplay()->GetResourceDesc(id);
}
void WrappedID3D12Device::AddResource(ResourceId id, ResourceType type, const char *defaultNamePrefix)
{
ResourceDescription &descr = GetReplay()->GetResourceDesc(id);
uint64_t num;
memcpy(&num, &id, sizeof(uint64_t));
descr.name = defaultNamePrefix + (" " + ToStr(num));
descr.autogeneratedName = true;
descr.type = type;
AddResourceCurChunk(descr);
}
void WrappedID3D12Device::DerivedResource(ID3D12DeviceChild *parent, ResourceId child)
{
ResourceId parentId = GetResourceManager()->GetOriginalID(GetResID(parent));
DerivedResource(parentId, child);
}
void WrappedID3D12Device::DerivedResource(ResourceId parent, ResourceId child)
{
GetReplay()->GetResourceDesc(parent).derivedResources.push_back(child);
GetReplay()->GetResourceDesc(child).parentResources.push_back(parent);
}
void WrappedID3D12Device::AddResourceCurChunk(ResourceDescription &descr)
{
descr.initialisationChunks.push_back((uint32_t)m_StructuredFile->chunks.size() - 1);
}
void WrappedID3D12Device::AddResourceCurChunk(ResourceId id)
{
AddResourceCurChunk(GetReplay()->GetResourceDesc(id));
}
ReplayStatus WrappedID3D12Device::ReadLogInitialisation(RDCFile *rdc, bool storeStructuredBuffers)
{
int sectionIdx = rdc->SectionIndex(SectionType::FrameCapture);
if(sectionIdx < 0)
return ReplayStatus::FileCorrupted;
StreamReader *reader = rdc->ReadSection(sectionIdx);
if(IsStructuredExporting(m_State))
{
// when structured exporting don't do any timebase conversion
m_TimeBase = 0;
m_TimeFrequency = 1.0;
}
else
{
m_TimeBase = rdc->GetTimestampBase();
m_TimeFrequency = rdc->GetTimestampFrequency();
}
if(reader->IsErrored())
{
delete reader;
return ReplayStatus::FileIOFailed;
}
ReadSerialiser ser(reader, Ownership::Stream);
APIProps.DXILShaders = m_UsedDXIL = m_InitParams.usedDXIL;
ser.SetStringDatabase(&m_StringDB);
ser.SetUserData(GetResourceManager());
ser.ConfigureStructuredExport(&GetChunkName, storeStructuredBuffers, m_TimeBase, m_TimeFrequency);
m_StructuredFile = &ser.GetStructuredFile();
m_StoredStructuredData.version = m_StructuredFile->version = m_SectionVersion;
ser.SetVersion(m_SectionVersion);
int chunkIdx = 0;
struct chunkinfo
{
chunkinfo() : count(0), totalsize(0), total(0.0) {}
int count;
uint64_t totalsize;
double total;
};
std::map<D3D12Chunk, chunkinfo> chunkInfos;
SCOPED_TIMER("chunk initialisation");
uint64_t frameDataSize = 0;
for(;;)
{
PerformanceTimer timer;
uint64_t offsetStart = reader->GetOffset();
D3D12Chunk context = ser.ReadChunk<D3D12Chunk>();
chunkIdx++;
if(reader->IsErrored())
return ReplayStatus::APIDataCorrupted;
bool success = ProcessChunk(ser, context);
ser.EndChunk();
if(reader->IsErrored())
return ReplayStatus::APIDataCorrupted;
// if there wasn't a serialisation error, but the chunk didn't succeed, then it's an API replay
// failure.
if(!success)
return m_FailedReplayStatus;
uint64_t offsetEnd = reader->GetOffset();
RenderDoc::Inst().SetProgress(LoadProgress::FileInitialRead,
float(offsetEnd) / float(reader->GetSize()));
if((SystemChunk)context == SystemChunk::CaptureScope)
{
GetReplay()->WriteFrameRecord().frameInfo.fileOffset = offsetStart;
// read the remaining data into memory and pass to immediate context
frameDataSize = reader->GetSize() - reader->GetOffset();
if(IsStructuredExporting(m_State))
{
m_Queue = new WrappedID3D12CommandQueue(NULL, this, m_State);
m_Queues.push_back(m_Queue);
}
m_Queue->SetFrameReader(new StreamReader(reader, frameDataSize));
if(!IsStructuredExporting(m_State))
{
rdcarray<DebugMessage> savedDebugMessages;
// save any debug messages we built up
savedDebugMessages.swap(m_DebugMessages);
ApplyInitialContents();
// restore saved messages - which implicitly discards any generated while applying initial
// contents
savedDebugMessages.swap(m_DebugMessages);
}
ReplayStatus status = m_Queue->ReplayLog(m_State, 0, 0, false);
if(status != ReplayStatus::Succeeded)
return status;
}
chunkInfos[context].total += timer.GetMilliseconds();
chunkInfos[context].totalsize += offsetEnd - offsetStart;
chunkInfos[context].count++;
if((SystemChunk)context == SystemChunk::CaptureScope || reader->IsErrored() || reader->AtEnd())
break;
}
// steal the structured data for ourselves
m_StructuredFile->Swap(m_StoredStructuredData);
// and in future use this file.
m_StructuredFile = &m_StoredStructuredData;
if(!IsStructuredExporting(m_State))
{
GetReplay()->WriteFrameRecord().drawcallList = m_Queue->GetParentDrawcall().Bake();
m_Queue->GetParentDrawcall().children.clear();
SetupDrawcallPointers(m_Drawcalls, GetReplay()->WriteFrameRecord().drawcallList);
D3D12CommandData &cmd = *m_Queue->GetCommandData();
for(auto it = cmd.m_BakedCmdListInfo.begin(); it != cmd.m_BakedCmdListInfo.end(); it++)
{
for(size_t i = 0; i < it->second.crackedLists.size(); i++)
it->second.crackedLists[i]->Release();
it->second.crackedLists.clear();
}
for(auto it = cmd.m_CrackedAllocators.begin(); it != cmd.m_CrackedAllocators.end(); it++)
SAFE_RELEASE(it->second);
}
#if ENABLED(RDOC_DEVEL)
for(auto it = chunkInfos.begin(); it != chunkInfos.end(); ++it)
{
double dcount = double(it->second.count);
RDCDEBUG(
"% 5d chunks - Time: %9.3fms total/%9.3fms avg - Size: %8.3fMB total/%7.3fMB avg - %s (%u)",
it->second.count, it->second.total, it->second.total / dcount,
double(it->second.totalsize) / (1024.0 * 1024.0),
double(it->second.totalsize) / (dcount * 1024.0 * 1024.0),
GetChunkName((uint32_t)it->first).c_str(), uint32_t(it->first));
}
#endif
GetReplay()->WriteFrameRecord().frameInfo.uncompressedFileSize =
rdc->GetSectionProperties(sectionIdx).uncompressedSize;
GetReplay()->WriteFrameRecord().frameInfo.compressedFileSize =
rdc->GetSectionProperties(sectionIdx).compressedSize;
GetReplay()->WriteFrameRecord().frameInfo.persistentSize = frameDataSize;
GetReplay()->WriteFrameRecord().frameInfo.initDataSize =
chunkInfos[(D3D12Chunk)SystemChunk::InitialContents].totalsize;
RDCDEBUG("Allocating %llu persistant bytes of memory for the log.",
GetReplay()->WriteFrameRecord().frameInfo.persistentSize);
return ReplayStatus::Succeeded;
}
void WrappedID3D12Device::ReplayLog(uint32_t startEventID, uint32_t endEventID,
ReplayLogType replayType)
{
bool partial = true;
if(startEventID == 0 && (replayType == eReplay_WithoutDraw || replayType == eReplay_Full))
{
startEventID = 1;
partial = false;
m_GPUSyncCounter++;
// I'm not sure the reason for this, but the debug layer warns about being unable to resubmit
// command lists due to the 'previous queue fence' not being ready yet, even if no fences are
// signalled or waited. So instead we just signal a dummy fence each new 'frame'
for(size_t i = 0; i < m_Queues.size(); i++)
m_Queues[i]->Signal(m_QueueFences[i], m_GPUSyncCounter);
FlushLists(true);
// take this opportunity to reset command allocators to ensure we don't steadily leak over time.
if(m_DataUploadAlloc)
m_DataUploadAlloc->Reset();
for(ID3D12CommandAllocator *alloc : m_CommandAllocators)
alloc->Reset();
}
if(!partial)
{
{
D3D12MarkerRegion apply(GetQueue(), "!!!!RenderDoc Internal: ApplyInitialContents");
ApplyInitialContents();
}
ExecuteLists();
FlushLists(true);
}
m_State = CaptureState::ActiveReplaying;
D3D12MarkerRegion::Set(
GetQueue(), StringFormat::Fmt("!!!!RenderDoc Internal: RenderDoc Replay %d (%d): %u->%u",
(int)replayType, (int)partial, startEventID, endEventID));
if(!partial)
{
ID3D12GraphicsCommandList *beginList = GetNewList();
{
rdcwstr text = StringFormat::UTF82Wide(AMDRGPControl::GetBeginMarker());
UINT size = UINT(text.length() * sizeof(wchar_t));
beginList->SetMarker(0, text.c_str(), size);
}
beginList->Close();
ExecuteLists();
}
{
D3D12CommandData &cmd = *m_Queue->GetCommandData();
if(!partial)
{
cmd.m_Partial[D3D12CommandData::Primary].Reset();
cmd.m_Partial[D3D12CommandData::Secondary].Reset();
cmd.m_RenderState = D3D12RenderState();
cmd.m_RenderState.m_ResourceManager = GetResourceManager();
cmd.m_RenderState.m_DebugManager = m_Replay->GetDebugManager();
}
else
{
// Copy the state in case m_RenderState was modified externally for the partial replay.
cmd.m_BakedCmdListInfo[cmd.m_Partial[D3D12CommandData::Primary].partialParent].state =
cmd.m_RenderState;
}
// we'll need our own command list if we're replaying just a subsection
// of events within a single command list record - always if it's only
// one drawcall, or if start event ID is > 0 we assume the outside code
// has chosen a subsection that lies within a command list
if(partial)
{
ID3D12GraphicsCommandListX *list = cmd.m_OutsideCmdList = GetNewList();
cmd.m_RenderState.ApplyState(this, list);
}
ReplayStatus status = ReplayStatus::Succeeded;
if(replayType == eReplay_Full)
status = m_Queue->ReplayLog(m_State, startEventID, endEventID, partial);
else if(replayType == eReplay_WithoutDraw)
status = m_Queue->ReplayLog(m_State, startEventID, RDCMAX(1U, endEventID) - 1, partial);
else if(replayType == eReplay_OnlyDraw)
status = m_Queue->ReplayLog(m_State, endEventID, endEventID, partial);
else
RDCFATAL("Unexpected replay type");
RDCASSERTEQUAL(status, ReplayStatus::Succeeded);
if(cmd.m_OutsideCmdList != NULL)
{
ID3D12GraphicsCommandList *list = cmd.m_OutsideCmdList;
list->Close();
ExecuteLists();
cmd.m_OutsideCmdList = NULL;
}
cmd.m_RenderState =
cmd.m_BakedCmdListInfo[cmd.m_Partial[D3D12CommandData::Primary].partialParent].state;
#if ENABLED(SINGLE_FLUSH_VALIDATE)
FlushLists(true);
#endif
}
D3D12MarkerRegion::Set(GetQueue(), "!!!!RenderDoc Internal: Done replay");
// ensure all UAV writes have finished before subsequent work
ID3D12GraphicsCommandList *list = GetNewList();
{
rdcwstr text = StringFormat::UTF82Wide(AMDRGPControl::GetEndMarker());
UINT size = UINT(text.length() * sizeof(wchar_t));
list->SetMarker(0, text.c_str(), size);
}
D3D12_RESOURCE_BARRIER uavBarrier = {};
uavBarrier.Type = D3D12_RESOURCE_BARRIER_TYPE_UAV;
list->ResourceBarrier(1, &uavBarrier);
list->Close();
ExecuteLists();
}