Fixes Vulkan command buffer leaks when an error occurs.
- Uses an anonymous function to delete command pool/buffers.
- Shuffles the code around a bit so that the CommandPoolAndBuffer are
clearly next to the EncodingContext stuff to make it clear that we
may be able to consolidate them in the future.
Bug: chromium:1372772
Change-Id: I92a1d0333b7a85d439b5963a58db69ac685c03a4
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/112181
Reviewed-by: Austin Eng <enga@chromium.org>
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Loko Kung <lokokung@google.com>
diff --git a/src/dawn/native/vulkan/CommandRecordingContext.h b/src/dawn/native/vulkan/CommandRecordingContext.h
index 7948e3b..7bd762f 100644
--- a/src/dawn/native/vulkan/CommandRecordingContext.h
+++ b/src/dawn/native/vulkan/CommandRecordingContext.h
@@ -19,11 +19,21 @@
#include "dawn/common/vulkan_platform.h"
#include "dawn/native/vulkan/BufferVk.h"
+#include "dawn/native/vulkan/VulkanFunctions.h"
namespace dawn::native::vulkan {
class Texture;
+// Wrapping class that currently associates a command buffer to it's corresponding pool.
+// TODO(dawn:1601) Revisit this structure since it is where the 1:1 mapping is implied.
+// Also consider reusing this in CommandRecordingContext below instead of
+// flattening the pool and command buffer again.
+struct CommandPoolAndBuffer {
+ VkCommandPool pool = VK_NULL_HANDLE;
+ VkCommandBuffer commandBuffer = VK_NULL_HANDLE;
+};
+
// Used to track operations that are handled after recording.
// Currently only tracks semaphores, but may be used to do barrier coalescing in the future.
struct CommandRecordingContext {
diff --git a/src/dawn/native/vulkan/DeviceVk.cpp b/src/dawn/native/vulkan/DeviceVk.cpp
index fed1cb9..63c94a2 100644
--- a/src/dawn/native/vulkan/DeviceVk.cpp
+++ b/src/dawn/native/vulkan/DeviceVk.cpp
@@ -69,6 +69,23 @@
VkSemaphore mSemaphore = VK_NULL_HANDLE;
};
+// Destroys command pool/buffer.
+// TODO(dawn:1601) Revisit this and potentially bake into pool/buffer objects instead.
+void DestroyCommandPoolAndBuffer(const VulkanFunctions& fn,
+ VkDevice device,
+ const CommandPoolAndBuffer& commands) {
+ // The VkCommandBuffer memory should be wholly owned by the pool and freed when it is
+ // destroyed, but that's not the case in some drivers and they leak memory. So we call
+ // FreeCommandBuffers before DestroyCommandPool to be safe.
+ // TODO(enga): Only do this on a known list of bad drivers.
+ if (commands.pool != VK_NULL_HANDLE) {
+ if (commands.commandBuffer != VK_NULL_HANDLE) {
+ fn.FreeCommandBuffers(device, commands.pool, 1, &commands.commandBuffer);
+ }
+ fn.DestroyCommandPool(device, commands.pool, nullptr);
+ }
+}
+
} // namespace
// static
@@ -745,7 +762,7 @@
return {};
}
-ResultOrError<Device::CommandPoolAndBuffer> Device::BeginVkCommandBuffer() {
+ResultOrError<CommandPoolAndBuffer> Device::BeginVkCommandBuffer() {
CommandPoolAndBuffer commands;
// First try to recycle unused command pools.
@@ -754,19 +771,7 @@
mUnusedCommands.pop_back();
DAWN_TRY_WITH_CLEANUP(
CheckVkSuccess(fn.ResetCommandPool(mVkDevice, commands.pool, 0), "vkResetCommandPool"),
- {
- // vkResetCommandPool failed (it may return out-of-memory).
- // Free the commands in the cleanup step before returning to
- // reclaim memory.
-
- // The VkCommandBuffer memory should be wholly owned by the
- // pool and freed when it is destroyed, but that's not the
- // case in some drivers and they leak memory. So we call
- // FreeCommandBuffers before DestroyCommandPool to be safe.
- // TODO(enga): Only do this on a known list of bad drivers.
- fn.FreeCommandBuffers(mVkDevice, commands.pool, 1, &commands.commandBuffer);
- fn.DestroyCommandPool(mVkDevice, commands.pool, nullptr);
- });
+ { DestroyCommandPoolAndBuffer(fn, mVkDevice, commands); });
} else {
// Create a new command pool for our commands and allocate the command buffer.
VkCommandPoolCreateInfo createInfo;
@@ -786,9 +791,10 @@
allocateInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
allocateInfo.commandBufferCount = 1;
- DAWN_TRY(CheckVkSuccess(
- fn.AllocateCommandBuffers(mVkDevice, &allocateInfo, &commands.commandBuffer),
- "vkAllocateCommandBuffers"));
+ DAWN_TRY_WITH_CLEANUP(CheckVkSuccess(fn.AllocateCommandBuffers(mVkDevice, &allocateInfo,
+ &commands.commandBuffer),
+ "vkAllocateCommandBuffers"),
+ { DestroyCommandPoolAndBuffer(fn, mVkDevice, commands); });
}
// Start the recording of commands in the command buffer.
@@ -798,8 +804,9 @@
beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
beginInfo.pInheritanceInfo = nullptr;
- DAWN_TRY(CheckVkSuccess(fn.BeginCommandBuffer(commands.commandBuffer, &beginInfo),
- "vkBeginCommandBuffer"));
+ DAWN_TRY_WITH_CLEANUP(CheckVkSuccess(fn.BeginCommandBuffer(commands.commandBuffer, &beginInfo),
+ "vkBeginCommandBuffer"),
+ { DestroyCommandPoolAndBuffer(fn, mVkDevice, commands); });
return commands;
}
@@ -1127,13 +1134,8 @@
// Immediately tag the recording context as unused so we don't try to submit it in Tick.
mRecordingContext.needsSubmit = false;
if (mRecordingContext.commandPool != VK_NULL_HANDLE) {
- // The VkCommandBuffer memory should be wholly owned by the pool and freed when it is
- // destroyed, but that's not the case in some drivers and the leak memory.
- // So we call FreeCommandBuffers before DestroyCommandPool to be safe.
- // TODO(enga): Only do this on a known list of bad drivers.
- fn.FreeCommandBuffers(mVkDevice, mRecordingContext.commandPool, 1,
- &mRecordingContext.commandBuffer);
- fn.DestroyCommandPool(mVkDevice, mRecordingContext.commandPool, nullptr);
+ DestroyCommandPoolAndBuffer(
+ fn, mVkDevice, {mRecordingContext.commandPool, mRecordingContext.commandBuffer});
}
for (VkSemaphore semaphore : mRecordingContext.waitSemaphores) {
@@ -1147,12 +1149,7 @@
ASSERT(mCommandsInFlight.Empty());
for (const CommandPoolAndBuffer& commands : mUnusedCommands) {
- // The VkCommandBuffer memory should be wholly owned by the pool and freed when it is
- // destroyed, but that's not the case in some drivers and the leak memory.
- // So we call FreeCommandBuffers before DestroyCommandPool to be safe.
- // TODO(enga): Only do this on a known list of bad drivers.
- fn.FreeCommandBuffers(mVkDevice, commands.pool, 1, &commands.commandBuffer);
- fn.DestroyCommandPool(mVkDevice, commands.pool, nullptr);
+ DestroyCommandPoolAndBuffer(fn, mVkDevice, commands);
}
mUnusedCommands.clear();
diff --git a/src/dawn/native/vulkan/DeviceVk.h b/src/dawn/native/vulkan/DeviceVk.h
index d9c01db..2b49a2e 100644
--- a/src/dawn/native/vulkan/DeviceVk.h
+++ b/src/dawn/native/vulkan/DeviceVk.h
@@ -210,11 +210,6 @@
const std::string mDebugPrefix;
std::vector<std::string> mDebugMessages;
- struct CommandPoolAndBuffer {
- VkCommandPool pool = VK_NULL_HANDLE;
- VkCommandBuffer commandBuffer = VK_NULL_HANDLE;
- };
-
MaybeError PrepareRecordingContext();
ResultOrError<CommandPoolAndBuffer> BeginVkCommandBuffer();
void RecycleCompletedCommands();