Make sure objects are deleted when idle
The Vulkan backend enqueues objects for deletion in FencedDeleter after
the pending command serial is completed. This works until idle and no
more work is submitted and the submitted/completed serial is never
incremented.
Have ReduceMemoryUsage() check if there any Vulkan objects pending
deletion. If there are then it will potentially submit and then tick.
If there was a vkQueueSubmit() the last submitted serial won't become
completed immediately, all work must finish on the GPU first, and
objects may still be pending deletion. Add a bool to ReduceMemoryUsage()
to signal to Chrome that there is more deletion work to be done in the
future and ReduceMemoryUsage() needs to be called again after a short
delay.
Bug: 398193014
Change-Id: I0207522ac501cbba18479f27a4113bd39598855b
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/231174
Reviewed-by: Brandon Jones <bajones@chromium.org>
Commit-Queue: Kyle Charbonneau <kylechar@google.com>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
diff --git a/include/dawn/native/DawnNative.h b/include/dawn/native/DawnNative.h
index 24b199b..2a22d7e 100644
--- a/include/dawn/native/DawnNative.h
+++ b/include/dawn/native/DawnNative.h
@@ -335,8 +335,10 @@
};
DAWN_NATIVE_EXPORT MemoryUsageInfo ComputeEstimatedMemoryUsageInfo(WGPUDevice device);
-// Free any unused GPU memory like staging buffers, cached resources, etc.
-DAWN_NATIVE_EXPORT void ReduceMemoryUsage(WGPUDevice device);
+// Free any unused GPU memory like staging buffers, cached resources, etc. Returns true if there are
+// still objects to delete and ReduceMemoryUsage() should be run again after a short delay to allow
+// submitted work to complete.
+DAWN_NATIVE_EXPORT bool ReduceMemoryUsage(WGPUDevice device);
// Perform tasks that are appropriate to do when idle like serializing pipeline
// caches, etc.
diff --git a/src/dawn/native/DawnNative.cpp b/src/dawn/native/DawnNative.cpp
index d25efee..d5da8f4 100644
--- a/src/dawn/native/DawnNative.cpp
+++ b/src/dawn/native/DawnNative.cpp
@@ -285,9 +285,9 @@
return FromAPI(device)->ComputeEstimatedMemoryUsage();
}
-void ReduceMemoryUsage(WGPUDevice device) {
+bool ReduceMemoryUsage(WGPUDevice device) {
auto deviceLock(FromAPI(device)->GetScopedLock());
- FromAPI(device)->ReduceMemoryUsage();
+ return FromAPI(device)->ReduceMemoryUsage();
}
void PerformIdleTasks(const wgpu::Device& device) {
diff --git a/src/dawn/native/Device.cpp b/src/dawn/native/Device.cpp
index 3f4078a..0038ee4 100644
--- a/src/dawn/native/Device.cpp
+++ b/src/dawn/native/Device.cpp
@@ -2335,6 +2335,10 @@
void DeviceBase::SetLabelImpl() {}
+bool DeviceBase::ReduceMemoryUsageImpl() {
+ return false;
+}
+
void DeviceBase::PerformIdleTasksImpl() {}
bool DeviceBase::ShouldDuplicateNumWorkgroupsForDispatchIndirect(
@@ -2459,10 +2463,10 @@
return info;
}
-void DeviceBase::ReduceMemoryUsage() {
+bool DeviceBase::ReduceMemoryUsage() {
DAWN_ASSERT(IsLockedByCurrentThreadIfNeeded());
if (ConsumedError(GetQueue()->CheckPassedSerials())) {
- return;
+ return false;
}
GetDynamicUploader()->Deallocate(GetQueue()->GetCompletedCommandSerial(), /*freeAll=*/true);
mInternalPipelineStore->ResetScratchBuffers();
@@ -2473,6 +2477,10 @@
});
TrimErrorScopeStacks(mErrorScopeStacks);
+
+ // TODO(crbug.com/398193014): This could return a future to wait on instead of just a bool
+ // saying there is work to wait on.
+ return ReduceMemoryUsageImpl();
}
void DeviceBase::PerformIdleTasks() {
diff --git a/src/dawn/native/Device.h b/src/dawn/native/Device.h
index 6fecb29..6212db9 100644
--- a/src/dawn/native/Device.h
+++ b/src/dawn/native/Device.h
@@ -419,7 +419,7 @@
void DumpMemoryStatistics(dawn::native::MemoryDump* dump) const;
MemoryUsageInfo ComputeEstimatedMemoryUsage() const;
- void ReduceMemoryUsage();
+ bool ReduceMemoryUsage();
void PerformIdleTasks();
ResultOrError<Ref<BufferBase>> GetOrCreateTemporaryUniformBuffer(size_t size);
@@ -495,6 +495,7 @@
virtual ResultOrError<Ref<SharedFenceBase>> ImportSharedFenceImpl(
const SharedFenceDescriptor* descriptor);
virtual void SetLabelImpl();
+ virtual bool ReduceMemoryUsageImpl();
virtual void PerformIdleTasksImpl();
virtual MaybeError TickImpl() = 0;
diff --git a/src/dawn/native/vulkan/DeviceVk.cpp b/src/dawn/native/vulkan/DeviceVk.cpp
index f4a415a..1ecae43 100644
--- a/src/dawn/native/vulkan/DeviceVk.cpp
+++ b/src/dawn/native/vulkan/DeviceVk.cpp
@@ -27,6 +27,8 @@
#include "dawn/native/vulkan/DeviceVk.h"
+#include <algorithm>
+
#include "dawn/common/Log.h"
#include "dawn/common/NonCopyable.h"
#include "dawn/common/Platform.h"
@@ -37,6 +39,7 @@
#include "dawn/native/Error.h"
#include "dawn/native/ErrorData.h"
#include "dawn/native/Instance.h"
+#include "dawn/native/IntegerTypes.h"
#include "dawn/native/SystemHandle.h"
#include "dawn/native/VulkanBackend.h"
#include "dawn/native/vulkan/BackendVk.h"
@@ -1027,6 +1030,39 @@
SetDebugName(this, VK_OBJECT_TYPE_DEVICE, mVkDevice, "Dawn_Device", GetLabel());
}
+bool Device::ReduceMemoryUsageImpl() {
+ ExecutionSerial deletionSerial =
+ std::max(GetFencedDeleter()->GetLastPendingDeletionSerial(),
+ GetResourceMemoryAllocator()->GetLastPendingDeletionSerial());
+
+ if (deletionSerial == kBeginningOfGPUTime) {
+ // Nothing pending deletion.
+ return false;
+ }
+
+ Queue* queue = ToBackend(GetQueue());
+
+ if (deletionSerial > queue->GetLastSubmittedCommandSerial()) {
+ // Submit any pending commands to ensure that the pending deletion serial completes. One
+ // complication here is that there might not be any pending commands and the queue would
+ // skip commit. Getting the recording context works makes it look like there is work to
+ // submit in all cases.
+ // TODO(crbug.com/398193014): If there is no work in the queue to submit then the device
+ // should be able to immediately delete objects enqueued for deletion after tick completes.
+ queue->GetPendingRecordingContext();
+ }
+
+ if (ConsumedError(TickImpl())) {
+ return false;
+ }
+ DAWN_ASSERT(deletionSerial <= queue->GetLastSubmittedCommandSerial());
+
+ // Check again if there is anything left to delete as tick might have deleted objects.
+ return std::max(GetFencedDeleter()->GetLastPendingDeletionSerial(),
+ GetResourceMemoryAllocator()->GetLastPendingDeletionSerial()) !=
+ kBeginningOfGPUTime;
+}
+
void Device::PerformIdleTasksImpl() {
if (mMonolithicPipelineCache) {
MaybeError maybeError = mMonolithicPipelineCache->StoreOnIdle();
diff --git a/src/dawn/native/vulkan/DeviceVk.h b/src/dawn/native/vulkan/DeviceVk.h
index 00a5bdb..d5ed961 100644
--- a/src/dawn/native/vulkan/DeviceVk.h
+++ b/src/dawn/native/vulkan/DeviceVk.h
@@ -118,6 +118,7 @@
float GetTimestampPeriodInNS() const override;
void SetLabelImpl() override;
+ bool ReduceMemoryUsageImpl() override;
void PerformIdleTasksImpl() override;
void OnDebugMessage(std::string message);
diff --git a/src/dawn/native/vulkan/FencedDeleter.cpp b/src/dawn/native/vulkan/FencedDeleter.cpp
index a754d95..9e4d519 100644
--- a/src/dawn/native/vulkan/FencedDeleter.cpp
+++ b/src/dawn/native/vulkan/FencedDeleter.cpp
@@ -27,6 +27,8 @@
#include "dawn/native/vulkan/FencedDeleter.h"
+#include <algorithm>
+
#include "dawn/native/Queue.h"
#include "dawn/native/vulkan/DeviceVk.h"
@@ -123,6 +125,35 @@
mSwapChainsToDelete.Enqueue(swapChain, mDevice->GetQueue()->GetPendingCommandSerial());
}
+ExecutionSerial FencedDeleter::GetLastPendingDeletionSerial() {
+ ExecutionSerial lastSerial = kBeginningOfGPUTime;
+ auto GetLastSubmitted = [&lastSerial](auto& queue) {
+ if (!queue.Empty()) {
+ lastSerial = std::max(lastSerial, queue.LastSerial());
+ }
+ };
+
+ GetLastSubmitted(mBuffersToDelete);
+ GetLastSubmitted(mDescriptorPoolsToDelete);
+ GetLastSubmitted(mFencesToDelete);
+ GetLastSubmitted(mFramebuffersToDelete);
+ GetLastSubmitted(mImagesToDelete);
+ GetLastSubmitted(mImageViewsToDelete);
+ GetLastSubmitted(mMemoriesToDelete);
+ GetLastSubmitted(mPipelinesToDelete);
+ GetLastSubmitted(mPipelineLayoutsToDelete);
+ GetLastSubmitted(mQueryPoolsToDelete);
+ GetLastSubmitted(mRenderPassesToDelete);
+ GetLastSubmitted(mSamplerYcbcrConversionsToDelete);
+ GetLastSubmitted(mSamplersToDelete);
+ GetLastSubmitted(mSemaphoresToDelete);
+ GetLastSubmitted(mShaderModulesToDelete);
+ GetLastSubmitted(mSurfacesToDelete);
+ GetLastSubmitted(mSwapChainsToDelete);
+
+ return lastSerial;
+}
+
void FencedDeleter::Tick(ExecutionSerial completedSerial) {
VkDevice vkDevice = mDevice->GetVkDevice();
VkInstance instance = mDevice->GetVkInstance();
diff --git a/src/dawn/native/vulkan/FencedDeleter.h b/src/dawn/native/vulkan/FencedDeleter.h
index c7b4bed..ebd241c 100644
--- a/src/dawn/native/vulkan/FencedDeleter.h
+++ b/src/dawn/native/vulkan/FencedDeleter.h
@@ -60,6 +60,10 @@
void DeleteWhenUnused(VkSurfaceKHR surface);
void DeleteWhenUnused(VkSwapchainKHR swapChain);
+ // Returns the last serial that an object is pending deletion after or
+ // kBeginningOfGPUTime if no objects are pending deletion.
+ ExecutionSerial GetLastPendingDeletionSerial();
+
void Tick(ExecutionSerial completedSerial);
private:
diff --git a/src/dawn/native/vulkan/ResourceMemoryAllocatorVk.cpp b/src/dawn/native/vulkan/ResourceMemoryAllocatorVk.cpp
index c7d4617..9425c6f 100644
--- a/src/dawn/native/vulkan/ResourceMemoryAllocatorVk.cpp
+++ b/src/dawn/native/vulkan/ResourceMemoryAllocatorVk.cpp
@@ -281,6 +281,17 @@
allocation->Invalidate();
}
+ExecutionSerial ResourceMemoryAllocator::GetLastPendingDeletionSerial() {
+ ExecutionSerial lastSerial = kBeginningOfGPUTime;
+ auto GetLastSubmitted = [&lastSerial](auto& queue) {
+ if (!queue.Empty()) {
+ lastSerial = std::max(lastSerial, queue.LastSerial());
+ }
+ };
+ GetLastSubmitted(mSubAllocationsToDelete);
+ return lastSerial;
+}
+
void ResourceMemoryAllocator::Tick(ExecutionSerial completedSerial) {
for (const ResourceMemoryAllocation& allocation :
mSubAllocationsToDelete.IterateUpTo(completedSerial)) {
diff --git a/src/dawn/native/vulkan/ResourceMemoryAllocatorVk.h b/src/dawn/native/vulkan/ResourceMemoryAllocatorVk.h
index d166fd1..77b5f13 100644
--- a/src/dawn/native/vulkan/ResourceMemoryAllocatorVk.h
+++ b/src/dawn/native/vulkan/ResourceMemoryAllocatorVk.h
@@ -69,6 +69,10 @@
void DestroyPool();
+ // Returns the last serial that an object is pending deletion after or
+ // kBeginningOfGPUTime if no objects are pending deletion.
+ ExecutionSerial GetLastPendingDeletionSerial();
+
void Tick(ExecutionSerial completedSerial);
int FindBestTypeIndex(VkMemoryRequirements requirements, MemoryKind kind);