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"))