From 0f851df6c2f85c7f64b7cb680f53d0c57e21ae97 Mon Sep 17 00:00:00 2001 From: baldurk Date: Tue, 11 Apr 2023 14:21:24 +0100 Subject: [PATCH] Batch prepare and allow limited buffering for vulkan initial states * We add a capture option that defines a soft limit we aim to keep under for memory overhead during capture, excess initial states after that will be stored temporarily on disk. --- qrenderdoc/Code/CaptureContext.cpp | 4 +- qrenderdoc/Code/Interface/QRDInterface.cpp | 2 + qrenderdoc/Windows/Dialogs/CaptureDialog.cpp | 2 + qrenderdoc/Windows/Dialogs/CaptureDialog.ui | 79 ++++++ renderdoc/api/app/renderdoc_app.h | 13 + renderdoc/api/replay/capture_options.h | 13 + renderdoc/api/replay/renderdoc_tostr.inl | 4 +- renderdoc/api/replay/replay_enums.h | 12 +- renderdoc/core/image_viewer.cpp | 2 +- renderdoc/core/resource_manager.h | 83 +++++- renderdoc/driver/d3d11/d3d11_device.cpp | 10 +- renderdoc/driver/d3d12/d3d12_counters.cpp | 2 +- renderdoc/driver/d3d12/d3d12_device.cpp | 8 +- renderdoc/driver/vulkan/vk_core.cpp | 34 ++- renderdoc/driver/vulkan/vk_core.h | 12 + renderdoc/driver/vulkan/vk_debug.h | 12 +- renderdoc/driver/vulkan/vk_initstate.cpp | 247 +++++++++++++----- renderdoc/driver/vulkan/vk_manager.cpp | 10 + renderdoc/driver/vulkan/vk_manager.h | 6 + renderdoc/driver/vulkan/vk_memory.cpp | 22 +- .../driver/vulkan/vk_msaa_buffer_conv.cpp | 84 +++--- renderdoc/driver/vulkan/vk_replay.cpp | 16 +- .../vulkan/wrappers/vk_device_funcs.cpp | 11 + .../driver/vulkan/wrappers/vk_wsi_funcs.cpp | 4 + renderdoc/replay/capture_options.cpp | 7 + renderdoc/replay/renderdoc_serialise.inl | 3 +- renderdoc/serialise/serialiser.cpp | 36 ++- renderdoc/serialise/serialiser.h | 10 +- renderdoc/serialise/streamio.cpp | 11 +- renderdoc/serialise/streamio.h | 11 + renderdoccmd/renderdoccmd.cpp | 4 + 31 files changed, 586 insertions(+), 188 deletions(-) diff --git a/qrenderdoc/Code/CaptureContext.cpp b/qrenderdoc/Code/CaptureContext.cpp index e88b89498..b6d2ab952 100644 --- a/qrenderdoc/Code/CaptureContext.cpp +++ b/qrenderdoc/Code/CaptureContext.cpp @@ -123,7 +123,7 @@ CaptureContext::CaptureContext(PersistantConfig &cfg) : m_Config(cfg) "a connection issue.") .arg(err.Message()); } - else if(err.code == ResultCode::ReplayDeviceLost) + else if(err.code == ResultCode::DeviceLost) { title = tr("Device Lost error"); text = tr("%1.\n\n" @@ -133,7 +133,7 @@ CaptureContext::CaptureContext(PersistantConfig &cfg) : m_Config(cfg) "API usage errors can cause this kind of problem.") .arg(err.Message()); } - else if(err.code == ResultCode::ReplayOutOfMemory) + else if(err.code == ResultCode::OutOfMemory) { title = tr("Out of memory"); text = diff --git a/qrenderdoc/Code/Interface/QRDInterface.cpp b/qrenderdoc/Code/Interface/QRDInterface.cpp index 63effb940..25196fa4b 100644 --- a/qrenderdoc/Code/Interface/QRDInterface.cpp +++ b/qrenderdoc/Code/Interface/QRDInterface.cpp @@ -106,6 +106,7 @@ CaptureSettings::operator QVariant() const opts[lit("refAllResources")] = options.refAllResources; opts[lit("captureAllCmdLists")] = options.captureAllCmdLists; opts[lit("debugOutputMute")] = options.debugOutputMute; + opts[lit("softMemoryLimit")] = options.softMemoryLimit; ret[lit("options")] = opts; ret[lit("queuedFrameCap")] = queuedFrameCap; @@ -149,6 +150,7 @@ CaptureSettings::CaptureSettings(const QVariant &v) options.refAllResources = opts[lit("refAllResources")].toBool(); options.captureAllCmdLists = opts[lit("captureAllCmdLists")].toBool(); options.debugOutputMute = opts[lit("debugOutputMute")].toBool(); + options.softMemoryLimit = opts[lit("softMemoryLimit")].toUInt(); if(data.contains(lit("queuedFrameCap"))) queuedFrameCap = data[lit("queuedFrameCap")].toUInt(); diff --git a/qrenderdoc/Windows/Dialogs/CaptureDialog.cpp b/qrenderdoc/Windows/Dialogs/CaptureDialog.cpp index 860861b07..c2569fa7d 100644 --- a/qrenderdoc/Windows/Dialogs/CaptureDialog.cpp +++ b/qrenderdoc/Windows/Dialogs/CaptureDialog.cpp @@ -928,6 +928,7 @@ void CaptureDialog::SetSettings(CaptureSettings settings) ui->DelayForDebugger->setValue(settings.options.delayForDebugger); ui->VerifyBufferAccess->setChecked(settings.options.verifyBufferAccess); ui->AutoStart->setChecked(settings.autoStart); + ui->SoftMemoryLimit->setValue(settings.options.softMemoryLimit); // force flush this state on_CaptureCallstacks_toggled(ui->CaptureCallstacks->isChecked()); @@ -975,6 +976,7 @@ CaptureSettings CaptureDialog::Settings() ret.options.captureAllCmdLists = ui->CaptureAllCmdLists->isChecked(); ret.options.delayForDebugger = (uint32_t)ui->DelayForDebugger->value(); ret.options.verifyBufferAccess = ui->VerifyBufferAccess->isChecked(); + ret.options.softMemoryLimit = (uint32_t)ui->SoftMemoryLimit->value(); if(ui->queueFrameCap->isChecked()) { diff --git a/qrenderdoc/Windows/Dialogs/CaptureDialog.ui b/qrenderdoc/Windows/Dialogs/CaptureDialog.ui index c2d991487..503cfd452 100644 --- a/qrenderdoc/Windows/Dialogs/CaptureDialog.ui +++ b/qrenderdoc/Windows/Dialogs/CaptureDialog.ui @@ -528,6 +528,85 @@ Verify Buffer Access + + + + + 0 + 0 + + + + QFrame::NoFrame + + + QFrame::Plain + + + + 4 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 40 + 0 + + + + Specifies a soft memory limit which some APIs may use to limit graphics memory use and use disk space instead for storing temporary data. + + + MB + + + 0 + + + 99999.000000000000000 + + + 0.000000000000000 + + + + + + + + 0 + 0 + + + + Specifies a soft memory limit which some APIs may use to limit graphics memory use and use disk space instead for storing temporary data. + + + Soft Mem Limit + + + + + diff --git a/renderdoc/api/app/renderdoc_app.h b/renderdoc/api/app/renderdoc_app.h index 3242f4e64..a4514c6b6 100644 --- a/renderdoc/api/app/renderdoc_app.h +++ b/renderdoc/api/app/renderdoc_app.h @@ -214,6 +214,19 @@ typedef enum RENDERDOC_CaptureOption { // necessary as directed by a RenderDoc developer. eRENDERDOC_Option_AllowUnsupportedVendorExtensions = 12, + // Define a soft memory limit which some APIs may aim to keep overhead under where + // possible. Anything above this limit will where possible be saved directly to disk during + // capture. + // This will cause increased disk space use (which may cause a capture to fail if disk space is + // exhausted) as well as slower capture times. + // + // Not all memory allocations may be deferred like this so it is not a guarantee of a memory + // limit. + // + // Units are in MBs, suggested values would range from 200MB to 1000MB. + // + // Default - 0 Megabytes + eRENDERDOC_Option_SoftMemoryLimit = 13, } RENDERDOC_CaptureOption; // Sets an option that controls how RenderDoc behaves on capture. diff --git a/renderdoc/api/replay/capture_options.h b/renderdoc/api/replay/capture_options.h index a07f944fc..7a9c4ed2b 100644 --- a/renderdoc/api/replay/capture_options.h +++ b/renderdoc/api/replay/capture_options.h @@ -209,6 +209,19 @@ Default - enabled ``False`` - API debugging is displayed as normal. )"); bool debugOutputMute; + + DOCUMENT(R"(Define a soft memory limit which some APIs may aim to keep overhead under where +possible. Anything above this limit will where possible be saved directly to disk during capture. +This will cause increased disk space use (which may cause a capture to fail if disk space is +exhausted) as well as slower capture times. + +Not all memory allocations may be deferred like this so it is not a guarantee of a memory limit. + +Units are in MBs, suggested values would range from 200MB to 1000MB. + +Default - 0 Megabytes +)"); + uint32_t softMemoryLimit; }; DECLARE_REFLECTION_STRUCT(CaptureOptions); diff --git a/renderdoc/api/replay/renderdoc_tostr.inl b/renderdoc/api/replay/renderdoc_tostr.inl index d99ccf0e1..16de30573 100644 --- a/renderdoc/api/replay/renderdoc_tostr.inl +++ b/renderdoc/api/replay/renderdoc_tostr.inl @@ -92,8 +92,8 @@ rdcstr DoStringise(const ResultCode &el) STRINGISE_ENUM_CLASS_NAMED(AndroidAPKVerifyFailed, "Failed to verify installed Android remote server"); STRINGISE_ENUM_CLASS_NAMED(RemoteServerConnectionLost, "Connection lost to remote server"); - STRINGISE_ENUM_CLASS_NAMED(ReplayOutOfMemory, "Encountered an out of memory error"); - STRINGISE_ENUM_CLASS_NAMED(ReplayDeviceLost, "Encountered a GPU device lost error"); + STRINGISE_ENUM_CLASS_NAMED(OutOfMemory, "Encountered an out of memory error"); + STRINGISE_ENUM_CLASS_NAMED(DeviceLost, "Encountered a GPU device lost error"); STRINGISE_ENUM_CLASS_NAMED(DataNotAvailable, "Data was requested through RenderDoc's API which is not available"); STRINGISE_ENUM_CLASS_NAMED(InvalidParameter, diff --git a/renderdoc/api/replay/replay_enums.h b/renderdoc/api/replay/replay_enums.h index a385ab1c8..567d5a593 100644 --- a/renderdoc/api/replay/replay_enums.h +++ b/renderdoc/api/replay/replay_enums.h @@ -3861,13 +3861,13 @@ a remote server. While replaying on a remote server, the connection was lost. -.. data:: ReplayOutOfMemory +.. data:: OutOfMemory - While replaying, an out of memory error was encountered. + An out of memory error was encountered. -.. data:: ReplayDeviceLost +.. data:: DeviceLost - While replaying a device lost fatal error was encountered. + A device lost fatal error was encountered. .. data:: DataNotAvailable @@ -3909,8 +3909,8 @@ enum class ResultCode : uint32_t AndroidAPKInstallFailed, AndroidAPKVerifyFailed, RemoteServerConnectionLost, - ReplayOutOfMemory, - ReplayDeviceLost, + OutOfMemory, + DeviceLost, DataNotAvailable, InvalidParameter, CompressionFailed, diff --git a/renderdoc/core/image_viewer.cpp b/renderdoc/core/image_viewer.cpp index 803c76251..2d346c2da 100644 --- a/renderdoc/core/image_viewer.cpp +++ b/renderdoc/core/image_viewer.cpp @@ -731,7 +731,7 @@ void ImageViewer::RefreshFile() if(!data) { - SET_ERROR_RESULT(m_Error, ResultCode::ReplayOutOfMemory, + SET_ERROR_RESULT(m_Error, ResultCode::OutOfMemory, "Allocation for %zu bytes failed for EXR data", datasize); return; } diff --git a/renderdoc/core/resource_manager.h b/renderdoc/core/resource_manager.h index 143ac40f7..e76110621 100644 --- a/renderdoc/core/resource_manager.h +++ b/renderdoc/core/resource_manager.h @@ -610,6 +610,7 @@ public: InitialContentData GetInitialContents(ResourceId id); void SetInitialContents(ResourceId id, InitialContentData contents); void SetInitialChunk(ResourceId id, Chunk *chunk); + void SetInitialFileStore(ResourceId id, const rdcstr &filename, uint64_t start, uint64_t end); // generate chunks for initial contents and insert. void InsertInitialContentsChunks(WriteSerialiser &ser); @@ -692,7 +693,9 @@ protected: { return true; } + virtual void Begin_PrepareInitialBatch() {} virtual bool Prepare_InitialState(WrappedResourceType res) = 0; + virtual void End_PrepareInitialBatch() {} virtual uint64_t GetSize_InitialState(ResourceId id, const InitialContentData &initial) = 0; virtual bool Serialise_InitialState(WriteSerialiser &ser, ResourceId id, RecordType *record, const InitialContentData *initialData) = 0; @@ -727,10 +730,12 @@ protected: // used during capture - holds resources marked as dirty, needing initial contents std::set m_DirtyResources; - struct InitialContentDataOrChunk + struct InitialContentStorage { Chunk *chunk = NULL; InitialContentData data; + rdcstr filename; + uint64_t fileStart = 0, fileEnd = 0; void Free(ResourceManager *mgr) { @@ -745,7 +750,7 @@ protected: }; // used during capture or replay - holds initial contents - std::unordered_map m_InitialContents; + std::unordered_map m_InitialContents; // used during capture or replay - map of resources currently alive with their real IDs, used in // capture and replay. @@ -930,9 +935,7 @@ void ResourceManager::MarkResourceFrameReferenced(ResourceId id, SkipOrPostponeOrPrepare_InitialState(id, refType); if(IsDirtyFrameRef(refType)) - { Prepare_InitialStateIfPostponed(id, true); - } } UpdateLastWriteTime(id, refType); @@ -1002,7 +1005,7 @@ void ResourceManager::SetInitialChunk(ResourceId id, Chunk *chunk RDCASSERT(id != ResourceId()); RDCASSERT(chunk->GetChunkType() == SystemChunk::InitialContents); - InitialContentDataOrChunk &data = m_InitialContents[id]; + InitialContentStorage &data = m_InitialContents[id]; if(data.chunk) data.chunk->Delete(); @@ -1010,6 +1013,23 @@ void ResourceManager::SetInitialChunk(ResourceId id, Chunk *chunk data.chunk = chunk; } +template +void ResourceManager::SetInitialFileStore(ResourceId id, const rdcstr &filename, + uint64_t start, uint64_t end) +{ + SCOPED_LOCK_OPTIONAL(m_Lock, m_Capturing); + + RDCASSERT(id != ResourceId()); + RDCASSERT(!filename.empty()); + RDCASSERT(end > start); + + InitialContentStorage &data = m_InitialContents[id]; + + data.filename = filename; + data.fileStart = start; + data.fileEnd = end; +} + template typename Configuration::InitialContentData ResourceManager::GetInitialContents( ResourceId id) @@ -1121,11 +1141,17 @@ void ResourceManager::Prepare_InitialStateIfPostponed(ResourceId return; if(midframe) + { RDCLOG("Preparing resource %s after it has been postponed.", ToStr(id).c_str()); + Begin_PrepareInitialBatch(); + } WrappedResourceType res = GetCurrentResource(id); Prepare_InitialState(res); + if(midframe) + End_PrepareInitialBatch(); + m_PostponedResourceIDs.erase(id); } @@ -1166,7 +1192,9 @@ void ResourceManager::SkipOrPostponeOrPrepare_InitialState(Resour WrappedResourceType res = GetCurrentResource(id); RDCDEBUG("Preparing resource %s after it has been skipped on refType of %s", ToStr(id).c_str(), ToStr(refType).c_str()); + Begin_PrepareInitialBatch(); Prepare_InitialState(res); + End_PrepareInitialBatch(); } } @@ -1349,7 +1377,7 @@ void ResourceManager::ApplyInitialContents() for(auto it = resources.begin(); it != resources.end(); ++it) { ResourceId id = *it; - const InitialContentDataOrChunk &data = m_InitialContents[id]; + const InitialContentStorage &data = m_InitialContents[id]; WrappedResourceType live = GetLiveResource(id); Apply_InitialState(live, data.data); } @@ -1446,6 +1474,8 @@ void ResourceManager::PrepareInitialContents() float num = float(m_DirtyResources.size()); float idx = 0.0f; + Begin_PrepareInitialBatch(); + for(auto it = m_DirtyResources.begin(); it != m_DirtyResources.end(); ++it) { ResourceId id = *it; @@ -1491,6 +1521,8 @@ void ResourceManager::PrepareInitialContents() Prepare_InitialState(res); } + End_PrepareInitialBatch(); + RDCLOG("Prepared %u dirty resources, postponed %u, skipped %u", prepared, postponed, skipped); } @@ -1508,6 +1540,32 @@ void ResourceManager::InsertInitialContentsChunks(WriteSerialiser float num = float(m_InitialContents.size()); float idx = 0.0f; + Begin_PrepareInitialBatch(); + + for(auto it = m_InitialContents.begin(); it != m_InitialContents.end(); ++it) + { + ResourceId id = it->first; + + if(m_FrameReferencedResources.find(id) == m_FrameReferencedResources.end() && + !RenderDoc::Inst().GetCaptureOptions().refAllResources) + { + continue; + } + + RecordType *record = GetResourceRecord(id); + + if(record == NULL) + continue; + + if(record->InternalResource) + continue; + + // Load postponed resource if needed. + Prepare_InitialStateIfPostponed(id, false); + } + + End_PrepareInitialBatch(); + for(auto it = m_InitialContents.begin(); it != m_InitialContents.end(); ++it) { ResourceId id = it->first; @@ -1547,9 +1605,6 @@ void ResourceManager::InsertInitialContentsChunks(WriteSerialiser RDCDEBUG("Serialising dirty Resource %s", ToStr(id).c_str()); #endif - // Load postponed resource if needed. - Prepare_InitialStateIfPostponed(id, false); - dirty++; if(!Need_InitialStateChunk(id, it->second.data)) @@ -1563,6 +1618,14 @@ void ResourceManager::InsertInitialContentsChunks(WriteSerialiser { it->second.chunk->Write(ser); } + else if(!it->second.filename.empty()) + { + FILE *f = FileIO::fopen(it->second.filename, FileIO::ReadBinary); + FileIO::fseek64(f, it->second.fileStart, SEEK_SET); + StreamReader reader(f, it->second.fileEnd - it->second.fileStart, Ownership::Stream); + + StreamTransfer(ser.GetWriter(), &reader, NULL); + } else { uint64_t size = GetSize_InitialState(id, it->second.data); @@ -1895,9 +1958,7 @@ void ResourceManager::ReleaseCurrentResource(ResourceId id) // We potentially need to prepare this resource on Active Capture, // if it was postponed, but is about to go away. if(IsActiveCapturing(m_State)) - { Prepare_InitialStateIfPostponed(id, true); - } m_CurrentResourceMap.erase(id); m_DirtyResources.erase(id); diff --git a/renderdoc/driver/d3d11/d3d11_device.cpp b/renderdoc/driver/d3d11/d3d11_device.cpp index f185fa70b..be37dfde3 100644 --- a/renderdoc/driver/d3d11/d3d11_device.cpp +++ b/renderdoc/driver/d3d11/d3d11_device.cpp @@ -1535,7 +1535,7 @@ RDResult WrappedID3D11Device::ReadLogInitialisation(RDCFile *rdc, bool storeStru return m_FatalError; if(m_pDevice && m_pDevice->GetDeviceRemovedReason() != S_OK) - RETURN_ERROR_RESULT(ResultCode::ReplayDeviceLost, "Device lost during load: %s", + RETURN_ERROR_RESULT(ResultCode::DeviceLost, "Device lost during load: %s", ToStr(m_pDevice->GetDeviceRemovedReason()).c_str()); return ResultCode::Succeeded; @@ -1587,7 +1587,7 @@ void WrappedID3D11Device::ReplayLog(uint32_t startEventID, uint32_t endEventID, D3D11MarkerRegion::Set("!!!!RenderDoc Internal: Done replay"); if(m_pDevice->GetDeviceRemovedReason() != S_OK) - SET_ERROR_RESULT(m_FatalError, ResultCode::ReplayDeviceLost, "Device lost during replay: %s", + SET_ERROR_RESULT(m_FatalError, ResultCode::DeviceLost, "Device lost during replay: %s", ToStr(m_pDevice->GetDeviceRemovedReason()).c_str()); } @@ -2594,8 +2594,8 @@ void WrappedID3D11Device::CheckHRESULT(HRESULT hr) if(hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET || hr == DXGI_ERROR_DEVICE_HUNG || hr == DXGI_ERROR_DRIVER_INTERNAL_ERROR) { - SET_ERROR_RESULT(m_FatalError, ResultCode::ReplayDeviceLost, - "Logging device lost fatal error for %s", ToStr(hr).c_str()); + SET_ERROR_RESULT(m_FatalError, ResultCode::DeviceLost, "Logging device lost fatal error for %s", + ToStr(hr).c_str()); } else if(hr == E_OUTOFMEMORY) { @@ -2605,7 +2605,7 @@ void WrappedID3D11Device::CheckHRESULT(HRESULT hr) } else { - SET_ERROR_RESULT(m_FatalError, ResultCode::ReplayOutOfMemory, + SET_ERROR_RESULT(m_FatalError, ResultCode::OutOfMemory, "Logging out of memory fatal error for %s", ToStr(hr).c_str()); } } diff --git a/renderdoc/driver/d3d12/d3d12_counters.cpp b/renderdoc/driver/d3d12/d3d12_counters.cpp index c8ca0b302..bbcefaea8 100644 --- a/renderdoc/driver/d3d12/d3d12_counters.cpp +++ b/renderdoc/driver/d3d12/d3d12_counters.cpp @@ -625,7 +625,7 @@ rdcarray D3D12Replay::FetchCounters(const rdcarray &c { RDResult err; SET_ERROR_RESULT( - err, ResultCode::ReplayDeviceLost, + err, ResultCode::DeviceLost, "D3D12 counters require Win10 developer mode enabled: Settings > Update & Security " "> For Developers > Developer Mode"); m_pDevice->ReportFatalError(err); diff --git a/renderdoc/driver/d3d12/d3d12_device.cpp b/renderdoc/driver/d3d12/d3d12_device.cpp index b6b38f876..4c6892aac 100644 --- a/renderdoc/driver/d3d12/d3d12_device.cpp +++ b/renderdoc/driver/d3d12/d3d12_device.cpp @@ -3338,8 +3338,8 @@ void WrappedID3D12Device::CheckHRESULT(HRESULT hr) if(hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET || hr == DXGI_ERROR_DEVICE_HUNG || hr == DXGI_ERROR_DRIVER_INTERNAL_ERROR) { - SET_ERROR_RESULT(m_FatalError, ResultCode::ReplayDeviceLost, - "Logging device lost fatal error for %s", ToStr(hr).c_str()); + SET_ERROR_RESULT(m_FatalError, ResultCode::DeviceLost, "Logging device lost fatal error for %s", + ToStr(hr).c_str()); } else if(hr == E_OUTOFMEMORY) { @@ -3350,7 +3350,7 @@ void WrappedID3D12Device::CheckHRESULT(HRESULT hr) else { RDCLOG("Logging out of memory fatal error for %s", ToStr(hr).c_str()); - m_FatalError = ResultCode::ReplayOutOfMemory; + m_FatalError = ResultCode::OutOfMemory; } } else @@ -4632,7 +4632,7 @@ RDResult WrappedID3D12Device::ReadLogInitialisation(RDCFile *rdc, bool storeStru return m_FatalError; if(m_pDevice && m_pDevice->GetDeviceRemovedReason() != S_OK) - RETURN_ERROR_RESULT(ResultCode::ReplayDeviceLost, "Device lost during load: %s", + RETURN_ERROR_RESULT(ResultCode::DeviceLost, "Device lost during load: %s", ToStr(m_pDevice->GetDeviceRemovedReason()).c_str()); return ResultCode::Succeeded; diff --git a/renderdoc/driver/vulkan/vk_core.cpp b/renderdoc/driver/vulkan/vk_core.cpp index b76b4566a..6be08dbd4 100644 --- a/renderdoc/driver/vulkan/vk_core.cpp +++ b/renderdoc/driver/vulkan/vk_core.cpp @@ -232,6 +232,11 @@ VkCommandBuffer WrappedVulkan::GetInitStateCmd() VkMarkerRegion::Begin("!!!!RenderDoc Internal: ApplyInitialContents batched list", initStateCurCmd); } + else + { + VkMarkerRegion::Begin("!!!!RenderDoc Internal: PrepareInitialContents batched list", + initStateCurCmd); + } } initStateCurBatch++; @@ -1863,6 +1868,8 @@ void WrappedVulkan::StartFrameCapture(DeviceOwnedWindow devWnd) if(!IsBackgroundCapturing(m_State)) return; + m_CaptureFailure = false; + RDCLOG("Starting capture"); if(m_Queue == VK_NULL_HANDLE && m_QueueFamilyIdx != ~0U) @@ -1930,11 +1937,8 @@ void WrappedVulkan::StartFrameCapture(DeviceOwnedWindow devWnd) CheckVkResult(vkr); } + m_PreparedNotSerialisedInitStates.clear(); GetResourceManager()->PrepareInitialContents(); - SubmitAndFlushImageStateBarriers(m_setupImageBarriers); - SubmitCmds(); - FlushQ(); - SubmitAndFlushImageStateBarriers(m_cleanupImageBarriers); { SCOPED_LOCK(m_CapDescriptorsLock); @@ -1979,6 +1983,14 @@ bool WrappedVulkan::EndFrameCapture(DeviceOwnedWindow devWnd) if(!IsActiveCapturing(m_State)) return true; + if(m_CaptureFailure) + { + m_LastCaptureFailed = Timing::GetUnixTimestamp(); + return DiscardFrameCapture(devWnd); + } + + m_CaptureFailure = false; + VkSwapchainKHR swap = VK_NULL_HANDLE; if(devWnd.windowHandle) @@ -2432,6 +2444,9 @@ bool WrappedVulkan::EndFrameCapture(DeviceOwnedWindow devWnd) GetResourceManager()->FreeInitialContents(); FreeAllMemory(MemoryScope::InitialContents); + for(rdcstr &fn : m_InitTempFiles) + FileIO::Delete(fn); + m_InitTempFiles.clear(); return true; } @@ -2441,6 +2456,8 @@ bool WrappedVulkan::DiscardFrameCapture(DeviceOwnedWindow devWnd) if(!IsActiveCapturing(m_State)) return true; + m_CaptureFailure = false; + RDCLOG("Discarding frame capture."); RenderDoc::Inst().FinishCaptureWriting(NULL, m_CapturedFrames.back().frameNumber); @@ -2486,6 +2503,9 @@ bool WrappedVulkan::DiscardFrameCapture(DeviceOwnedWindow devWnd) GetResourceManager()->FreeInitialContents(); FreeAllMemory(MemoryScope::InitialContents); + for(rdcstr &fn : m_InitTempFiles) + FileIO::Delete(fn); + m_InitTempFiles.clear(); return true; } @@ -4266,8 +4286,8 @@ void WrappedVulkan::CheckErrorVkResult(VkResult vkr) if(vkr == VK_ERROR_INITIALIZATION_FAILED || vkr == VK_ERROR_DEVICE_LOST || vkr == VK_ERROR_UNKNOWN) { - SET_ERROR_RESULT(m_FatalError, ResultCode::ReplayDeviceLost, - "Logging device lost fatal error for %s", ToStr(vkr).c_str()); + SET_ERROR_RESULT(m_FatalError, ResultCode::DeviceLost, "Logging device lost fatal error for %s", + ToStr(vkr).c_str()); m_FailedReplayResult = m_FatalError; } else if(vkr == VK_ERROR_OUT_OF_HOST_MEMORY || vkr == VK_ERROR_OUT_OF_DEVICE_MEMORY) @@ -4278,7 +4298,7 @@ void WrappedVulkan::CheckErrorVkResult(VkResult vkr) } else { - SET_ERROR_RESULT(m_FatalError, ResultCode::ReplayOutOfMemory, + SET_ERROR_RESULT(m_FatalError, ResultCode::OutOfMemory, "Logging out of memory fatal error for %s", ToStr(vkr).c_str()); m_FailedReplayResult = m_FatalError; } diff --git a/renderdoc/driver/vulkan/vk_core.h b/renderdoc/driver/vulkan/vk_core.h index 419a67c26..a3b829b5f 100644 --- a/renderdoc/driver/vulkan/vk_core.h +++ b/renderdoc/driver/vulkan/vk_core.h @@ -312,6 +312,9 @@ private: rdcarray GetDebugMessages(); void AddDebugMessage(DebugMessage msg); + bool m_CaptureFailure = false; + uint64_t m_LastCaptureFailed = 0; + RDResult m_LastCaptureError = ResultCode::Succeeded; int m_OOMHandler = 0; RDResult m_FatalError = ResultCode::Succeeded; CaptureState m_State; @@ -574,6 +577,12 @@ private: static const int initialStateMaxBatch = 100; int initStateCurBatch = 0; + + bool m_PrepareInitStateBatching = false; + // list of resources which have been prepared but haven't been serialised + rdcarray m_PreparedNotSerialisedInitStates; + + rdcarray m_InitTempFiles; VkCommandBuffer initStateCurCmd = VK_NULL_HANDLE; rdcarray> m_PendingCleanups; @@ -590,6 +599,7 @@ private: VkDeviceSize m_MemoryBlockSize[arraydim()] = {}; void FreeAllMemory(MemoryScope scope); + uint64_t CurMemoryUsage(MemoryScope scope); void ResetMemoryBlocks(MemoryScope scope); void FreeMemoryAllocation(MemoryAllocation alloc); @@ -1083,6 +1093,8 @@ public: CaptureState GetState() { return m_State; } VulkanReplay *GetReplay() { return m_Replay; } // replay interface + void Begin_PrepareInitialBatch(); + void End_PrepareInitialBatch(); bool Prepare_InitialState(WrappedVkRes *res); uint64_t GetSize_InitialState(ResourceId id, const VkInitialContents &initial); template diff --git a/renderdoc/driver/vulkan/vk_debug.h b/renderdoc/driver/vulkan/vk_debug.h index bde1bd11b..5a0952700 100644 --- a/renderdoc/driver/vulkan/vk_debug.h +++ b/renderdoc/driver/vulkan/vk_debug.h @@ -63,9 +63,9 @@ public: void GetBufferData(ResourceId buff, uint64_t offset, uint64_t len, bytebuf &ret); - void CopyTex2DMSToBuffer(VkBuffer destBuffer, VkImage srcMS, VkExtent3D extent, - uint32_t baseSlice, uint32_t numSlices, uint32_t baseSample, - uint32_t numSamples, VkFormat fmt); + void CopyTex2DMSToBuffer(VkCommandBuffer cmd, VkBuffer destBuffer, VkImage srcMS, + VkExtent3D extent, uint32_t baseSlice, uint32_t numSlices, + uint32_t baseSample, uint32_t numSamples, VkFormat fmt); void CopyBufferToTex2DMS(VkCommandBuffer cmd, VkImage destMS, VkBuffer srcBuffer, VkExtent3D extent, uint32_t numSlices, uint32_t numSamples, VkFormat fmt); @@ -163,9 +163,9 @@ private: VkPipeline TexPipeline = VK_NULL_HANDLE; } m_Custom; - void CopyDepthTex2DMSToBuffer(VkBuffer destBuffer, VkImage srcMS, VkExtent3D extent, - uint32_t baseSlice, uint32_t numSlices, uint32_t baseSample, - uint32_t numSamples, VkFormat fmt); + void CopyDepthTex2DMSToBuffer(VkCommandBuffer cmd, VkBuffer destBuffer, VkImage srcMS, + VkExtent3D extent, uint32_t baseSlice, uint32_t numSlices, + uint32_t baseSample, uint32_t numSamples, VkFormat fmt); void CopyDepthBufferToTex2DMS(VkCommandBuffer cmd, VkImage destMS, VkBuffer srcBuffer, VkExtent3D extent, uint32_t numSlices, uint32_t numSamples, diff --git a/renderdoc/driver/vulkan/vk_initstate.cpp b/renderdoc/driver/vulkan/vk_initstate.cpp index d0cdf691c..6be6df637 100644 --- a/renderdoc/driver/vulkan/vk_initstate.cpp +++ b/renderdoc/driver/vulkan/vk_initstate.cpp @@ -23,6 +23,7 @@ ******************************************************************************/ #include "core/settings.h" +#include "strings/string_utils.h" #include "vk_core.h" #include "vk_debug.h" @@ -45,12 +46,107 @@ void DoSerialise(SerialiserType &ser, AspectSparseTable &el) SERIALISE_MEMBER(table); } +void WrappedVulkan::Begin_PrepareInitialBatch() +{ + m_PrepareInitStateBatching = true; +} + +void WrappedVulkan::End_PrepareInitialBatch() +{ + CloseInitStateCmd(); + SubmitAndFlushImageStateBarriers(m_setupImageBarriers); + SubmitCmds(); + FlushQ(); + SubmitAndFlushImageStateBarriers(m_cleanupImageBarriers); + m_PrepareInitStateBatching = false; +} + bool WrappedVulkan::Prepare_InitialState(WrappedVkRes *res) { ResourceId id = GetResourceManager()->GetID(res); + RDCASSERT(m_PrepareInitStateBatching); + VkResourceType type = IdentifyTypeByPtr(res); + uint64_t estimatedSize = 0; + if(type == eResDeviceMemory) + { + VkResourceRecord *record = GetResourceManager()->GetResourceRecord(id); + if(record) + estimatedSize = record->Length; + } + else if(type == eResImage) + { + WrappedVkImage *im = (WrappedVkImage *)res; + const ResourceInfo &resInfo = *im->record->resInfo; + const ImageInfo &imageInfo = resInfo.imageInfo; + + estimatedSize = GetByteSize(imageInfo.extent.width, imageInfo.extent.height, + imageInfo.extent.depth, imageInfo.format, 0); + if(imageInfo.sampleCount > 1) + estimatedSize *= imageInfo.sampleCount; + if(imageInfo.layerCount > 1) + estimatedSize *= imageInfo.layerCount; + // conservative estimate of full mip chain impact + if(imageInfo.levelCount > 1) + estimatedSize *= 2; + } + + uint32_t softMemoryLimit = RenderDoc::Inst().GetCaptureOptions().softMemoryLimit; + if(softMemoryLimit > 0 && !m_PreparedNotSerialisedInitStates.empty() && + CurMemoryUsage(MemoryScope::InitialContents) + estimatedSize > softMemoryLimit * 1024 * 1024ULL) + { + CloseInitStateCmd(); + SubmitAndFlushImageStateBarriers(m_setupImageBarriers); + SubmitCmds(); + FlushQ(); + SubmitAndFlushImageStateBarriers(m_cleanupImageBarriers); + + RDCLOG("Flushing batch of initial states to disk with %llu bytes allocated", + CurMemoryUsage(MemoryScope::InitialContents)); + rdcstr tempFile = StringFormat::Fmt( + "%s/rdoc_%llu_%llu.bin", get_dirname(RenderDoc::Inst().GetCaptureFileTemplate()).c_str(), + Timing::GetTick(), Threading::GetCurrentID()); + FileIO::CreateParentDirectory(tempFile); + m_InitTempFiles.push_back(tempFile); + WriteSerialiser ser( + new StreamWriter(FileIO::fopen(tempFile, FileIO::WriteBinary), Ownership::Stream), + Ownership::Stream); + + for(ResourceId flushId : m_PreparedNotSerialisedInitStates) + { + VkInitialContents initData = GetResourceManager()->GetInitialContents(flushId); + + GetResourceManager()->SetInitialContents(flushId, VkInitialContents()); + + uint64_t start = ser.GetWriter()->GetOffset(); + { + uint64_t size = GetSize_InitialState(id, initData); + + SCOPED_SERIALISE_CHUNK(SystemChunk::InitialContents, size); + + // record is not needed on vulkan + Serialise_InitialState(ser, flushId, NULL, &initData); + } + uint64_t end = ser.GetWriter()->GetOffset(); + + if(ser.IsErrored()) + break; + + GetResourceManager()->SetInitialFileStore(flushId, tempFile, start, end); + } + + m_PreparedNotSerialisedInitStates.clear(); + + if(ser.IsErrored()) + { + m_CaptureFailure = true; + m_LastCaptureError = ser.GetError(); + return false; + } + } + if(type == eResDescriptorSet) { VkResourceRecord *record = GetResourceManager()->GetResourceRecord(id); @@ -115,8 +211,7 @@ bool WrappedVulkan::Prepare_InitialState(WrappedVkRes *res) } VkDevice d = GetDev(); - // INITSTATEBATCH - VkCommandBuffer cmd = GetNextCmd(); + VkCommandBuffer cmd = GetInitStateCmd(); // must ensure offset remains valid. Must be multiple of block size, or 4, depending on format VkDeviceSize bufAlignment = 4; @@ -211,31 +306,26 @@ bool WrappedVulkan::Prepare_InitialState(WrappedVkRes *res) } // since this happens during capture, we don't want to start serialising extra buffer creates, - // so we manually create & then just wrap. + // leave this buffer as unwrapped VkBuffer dstBuf; vkr = ObjDisp(d)->CreateBuffer(Unwrap(d), &bufInfo, NULL, &dstBuf); CheckVkResult(vkr); - GetResourceManager()->WrapResource(Unwrap(d), dstBuf); - - MemoryAllocation readbackmem = - AllocateMemoryForResource(dstBuf, MemoryScope::InitialContents, MemoryType::Readback); + VkMemoryRequirements dstBufMrq = {}; + ObjDisp(d)->GetBufferMemoryRequirements(Unwrap(d), dstBuf, &dstBufMrq); + MemoryAllocation readbackmem = AllocateMemoryForResource( + true, dstBufMrq, MemoryScope::InitialContents, MemoryType::Readback); if(readbackmem.mem == VK_NULL_HANDLE) { - RDCERR("Couldn't allocate readback memory"); + SET_ERROR_RESULT(m_LastCaptureError, ResultCode::OutOfMemory, + "Couldn't allocate readback memory"); + m_CaptureFailure = true; return false; } - vkr = ObjDisp(d)->BindBufferMemory(Unwrap(d), Unwrap(dstBuf), Unwrap(readbackmem.mem), - readbackmem.offs); - CheckVkResult(vkr); - - VkCommandBufferBeginInfo beginInfo = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, NULL, - VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT}; - - vkr = ObjDisp(d)->BeginCommandBuffer(Unwrap(cmd), &beginInfo); + vkr = ObjDisp(d)->BindBufferMemory(Unwrap(d), dstBuf, Unwrap(readbackmem.mem), readbackmem.offs); CheckVkResult(vkr); VkImageAspectFlags aspectFlags = FormatImageAspects(imageInfo.format); @@ -252,18 +342,10 @@ bool WrappedVulkan::Prepare_InitialState(WrappedVkRes *res) m_setupImageBarriers.Merge(setupBarriers); if(wasms) { - vkr = ObjDisp(d)->EndCommandBuffer(Unwrap(cmd)); - CheckVkResult(vkr); - - GetDebugManager()->CopyTex2DMSToBuffer(Unwrap(dstBuf), realim, imageInfo.extent, 0, + GetDebugManager()->CopyTex2DMSToBuffer(cmd, dstBuf, realim, imageInfo.extent, 0, imageInfo.layerCount, 0, imageInfo.sampleCount, imageInfo.format); - cmd = GetNextCmd(); - - vkr = ObjDisp(d)->BeginCommandBuffer(Unwrap(cmd), &beginInfo); - CheckVkResult(vkr); - VkBufferMemoryBarrier bufBarrier = { VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER, NULL, @@ -271,7 +353,7 @@ bool WrappedVulkan::Prepare_InitialState(WrappedVkRes *res) VK_ACCESS_HOST_READ_BIT, VK_QUEUE_FAMILY_IGNORED, VK_QUEUE_FAMILY_IGNORED, - Unwrap(dstBuf), + dstBuf, 0, bufInfo.size, }; @@ -321,9 +403,8 @@ bool WrappedVulkan::Prepare_InitialState(WrappedVkRes *res) bufOffset += GetPlaneByteSize(imageInfo.extent.width, imageInfo.extent.height, imageInfo.extent.depth, sizeFormat, m, i); - ObjDisp(d)->CmdCopyImageToBuffer(Unwrap(cmd), realim, - VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, Unwrap(dstBuf), - 1, ®ion); + ObjDisp(d)->CmdCopyImageToBuffer( + Unwrap(cmd), realim, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, dstBuf, 1, ®ion); } } else @@ -340,7 +421,7 @@ bool WrappedVulkan::Prepare_InitialState(WrappedVkRes *res) imageInfo.extent.depth, sizeFormat, m); ObjDisp(d)->CmdCopyImageToBuffer( - Unwrap(cmd), realim, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, Unwrap(dstBuf), 1, ®ion); + Unwrap(cmd), realim, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, dstBuf, 1, ®ion); if(aspectFlags == (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) { @@ -353,9 +434,8 @@ bool WrappedVulkan::Prepare_InitialState(WrappedVkRes *res) bufOffset += GetByteSize(imageInfo.extent.width, imageInfo.extent.height, imageInfo.extent.depth, VK_FORMAT_S8_UINT, m); - ObjDisp(d)->CmdCopyImageToBuffer(Unwrap(cmd), realim, - VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, Unwrap(dstBuf), - 1, ®ion); + ObjDisp(d)->CmdCopyImageToBuffer( + Unwrap(cmd), realim, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, dstBuf, 1, ®ion); } } @@ -372,16 +452,16 @@ bool WrappedVulkan::Prepare_InitialState(WrappedVkRes *res) InlineCleanupImageBarriers(cmd, cleanupBarriers); m_cleanupImageBarriers.Merge(cleanupBarriers); - vkr = ObjDisp(d)->EndCommandBuffer(Unwrap(cmd)); - CheckVkResult(vkr); + if(Vulkan_Debug_SingleSubmitFlushing()) + { + CloseInitStateCmd(); + SubmitAndFlushImageStateBarriers(m_setupImageBarriers); + SubmitCmds(); + FlushQ(); + SubmitAndFlushImageStateBarriers(m_cleanupImageBarriers); + } - SubmitAndFlushImageStateBarriers(m_setupImageBarriers); - SubmitCmds(); - FlushQ(); - SubmitAndFlushImageStateBarriers(m_cleanupImageBarriers); - - ObjDisp(d)->DestroyBuffer(Unwrap(d), Unwrap(dstBuf), NULL); - GetResourceManager()->ReleaseWrappedResource(dstBuf); + AddPendingObjectCleanup([d, dstBuf]() { ObjDisp(d)->DestroyBuffer(Unwrap(d), dstBuf, NULL); }); VkInitialContents initialContents(type, readbackmem); @@ -392,6 +472,7 @@ bool WrappedVulkan::Prepare_InitialState(WrappedVkRes *res) } GetResourceManager()->SetInitialContents(id, initialContents); + m_PreparedNotSerialisedInitStates.push_back(id); return true; } @@ -410,8 +491,7 @@ bool WrappedVulkan::Prepare_InitialState(WrappedVkRes *res) VkResult vkr = VK_SUCCESS; VkDevice d = GetDev(); - // INITSTATEBATCH - VkCommandBuffer cmd = GetNextCmd(); + VkCommandBuffer cmd = GetInitStateCmd(); VkDeviceMemory datamem = ToUnwrappedHandle(res); VkDeviceSize datasize = record->Length; @@ -443,51 +523,45 @@ bool WrappedVulkan::Prepare_InitialState(WrappedVkRes *res) bufInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; // since this happens during capture, we don't want to start serialising extra buffer creates, - // so we manually create & then just wrap. + // leave this buffer as unwrapped VkBuffer dstBuf; bufInfo.size = datasize; vkr = ObjDisp(d)->CreateBuffer(Unwrap(d), &bufInfo, NULL, &dstBuf); CheckVkResult(vkr); - GetResourceManager()->WrapResource(Unwrap(d), dstBuf); - - MemoryAllocation readbackmem = - AllocateMemoryForResource(dstBuf, MemoryScope::InitialContents, MemoryType::Readback); + VkMemoryRequirements dstBufMrq = {}; + ObjDisp(d)->GetBufferMemoryRequirements(Unwrap(d), dstBuf, &dstBufMrq); + MemoryAllocation readbackmem = AllocateMemoryForResource( + true, dstBufMrq, MemoryScope::InitialContents, MemoryType::Readback); if(readbackmem.mem == VK_NULL_HANDLE) { - RDCERR("Couldn't allocate readback memory"); + SET_ERROR_RESULT(m_LastCaptureError, ResultCode::OutOfMemory, + "Couldn't allocate readback memory"); + m_CaptureFailure = true; return false; } CheckVkResult(vkr); - vkr = ObjDisp(d)->BindBufferMemory(Unwrap(d), Unwrap(dstBuf), Unwrap(readbackmem.mem), - readbackmem.offs); - CheckVkResult(vkr); - - VkCommandBufferBeginInfo beginInfo = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, NULL, - VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT}; - - vkr = ObjDisp(d)->BeginCommandBuffer(Unwrap(cmd), &beginInfo); + vkr = ObjDisp(d)->BindBufferMemory(Unwrap(d), dstBuf, Unwrap(readbackmem.mem), readbackmem.offs); CheckVkResult(vkr); VkBufferCopy region = {0, 0, datasize}; - ObjDisp(d)->CmdCopyBuffer(Unwrap(cmd), Unwrap(record->memMapState->wholeMemBuf), Unwrap(dstBuf), - 1, ®ion); + ObjDisp(d)->CmdCopyBuffer(Unwrap(cmd), Unwrap(record->memMapState->wholeMemBuf), dstBuf, 1, + ®ion); - vkr = ObjDisp(d)->EndCommandBuffer(Unwrap(cmd)); - CheckVkResult(vkr); + AddPendingObjectCleanup([d, dstBuf]() { ObjDisp(d)->DestroyBuffer(Unwrap(d), dstBuf, NULL); }); - // INITSTATEBATCH - SubmitCmds(); - FlushQ(); - - ObjDisp(d)->DestroyBuffer(Unwrap(d), Unwrap(dstBuf), NULL); - GetResourceManager()->ReleaseWrappedResource(dstBuf); + if(Vulkan_Debug_SingleSubmitFlushing()) + { + SubmitCmds(); + FlushQ(); + } GetResourceManager()->SetInitialContents(id, VkInitialContents(type, readbackmem)); + m_PreparedNotSerialisedInitStates.push_back(id); return true; } @@ -496,6 +570,8 @@ bool WrappedVulkan::Prepare_InitialState(WrappedVkRes *res) RDCERR("Unhandled resource type %d", type); } + m_CaptureFailure = true; + SET_ERROR_RESULT(m_LastCaptureError, ResultCode::InternalError, "Unknown resource encountered"); return false; } @@ -969,6 +1045,41 @@ bool WrappedVulkan::Serialise_InitialState(SerialiserType &ser, ResourceId id, V AddResourceCurChunk(id); } + // we will never have a case where we prepare some resources, serialise one, and then don't + // serialise all the rest of those resources before any further prepares. Whenever we see a + // serialise we can then reset the memory for re-use in any prepares that do then come later. + // + // The expected order is: + // 1. Prepare all non-postponed resources. + // As part of this in Prepare_initialState we might serialise all pending resources if we + // bump into the memory limit. This means memory can be re-used so complies with our + // stipulations above + // 2. Prepare postponed resources last minute + // These are individually synchronised to the GPU (not batched) to ensure we have the right + // data, but otherwise are treated as above - they will be left pending unless flushed due to + // the memory limit and if they are flushed they work exactly the same way + // 3. At the end of the frame, all postponed resources are prepared in a batch + // 4. Finally any resources that must be serialised are. + // + // Note that with no memory limit, all prepares will happen in steps 1-3 (in two large batches 1 + // and 3 and a batch-per-resource in 2) before any serialises in 4, so this reset is effectively + // redundant at that point. + // + // Note also that with a memory limit resources may overlap between these cases - the only + // Serialise calls happen when the memory limit is hit and we flush everything pending, but it is + // likely that some resources will be prepared in step 1, and not be flushed and so still be + // pending through some or all of the last-minute prepares in step 2 and into step 3. + // This is still fine though as Serialise is not called in between there for other resources. + + if(IsCaptureMode(m_State)) + { + ResetMemoryBlocks(MemoryScope::InitialContents); + + // if got here through 'natural' serialises, this array will not be cleared otherwise so we + // clear it here just for sanity. It should not be used otherwise though + m_PreparedNotSerialisedInitStates.clear(); + } + if(type == eResDescriptorSet) { DescriptorSetSlot *Bindings = NULL; diff --git a/renderdoc/driver/vulkan/vk_manager.cpp b/renderdoc/driver/vulkan/vk_manager.cpp index 78202c05e..32f6673a7 100644 --- a/renderdoc/driver/vulkan/vk_manager.cpp +++ b/renderdoc/driver/vulkan/vk_manager.cpp @@ -996,6 +996,16 @@ MemRefs *VulkanResourceManager::FindMemRefs(ResourceId mem) return NULL; } +void VulkanResourceManager::Begin_PrepareInitialBatch() +{ + return m_Core->Begin_PrepareInitialBatch(); +} + +void VulkanResourceManager::End_PrepareInitialBatch() +{ + return m_Core->End_PrepareInitialBatch(); +} + bool VulkanResourceManager::Prepare_InitialState(WrappedVkRes *res) { return m_Core->Prepare_InitialState(res); diff --git a/renderdoc/driver/vulkan/vk_manager.h b/renderdoc/driver/vulkan/vk_manager.h index 8a9774272..7d8b96475 100644 --- a/renderdoc/driver/vulkan/vk_manager.h +++ b/renderdoc/driver/vulkan/vk_manager.h @@ -315,7 +315,11 @@ public: void PreFreeMemory(ResourceId id) { if(IsActiveCapturing(m_State)) + { + ResourceManager::Begin_PrepareInitialBatch(); ResourceManager::Prepare_InitialStateIfPostponed(id, true); + ResourceManager::End_PrepareInitialBatch(); + } } template @@ -452,6 +456,8 @@ private: bool ResourceTypeRelease(WrappedVkRes *res); bool Prepare_InitialState(WrappedVkRes *res); + void Begin_PrepareInitialBatch(); + void End_PrepareInitialBatch(); uint64_t GetSize_InitialState(ResourceId id, const VkInitialContents &initial); bool Serialise_InitialState(WriteSerialiser &ser, ResourceId id, VkResourceRecord *record, const VkInitialContents *initial); diff --git a/renderdoc/driver/vulkan/vk_memory.cpp b/renderdoc/driver/vulkan/vk_memory.cpp index 95ca16661..0bb764d5e 100644 --- a/renderdoc/driver/vulkan/vk_memory.cpp +++ b/renderdoc/driver/vulkan/vk_memory.cpp @@ -280,6 +280,10 @@ MemoryAllocation WrappedVulkan::AllocateMemoryForResource(bool buffer, VkMemoryR break; } + uint64_t initStateLimitMB = RenderDoc::Inst().GetCaptureOptions().softMemoryLimit; + if(initStateLimitMB > 0) + allocSize = RDCMAX(initStateLimitMB, allocSize); + uint32_t memoryTypeIndex = 0; // Upload heaps are sometimes limited in size. To prevent OOM issues, deselect any memory types @@ -323,8 +327,11 @@ MemoryAllocation WrappedVulkan::AllocateMemoryForResource(bool buffer, VkMemoryR { // if we get an over-sized allocation, first try to immediately jump to the largest block // size. - allocSize = 256; - info.allocationSize = allocSize * 1024 * 1024; + if(initStateLimitMB == 0) + { + allocSize = 256; + info.allocationSize = allocSize * 1024 * 1024; + } // if it's still over-sized, just allocate precisely enough and give it a dedicated allocation if(ret.size > info.allocationSize) @@ -401,6 +408,17 @@ MemoryAllocation WrappedVulkan::AllocateMemoryForResource(VkBuffer buf, MemorySc return AllocateMemoryForResource(true, mrq, scope, type); } +uint64_t WrappedVulkan::CurMemoryUsage(MemoryScope scope) +{ + rdcarray &allocList = m_MemoryBlocks[(size_t)scope]; + + uint64_t ret = 0; + for(MemoryAllocation &alloc : allocList) + ret += alloc.offs; + + return ret; +} + void WrappedVulkan::FreeAllMemory(MemoryScope scope) { rdcarray &allocList = m_MemoryBlocks[(size_t)scope]; diff --git a/renderdoc/driver/vulkan/vk_msaa_buffer_conv.cpp b/renderdoc/driver/vulkan/vk_msaa_buffer_conv.cpp index fd4dd9abb..4345206e5 100644 --- a/renderdoc/driver/vulkan/vk_msaa_buffer_conv.cpp +++ b/renderdoc/driver/vulkan/vk_msaa_buffer_conv.cpp @@ -30,13 +30,14 @@ #include "data/glsl/glsl_globals.h" #include "data/glsl/glsl_ubos_cpp.h" -void VulkanDebugManager::CopyTex2DMSToBuffer(VkBuffer destBuffer, VkImage srcMS, VkExtent3D extent, - uint32_t baseSlice, uint32_t numSlices, - uint32_t baseSample, uint32_t numSamples, VkFormat fmt) +void VulkanDebugManager::CopyTex2DMSToBuffer(VkCommandBuffer cmd, VkBuffer destBuffer, + VkImage srcMS, VkExtent3D extent, uint32_t baseSlice, + uint32_t numSlices, uint32_t baseSample, + uint32_t numSamples, VkFormat fmt) { if(IsDepthOrStencilFormat(fmt)) { - CopyDepthTex2DMSToBuffer(destBuffer, srcMS, extent, baseSlice, numSlices, baseSample, + CopyDepthTex2DMSToBuffer(cmd, destBuffer, srcMS, extent, baseSlice, numSlices, baseSample, numSamples, fmt); return; } @@ -87,16 +88,22 @@ void VulkanDebugManager::CopyTex2DMSToBuffer(VkBuffer destBuffer, VkImage srcMS, CheckVkResult(vkr); NameUnwrappedVulkanObject(srcView, "MS -> Buffer srcView"); - VkCommandBuffer cmd = VK_NULL_HANDLE; VkCommandBufferBeginInfo beginInfo = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, NULL, VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT}; - cmd = m_pDriver->GetNextCmd(); + + bool endCommand = false; + + if(cmd == VK_NULL_HANDLE) + { + cmd = m_pDriver->GetNextCmd(); + if(cmd != VK_NULL_HANDLE) + ObjDisp(cmd)->BeginCommandBuffer(Unwrap(cmd), &beginInfo); + endCommand = true; + } if(cmd == VK_NULL_HANDLE) return; - ObjDisp(cmd)->BeginCommandBuffer(Unwrap(cmd), &beginInfo); - { VkMarkerRegion region(cmd, "CopyTex2DMSToBuffer"); @@ -159,24 +166,21 @@ void VulkanDebugManager::CopyTex2DMSToBuffer(VkBuffer destBuffer, VkImage srcMS, } } } - ObjDisp(cmd)->EndCommandBuffer(Unwrap(cmd)); - cmd = VK_NULL_HANDLE; + if(endCommand) + ObjDisp(cmd)->EndCommandBuffer(Unwrap(cmd)); - // submit cmds and wait for idle so we can readback - m_pDriver->SubmitCmds(); - m_pDriver->FlushQ(); - - RDCASSERT(cmd == VK_NULL_HANDLE); - - ObjDisp(dev)->DestroyImageView(Unwrap(dev), srcView, NULL); - ResetBufferMSDescriptorPools(); + m_pDriver->AddPendingObjectCleanup([this, dev, srcView]() { + ObjDisp(dev)->DestroyImageView(Unwrap(dev), srcView, NULL); + ResetBufferMSDescriptorPools(); + }); } -void VulkanDebugManager::CopyDepthTex2DMSToBuffer(VkBuffer destBuffer, VkImage srcMS, - VkExtent3D extent, uint32_t baseSlice, - uint32_t numSlices, uint32_t baseSample, - uint32_t numSamples, VkFormat fmt) +void VulkanDebugManager::CopyDepthTex2DMSToBuffer(VkCommandBuffer cmd, VkBuffer destBuffer, + VkImage srcMS, VkExtent3D extent, + uint32_t baseSlice, uint32_t numSlices, + uint32_t baseSample, uint32_t numSamples, + VkFormat fmt) { if(m_DepthMS2BufferPipe == VK_NULL_HANDLE) return; @@ -242,17 +246,22 @@ void VulkanDebugManager::CopyDepthTex2DMSToBuffer(VkBuffer destBuffer, VkImage s NameUnwrappedVulkanObject(srcStencilView, "Depth MS -> Array srcStencilView"); } - VkCommandBuffer cmd = VK_NULL_HANDLE; VkCommandBufferBeginInfo beginInfo = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, NULL, VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT}; - cmd = m_pDriver->GetNextCmd(); + bool endCommand = false; + + if(cmd == VK_NULL_HANDLE) + { + cmd = m_pDriver->GetNextCmd(); + if(cmd != VK_NULL_HANDLE) + ObjDisp(cmd)->BeginCommandBuffer(Unwrap(cmd), &beginInfo); + endCommand = true; + } if(cmd == VK_NULL_HANDLE) return; - ObjDisp(cmd)->BeginCommandBuffer(Unwrap(cmd), &beginInfo); - ObjDisp(cmd)->CmdBindPipeline(Unwrap(cmd), VK_PIPELINE_BIND_POINT_COMPUTE, Unwrap(m_DepthMS2BufferPipe)); @@ -344,21 +353,16 @@ void VulkanDebugManager::CopyDepthTex2DMSToBuffer(VkBuffer destBuffer, VkImage s } } - ObjDisp(cmd)->EndCommandBuffer(Unwrap(cmd)); + if(endCommand) + ObjDisp(cmd)->EndCommandBuffer(Unwrap(cmd)); - cmd = VK_NULL_HANDLE; - - // submit cmds and wait for idle so we can readback - m_pDriver->SubmitCmds(); - m_pDriver->FlushQ(); - - RDCASSERT(cmd == VK_NULL_HANDLE); - - if(srcDepthView != VK_NULL_HANDLE) - ObjDisp(dev)->DestroyImageView(Unwrap(dev), srcDepthView, NULL); - if(srcStencilView != VK_NULL_HANDLE) - ObjDisp(dev)->DestroyImageView(Unwrap(dev), srcStencilView, NULL); - ResetBufferMSDescriptorPools(); + m_pDriver->AddPendingObjectCleanup([this, dev, srcDepthView, srcStencilView]() { + if(srcDepthView != VK_NULL_HANDLE) + ObjDisp(dev)->DestroyImageView(Unwrap(dev), srcDepthView, NULL); + if(srcStencilView != VK_NULL_HANDLE) + ObjDisp(dev)->DestroyImageView(Unwrap(dev), srcStencilView, NULL); + ResetBufferMSDescriptorPools(); + }); } void VulkanDebugManager::CopyBufferToTex2DMS(VkCommandBuffer cmd, VkImage destMS, diff --git a/renderdoc/driver/vulkan/vk_replay.cpp b/renderdoc/driver/vulkan/vk_replay.cpp index 341aab7d8..2a4276c8a 100644 --- a/renderdoc/driver/vulkan/vk_replay.cpp +++ b/renderdoc/driver/vulkan/vk_replay.cpp @@ -3788,20 +3788,8 @@ void VulkanReplay::GetTextureData(ResourceId tex, const Subresource &sub, m_pDriver->InlineSetupImageBarriers(cmd, setupBarriers); m_pDriver->SubmitAndFlushImageStateBarriers(setupBarriers); - vkr = vt->EndCommandBuffer(Unwrap(cmd)); - CheckVkResult(vkr); - - GetDebugManager()->CopyTex2DMSToBuffer(readbackBuf, srcImage, imCreateInfo.extent, s.slice, 1, - s.sample, 1, imCreateInfo.format); - - // fetch a new command buffer for copy & readback - cmd = m_pDriver->GetNextCmd(); - - if(cmd == VK_NULL_HANDLE) - return; - - vkr = vt->BeginCommandBuffer(Unwrap(cmd), &beginInfo); - CheckVkResult(vkr); + GetDebugManager()->CopyTex2DMSToBuffer(cmd, readbackBuf, srcImage, imCreateInfo.extent, s.slice, + 1, s.sample, 1, imCreateInfo.format); m_pDriver->InlineCleanupImageBarriers(cmd, cleanupBarriers); diff --git a/renderdoc/driver/vulkan/wrappers/vk_device_funcs.cpp b/renderdoc/driver/vulkan/wrappers/vk_device_funcs.cpp index 29e72b10a..185dc37a2 100644 --- a/renderdoc/driver/vulkan/wrappers/vk_device_funcs.cpp +++ b/renderdoc/driver/vulkan/wrappers/vk_device_funcs.cpp @@ -4221,6 +4221,17 @@ VkResult WrappedVulkan::vkCreateDevice(VkPhysicalDevice physicalDevice, m_PhysicalDeviceData.driverInfo = VkDriverInfo(m_PhysicalDeviceData.props, m_PhysicalDeviceData.driverProps, true); + // hack for steamdeck, set soft memory limit to 200MB if it's not specified + if(m_PhysicalDeviceData.driverProps.driverID == VK_DRIVER_ID_MESA_RADV && + m_PhysicalDeviceData.props.vendorID == 0x1002 && m_PhysicalDeviceData.props.deviceID == 0x163F) + { + CaptureOptions opts = RenderDoc::Inst().GetCaptureOptions(); + if(opts.softMemoryLimit == 0) + opts.softMemoryLimit = 200; + RenderDoc::Inst().SetCaptureOptions(opts); + RDCLOG("Forcing 200MB soft memory limit"); + } + ChooseMemoryIndices(); m_PhysicalDeviceData.queueCount = (uint32_t)queueProps.size(); diff --git a/renderdoc/driver/vulkan/wrappers/vk_wsi_funcs.cpp b/renderdoc/driver/vulkan/wrappers/vk_wsi_funcs.cpp index 347469a22..e5024d51a 100644 --- a/renderdoc/driver/vulkan/wrappers/vk_wsi_funcs.cpp +++ b/renderdoc/driver/vulkan/wrappers/vk_wsi_funcs.cpp @@ -1090,6 +1090,10 @@ void WrappedVulkan::HandlePresent(VkQueue queue, const VkPresentInfoKHR *pPresen rdcstr overlayText = RenderDoc::Inst().GetOverlayText(RDCDriver::Vulkan, devWnd, m_FrameCounter, 0); + if(m_LastCaptureFailed > 0 && Timing::GetUnixTimestamp() - m_LastCaptureFailed < 5) + overlayText += StringFormat::Fmt("\nCapture failed: %s", + ResultDetails(m_LastCaptureError).Message().c_str()); + if(!overlayText.empty()) { m_TextRenderer->BeginText(textstate); diff --git a/renderdoc/replay/capture_options.cpp b/renderdoc/replay/capture_options.cpp index f2d62b918..16466b336 100644 --- a/renderdoc/replay/capture_options.cpp +++ b/renderdoc/replay/capture_options.cpp @@ -56,6 +56,7 @@ int RENDERDOC_CC SetCaptureOptionU32(RENDERDOC_CaptureOption opt, uint32_t val) else RDCWARN("AllowUnsupportedVendorExtensions unexpected parameter %x", val); break; + case eRENDERDOC_Option_SoftMemoryLimit: opts.softMemoryLimit = val; break; default: RDCLOG("Unrecognised capture option '%d'", opt); return 0; } @@ -88,6 +89,7 @@ int RENDERDOC_CC SetCaptureOptionF32(RENDERDOC_CaptureOption opt, float val) case eRENDERDOC_Option_AllowUnsupportedVendorExtensions: RDCWARN("AllowUnsupportedVendorExtensions unexpected parameter %f", val); break; + case eRENDERDOC_Option_SoftMemoryLimit: opts.softMemoryLimit = (uint32_t)val; break; default: RDCLOG("Unrecognised capture option '%d'", opt); return 0; } @@ -125,6 +127,8 @@ uint32_t RENDERDOC_CC GetCaptureOptionU32(RENDERDOC_CaptureOption opt) case eRENDERDOC_Option_DebugOutputMute: return (RenderDoc::Inst().GetCaptureOptions().debugOutputMute ? 1 : 0); case eRENDERDOC_Option_AllowUnsupportedVendorExtensions: return 0; + case eRENDERDOC_Option_SoftMemoryLimit: + return (RenderDoc::Inst().GetCaptureOptions().softMemoryLimit); default: break; } @@ -162,6 +166,8 @@ float RENDERDOC_CC GetCaptureOptionF32(RENDERDOC_CaptureOption opt) case eRENDERDOC_Option_DebugOutputMute: return (RenderDoc::Inst().GetCaptureOptions().debugOutputMute ? 1.0f : 0.0f); case eRENDERDOC_Option_AllowUnsupportedVendorExtensions: return 0.0f; + case eRENDERDOC_Option_SoftMemoryLimit: + return (RenderDoc::Inst().GetCaptureOptions().softMemoryLimit * 1.0f); default: break; } @@ -184,6 +190,7 @@ CaptureOptions::CaptureOptions() refAllResources = false; captureAllCmdLists = false; debugOutputMute = true; + softMemoryLimit = 0; } #if ENABLED(ENABLE_UNIT_TESTS) diff --git a/renderdoc/replay/renderdoc_serialise.inl b/renderdoc/replay/renderdoc_serialise.inl index 62e7da180..2deeaf6d8 100644 --- a/renderdoc/replay/renderdoc_serialise.inl +++ b/renderdoc/replay/renderdoc_serialise.inl @@ -102,8 +102,9 @@ void DoSerialise(SerialiserType &ser, CaptureOptions &el) SERIALISE_MEMBER(refAllResources); SERIALISE_MEMBER(captureAllCmdLists); SERIALISE_MEMBER(debugOutputMute); + SERIALISE_MEMBER(softMemoryLimit); - SIZE_CHECK(20); + SIZE_CHECK(24); } template diff --git a/renderdoc/serialise/serialiser.cpp b/renderdoc/serialise/serialiser.cpp index d5916b97a..ee759ad87 100644 --- a/renderdoc/serialise/serialiser.cpp +++ b/renderdoc/serialise/serialiser.cpp @@ -1019,7 +1019,7 @@ rdcstr DoStringise(const bool &el) } Chunk *Chunk::Create(Serialiser &ser, uint16_t chunkType, - ChunkAllocator *allocator) + ChunkAllocator *allocator, bool stealDataFromWriter) { RDCCOMPILE_ASSERT(sizeof(Chunk) <= 16, "Chunk should be no more than 16 bytes"); @@ -1027,24 +1027,32 @@ Chunk *Chunk::Create(Serialiser &ser, uint16_t chunkTyp uint32_t length = (uint32_t)ser.GetWriter()->GetOffset(); byte *data = NULL; - if(allocator) + + if(stealDataFromWriter) { - // try to allocate from the allocator - data = allocator->AllocAlignedBuffer(length); - - // if we couldn't satisfy the allocation then pretend we never had an allocator in the first - // place. We'll externally allocate the chunk and the data. - if(!data) - allocator = NULL; + data = ser.GetWriter()->StealDataAndRewind(); } + else + { + if(allocator) + { + // try to allocate from the allocator + data = allocator->AllocAlignedBuffer(length); - // if we don't have an allocator or we gave up on it above, allocate the data externally - if(!allocator) - data = AllocAlignedBuffer(length); + // if we couldn't satisfy the allocation then pretend we never had an allocator in the first + // place. We'll externally allocate the chunk and the data. + if(!data) + allocator = NULL; + } - memcpy(data, ser.GetWriter()->GetData(), (size_t)length); + // if we don't have an allocator or we gave up on it above, allocate the data externally + if(!allocator) + data = AllocAlignedBuffer(length); - ser.GetWriter()->Rewind(); + memcpy(data, ser.GetWriter()->GetData(), (size_t)length); + + ser.GetWriter()->Rewind(); + } Chunk *ret = NULL; diff --git a/renderdoc/serialise/serialiser.h b/renderdoc/serialise/serialiser.h index 6aab82aa6..9fb826eb0 100644 --- a/renderdoc/serialise/serialiser.h +++ b/renderdoc/serialise/serialiser.h @@ -1807,7 +1807,7 @@ public: // grab current contents of the serialiser into a new chunk static Chunk *Create(Serialiser &ser, uint16_t chunkType, - ChunkAllocator *allocator = NULL); + ChunkAllocator *allocator = NULL, bool stealDataFromWriter = false); byte *GetData() const { return m_Data; } Chunk *Duplicate() @@ -1867,10 +1867,16 @@ public: End(); } + Chunk *Steal() + { + End(); + return Chunk::Create(m_Ser, m_Idx, NULL, true); + } + Chunk *Get(ChunkAllocator *allocator = NULL) { End(); - return Chunk::Create(m_Ser, m_Idx, allocator); + return Chunk::Create(m_Ser, m_Idx, allocator, false); } private: diff --git a/renderdoc/serialise/streamio.cpp b/renderdoc/serialise/streamio.cpp index ee5a5d6c7..ee76e4e03 100644 --- a/renderdoc/serialise/streamio.cpp +++ b/renderdoc/serialise/streamio.cpp @@ -380,8 +380,15 @@ bool StreamReader::ReadFromExternal(void *buffer, uint64_t length) success = (numRead == length); if(!success) - SET_ERROR_RESULT(m_Error, ResultCode::FileIOFailed, "Error reading from file: %s", - FileIO::ErrorString().c_str()); + { + if(FileIO::feof(m_File)) + SET_ERROR_RESULT(m_Error, ResultCode::FileIOFailed, + "Error reading from file: hit end of file unexpectedly. Out of disk space " + "or truncated file?"); + else + SET_ERROR_RESULT(m_Error, ResultCode::FileIOFailed, "Error reading from file: %s", + FileIO::ErrorString().c_str()); + } } else if(m_Sock) { diff --git a/renderdoc/serialise/streamio.h b/renderdoc/serialise/streamio.h index 7bcb371f4..d70f3a734 100644 --- a/renderdoc/serialise/streamio.h +++ b/renderdoc/serialise/streamio.h @@ -393,6 +393,17 @@ public: uint64_t GetOffset() { return m_WriteSize; } const byte *GetData() { return m_BufferBase; } + byte *StealDataAndRewind() + { + byte *ret = m_BufferBase; + + const uint64_t bufferSize = m_BufferEnd - m_BufferBase; + m_BufferBase = m_BufferHead = AllocAlignedBuffer(bufferSize); + m_BufferEnd = m_BufferBase + bufferSize; + m_WriteSize = 0; + + return ret; + } template bool AlignTo() { diff --git a/renderdoccmd/renderdoccmd.cpp b/renderdoccmd/renderdoccmd.cpp index b9f0951cf..71f01dcc2 100644 --- a/renderdoccmd/renderdoccmd.cpp +++ b/renderdoccmd/renderdoccmd.cpp @@ -1639,6 +1639,9 @@ int renderdoccmd(GlobalEnvironment &env, std::vector &argv) "Capturing Option: Include all live resources, not just those used by a frame."); cmd.add("opt-capture-all-cmd-lists", 0, "Capturing Option: In D3D11, record all command lists from application start."); + cmd.add("opt-soft-memory-limit", 0, + "Capturing Option: Specify a soft memory limit to try to respect.", false, 0, + cmdline::range(0, 10000)); } cmd.parse_check(argv, true); @@ -1670,6 +1673,7 @@ int renderdoccmd(GlobalEnvironment &env, std::vector &argv) opts.captureAllCmdLists = true; opts.delayForDebugger = (uint32_t)cmd.get("opt-delay-for-debugger"); + opts.softMemoryLimit = (uint32_t)cmd.get("opt-soft-memory-limit"); } if(!it->second->HandlesUsageManually() && cmd.exist("help"))