Vulkan: Properly handle errors for fences and commands

This commit makes the following changes:
 - Unused fences are reset when they will next be used so there is a
   single place where error handling is needed, either for resetting
   an existing fence, or for creating a new fence.
 - All accesses to VkCommandBuffer are moved to using the
   CommandRecordingContext and GetPendingCommandBuffer is removed.
 - Instead of tracking both a current (VkCommandBuffer + Pool) and
   a command recording context that contains the same VkCommandBuffer,
   the RecordingContext now holds the pool too, as well as a tag to
   know if it was ever queried (meaning it contains commands).
 - mRecordingContext is now always valid, such that
   GetRecordingContext() doesn't need to return an error.

BUG=dawn:19

Change-Id: I853d4ecdc6905b66e842688f39d863e362f59b66
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/12022
Commit-Queue: Corentin Wallez <cwallez@chromium.org>
Reviewed-by: Austin Eng <enga@chromium.org>
Reviewed-by: Kai Ninomiya <kainino@chromium.org>
This commit is contained in:
Corentin Wallez 2019-10-11 12:12:07 +00:00 committed by Commit Bot service account
parent 76e1e41cc7
commit c44b2d4881
4 changed files with 87 additions and 97 deletions

View File

@ -33,6 +33,10 @@ namespace dawn_native { namespace vulkan {
// The internal buffers used in the workaround of texture-to-texture copies with compressed // The internal buffers used in the workaround of texture-to-texture copies with compressed
// formats. // formats.
std::vector<Ref<Buffer>> tempBuffers; std::vector<Ref<Buffer>> tempBuffers;
// For Device state tracking only.
VkCommandPool commandPool = VK_NULL_HANDLE;
bool used = false;
}; };
}} // namespace dawn_native::vulkan }} // namespace dawn_native::vulkan

View File

@ -75,12 +75,15 @@ namespace dawn_native { namespace vulkan {
mExternalMemoryService = std::make_unique<external_memory::Service>(this); mExternalMemoryService = std::make_unique<external_memory::Service>(this);
mExternalSemaphoreService = std::make_unique<external_semaphore::Service>(this); mExternalSemaphoreService = std::make_unique<external_semaphore::Service>(this);
DAWN_TRY(PrepareRecordingContext());
return {}; return {};
} }
Device::~Device() { Device::~Device() {
// Immediately forget about all pending commands so we don't try to submit them in Tick // Immediately tag the recording context as unused so we don't try to submit it in Tick.
FreeCommands(&mPendingCommands); mRecordingContext.used = false;
fn.DestroyCommandPool(mVkDevice, mRecordingContext.commandPool, nullptr);
if (fn.QueueWaitIdle(mQueue) != VK_SUCCESS) { if (fn.QueueWaitIdle(mQueue) != VK_SUCCESS) {
ASSERT(false); ASSERT(false);
@ -110,8 +113,8 @@ namespace dawn_native { namespace vulkan {
Tick(); Tick();
ASSERT(mCommandsInFlight.Empty()); ASSERT(mCommandsInFlight.Empty());
for (auto& commands : mUnusedCommands) { for (const CommandPoolAndBuffer& commands : mUnusedCommands) {
FreeCommands(&commands); fn.DestroyCommandPool(mVkDevice, commands.pool, nullptr);
} }
mUnusedCommands.clear(); mUnusedCommands.clear();
@ -221,7 +224,7 @@ namespace dawn_native { namespace vulkan {
mDeleter->Tick(mCompletedSerial); mDeleter->Tick(mCompletedSerial);
if (mPendingCommands.pool != VK_NULL_HANDLE) { if (mRecordingContext.used) {
DAWN_TRY(SubmitPendingCommands()); DAWN_TRY(SubmitPendingCommands());
} else if (mCompletedSerial == mLastSubmittedSerial) { } else if (mCompletedSerial == mLastSubmittedSerial) {
// If there's no GPU work in flight we still need to artificially increment the serial // If there's no GPU work in flight we still need to artificially increment the serial
@ -268,38 +271,18 @@ namespace dawn_native { namespace vulkan {
return mRenderPassCache.get(); return mRenderPassCache.get();
} }
VkCommandBuffer Device::GetPendingCommandBuffer() {
if (mPendingCommands.pool == VK_NULL_HANDLE) {
mPendingCommands = GetUnusedCommands();
VkCommandBufferBeginInfo beginInfo;
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
beginInfo.pNext = nullptr;
beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
beginInfo.pInheritanceInfo = nullptr;
if (fn.BeginCommandBuffer(mPendingCommands.commandBuffer, &beginInfo) != VK_SUCCESS) {
ASSERT(false);
}
}
return mPendingCommands.commandBuffer;
}
CommandRecordingContext* Device::GetPendingRecordingContext() { CommandRecordingContext* Device::GetPendingRecordingContext() {
if (mRecordingContext.commandBuffer == VK_NULL_HANDLE) { ASSERT(mRecordingContext.commandBuffer != VK_NULL_HANDLE);
mRecordingContext.commandBuffer = GetPendingCommandBuffer(); mRecordingContext.used = true;
}
return &mRecordingContext; return &mRecordingContext;
} }
MaybeError Device::SubmitPendingCommands() { MaybeError Device::SubmitPendingCommands() {
if (mPendingCommands.pool == VK_NULL_HANDLE) { if (!mRecordingContext.used) {
return {}; return {};
} }
DAWN_TRY(CheckVkSuccess(fn.EndCommandBuffer(mPendingCommands.commandBuffer), DAWN_TRY(CheckVkSuccess(fn.EndCommandBuffer(mRecordingContext.commandBuffer),
"vkEndCommandBuffer")); "vkEndCommandBuffer"));
std::vector<VkPipelineStageFlags> dstStageMasks(mRecordingContext.waitSemaphores.size(), std::vector<VkPipelineStageFlags> dstStageMasks(mRecordingContext.waitSemaphores.size(),
@ -313,19 +296,24 @@ namespace dawn_native { namespace vulkan {
submitInfo.pWaitSemaphores = mRecordingContext.waitSemaphores.data(); submitInfo.pWaitSemaphores = mRecordingContext.waitSemaphores.data();
submitInfo.pWaitDstStageMask = dstStageMasks.data(); submitInfo.pWaitDstStageMask = dstStageMasks.data();
submitInfo.commandBufferCount = 1; submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &mPendingCommands.commandBuffer; submitInfo.pCommandBuffers = &mRecordingContext.commandBuffer;
submitInfo.signalSemaphoreCount = submitInfo.signalSemaphoreCount =
static_cast<uint32_t>(mRecordingContext.signalSemaphores.size()); static_cast<uint32_t>(mRecordingContext.signalSemaphores.size());
submitInfo.pSignalSemaphores = mRecordingContext.signalSemaphores.data(); submitInfo.pSignalSemaphores = mRecordingContext.signalSemaphores.data();
VkFence fence = GetUnusedFence(); VkFence fence = VK_NULL_HANDLE;
DAWN_TRY_ASSIGN(fence, GetUnusedFence());
DAWN_TRY(CheckVkSuccess(fn.QueueSubmit(mQueue, 1, &submitInfo, fence), "vkQueueSubmit")); DAWN_TRY(CheckVkSuccess(fn.QueueSubmit(mQueue, 1, &submitInfo, fence), "vkQueueSubmit"));
mLastSubmittedSerial++; mLastSubmittedSerial++;
mCommandsInFlight.Enqueue(mPendingCommands, mLastSubmittedSerial);
mPendingCommands = CommandPoolAndBuffer();
mFencesInFlight.emplace(fence, mLastSubmittedSerial); mFencesInFlight.emplace(fence, mLastSubmittedSerial);
CommandPoolAndBuffer submittedCommands = {mRecordingContext.commandPool,
mRecordingContext.commandBuffer};
mCommandsInFlight.Enqueue(submittedCommands, mLastSubmittedSerial);
mRecordingContext = CommandRecordingContext();
DAWN_TRY(PrepareRecordingContext());
for (VkSemaphore semaphore : mRecordingContext.waitSemaphores) { for (VkSemaphore semaphore : mRecordingContext.waitSemaphores) {
mDeleter->DeleteWhenUnused(semaphore); mDeleter->DeleteWhenUnused(semaphore);
} }
@ -334,8 +322,6 @@ namespace dawn_native { namespace vulkan {
mDeleter->DeleteWhenUnused(semaphore); mDeleter->DeleteWhenUnused(semaphore);
} }
mRecordingContext = CommandRecordingContext();
return {}; return {};
} }
@ -461,9 +447,11 @@ namespace dawn_native { namespace vulkan {
return const_cast<VulkanFunctions*>(&fn); return const_cast<VulkanFunctions*>(&fn);
} }
VkFence Device::GetUnusedFence() { ResultOrError<VkFence> Device::GetUnusedFence() {
if (!mUnusedFences.empty()) { if (!mUnusedFences.empty()) {
VkFence fence = mUnusedFences.back(); VkFence fence = mUnusedFences.back();
DAWN_TRY(CheckVkSuccess(fn.ResetFences(mVkDevice, 1, &fence), "vkResetFences"));
mUnusedFences.pop_back(); mUnusedFences.pop_back();
return fence; return fence;
} }
@ -474,9 +462,8 @@ namespace dawn_native { namespace vulkan {
createInfo.flags = 0; createInfo.flags = 0;
VkFence fence = VK_NULL_HANDLE; VkFence fence = VK_NULL_HANDLE;
if (fn.CreateFence(mVkDevice, &createInfo, nullptr, &fence) != VK_SUCCESS) { DAWN_TRY(CheckVkSuccess(fn.CreateFence(mVkDevice, &createInfo, nullptr, &fence),
ASSERT(false); "vkCreateFence"));
}
return fence; return fence;
} }
@ -495,11 +482,7 @@ namespace dawn_native { namespace vulkan {
return; return;
} }
if (fn.ResetFences(mVkDevice, 1, &fence) != VK_SUCCESS) {
ASSERT(false);
}
mUnusedFences.push_back(fence); mUnusedFences.push_back(fence);
mFencesInFlight.pop(); mFencesInFlight.pop();
ASSERT(fenceSerial > mCompletedSerial); ASSERT(fenceSerial > mCompletedSerial);
@ -507,60 +490,62 @@ namespace dawn_native { namespace vulkan {
} }
} }
Device::CommandPoolAndBuffer Device::GetUnusedCommands() { MaybeError Device::PrepareRecordingContext() {
ASSERT(!mRecordingContext.used);
ASSERT(mRecordingContext.commandBuffer == VK_NULL_HANDLE);
ASSERT(mRecordingContext.commandPool == VK_NULL_HANDLE);
// First try to recycle unused command pools.
if (!mUnusedCommands.empty()) { if (!mUnusedCommands.empty()) {
CommandPoolAndBuffer commands = mUnusedCommands.back(); CommandPoolAndBuffer commands = mUnusedCommands.back();
mUnusedCommands.pop_back(); mUnusedCommands.pop_back();
return commands; DAWN_TRY(CheckVkSuccess(fn.ResetCommandPool(mVkDevice, commands.pool, 0),
"vkResetCommandPool"));
mRecordingContext.commandBuffer = commands.commandBuffer;
mRecordingContext.commandPool = commands.pool;
} else {
// Create a new command pool for our commands and allocate the command buffer.
VkCommandPoolCreateInfo createInfo;
createInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
createInfo.pNext = nullptr;
createInfo.flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT;
createInfo.queueFamilyIndex = mQueueFamily;
DAWN_TRY(CheckVkSuccess(fn.CreateCommandPool(mVkDevice, &createInfo, nullptr,
&mRecordingContext.commandPool),
"vkCreateCommandPool"));
VkCommandBufferAllocateInfo allocateInfo;
allocateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
allocateInfo.pNext = nullptr;
allocateInfo.commandPool = mRecordingContext.commandPool;
allocateInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
allocateInfo.commandBufferCount = 1;
DAWN_TRY(CheckVkSuccess(fn.AllocateCommandBuffers(mVkDevice, &allocateInfo,
&mRecordingContext.commandBuffer),
"vkAllocateCommandBuffers"));
} }
CommandPoolAndBuffer commands; // Start the recording of commands in the command buffer.
VkCommandBufferBeginInfo beginInfo;
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
beginInfo.pNext = nullptr;
beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
beginInfo.pInheritanceInfo = nullptr;
VkCommandPoolCreateInfo createInfo; return CheckVkSuccess(fn.BeginCommandBuffer(mRecordingContext.commandBuffer, &beginInfo),
createInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; "vkBeginCommandBuffer");
createInfo.pNext = nullptr;
createInfo.flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT;
createInfo.queueFamilyIndex = mQueueFamily;
if (fn.CreateCommandPool(mVkDevice, &createInfo, nullptr, &commands.pool) != VK_SUCCESS) {
ASSERT(false);
}
VkCommandBufferAllocateInfo allocateInfo;
allocateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
allocateInfo.pNext = nullptr;
allocateInfo.commandPool = commands.pool;
allocateInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
allocateInfo.commandBufferCount = 1;
if (fn.AllocateCommandBuffers(mVkDevice, &allocateInfo, &commands.commandBuffer) !=
VK_SUCCESS) {
ASSERT(false);
}
return commands;
} }
void Device::RecycleCompletedCommands() { void Device::RecycleCompletedCommands() {
for (auto& commands : mCommandsInFlight.IterateUpTo(mCompletedSerial)) { for (auto& commands : mCommandsInFlight.IterateUpTo(mCompletedSerial)) {
if (fn.ResetCommandPool(mVkDevice, commands.pool, 0) != VK_SUCCESS) {
ASSERT(false);
}
mUnusedCommands.push_back(commands); mUnusedCommands.push_back(commands);
} }
mCommandsInFlight.ClearUpTo(mCompletedSerial); mCommandsInFlight.ClearUpTo(mCompletedSerial);
} }
void Device::FreeCommands(CommandPoolAndBuffer* commands) {
if (commands->pool != VK_NULL_HANDLE) {
fn.DestroyCommandPool(mVkDevice, commands->pool, nullptr);
commands->pool = VK_NULL_HANDLE;
}
// Command buffers are implicitly destroyed when the command pool is.
commands->commandBuffer = VK_NULL_HANDLE;
}
ResultOrError<std::unique_ptr<StagingBufferBase>> Device::CreateStagingBuffer(size_t size) { ResultOrError<std::unique_ptr<StagingBufferBase>> Device::CreateStagingBuffer(size_t size) {
std::unique_ptr<StagingBufferBase> stagingBuffer = std::unique_ptr<StagingBufferBase> stagingBuffer =
std::make_unique<StagingBuffer>(size, this); std::make_unique<StagingBuffer>(size, this);
@ -573,6 +558,8 @@ namespace dawn_native { namespace vulkan {
BufferBase* destination, BufferBase* destination,
uint64_t destinationOffset, uint64_t destinationOffset,
uint64_t size) { uint64_t size) {
CommandRecordingContext* recordingContext = GetPendingRecordingContext();
// Insert memory barrier to ensure host write operations are made visible before // Insert memory barrier to ensure host write operations are made visible before
// copying from the staging buffer. However, this barrier can be removed (see note below). // copying from the staging buffer. However, this barrier can be removed (see note below).
// //
@ -582,15 +569,15 @@ namespace dawn_native { namespace vulkan {
// Insert pipeline barrier to ensure correct ordering with previous memory operations on the // Insert pipeline barrier to ensure correct ordering with previous memory operations on the
// buffer. // buffer.
ToBackend(destination) ToBackend(destination)->TransitionUsageNow(recordingContext, dawn::BufferUsage::CopyDst);
->TransitionUsageNow(GetPendingRecordingContext(), dawn::BufferUsage::CopyDst);
VkBufferCopy copy; VkBufferCopy copy;
copy.srcOffset = sourceOffset; copy.srcOffset = sourceOffset;
copy.dstOffset = destinationOffset; copy.dstOffset = destinationOffset;
copy.size = size; copy.size = size;
this->fn.CmdCopyBuffer(GetPendingCommandBuffer(), ToBackend(source)->GetBufferHandle(), this->fn.CmdCopyBuffer(recordingContext->commandBuffer,
ToBackend(source)->GetBufferHandle(),
ToBackend(destination)->GetHandle(), 1, &copy); ToBackend(destination)->GetHandle(), 1, &copy);
return {}; return {};

View File

@ -64,7 +64,6 @@ namespace dawn_native { namespace vulkan {
MemoryAllocator* GetMemoryAllocator() const; MemoryAllocator* GetMemoryAllocator() const;
RenderPassCache* GetRenderPassCache() const; RenderPassCache* GetRenderPassCache() const;
VkCommandBuffer GetPendingCommandBuffer();
CommandRecordingContext* GetPendingRecordingContext(); CommandRecordingContext* GetPendingRecordingContext();
Serial GetPendingCommandSerial() const override; Serial GetPendingCommandSerial() const override;
MaybeError SubmitPendingCommands(); MaybeError SubmitPendingCommands();
@ -144,7 +143,7 @@ namespace dawn_native { namespace vulkan {
std::unique_ptr<external_memory::Service> mExternalMemoryService; std::unique_ptr<external_memory::Service> mExternalMemoryService;
std::unique_ptr<external_semaphore::Service> mExternalSemaphoreService; std::unique_ptr<external_semaphore::Service> mExternalSemaphoreService;
VkFence GetUnusedFence(); ResultOrError<VkFence> GetUnusedFence();
void CheckPassedFences(); void CheckPassedFences();
// We track which operations are in flight on the GPU with an increasing serial. // We track which operations are in flight on the GPU with an increasing serial.
@ -152,22 +151,22 @@ namespace dawn_native { namespace vulkan {
// to a serial and a fence, such that when the fence is "ready" we know the operations // to a serial and a fence, such that when the fence is "ready" we know the operations
// have finished. // have finished.
std::queue<std::pair<VkFence, Serial>> mFencesInFlight; std::queue<std::pair<VkFence, Serial>> mFencesInFlight;
// Fences in the unused list aren't reset yet.
std::vector<VkFence> mUnusedFences; std::vector<VkFence> mUnusedFences;
Serial mCompletedSerial = 0; Serial mCompletedSerial = 0;
Serial mLastSubmittedSerial = 0; Serial mLastSubmittedSerial = 0;
MaybeError PrepareRecordingContext();
void RecycleCompletedCommands();
struct CommandPoolAndBuffer { struct CommandPoolAndBuffer {
VkCommandPool pool = VK_NULL_HANDLE; VkCommandPool pool = VK_NULL_HANDLE;
VkCommandBuffer commandBuffer = VK_NULL_HANDLE; VkCommandBuffer commandBuffer = VK_NULL_HANDLE;
}; };
CommandPoolAndBuffer GetUnusedCommands();
void RecycleCompletedCommands();
void FreeCommands(CommandPoolAndBuffer* commands);
SerialQueue<CommandPoolAndBuffer> mCommandsInFlight; SerialQueue<CommandPoolAndBuffer> mCommandsInFlight;
// Command pools in the unused list haven't been reset yet.
std::vector<CommandPoolAndBuffer> mUnusedCommands; std::vector<CommandPoolAndBuffer> mUnusedCommands;
CommandPoolAndBuffer mPendingCommands; // There is always a valid recording context stored in mRecordingContext
CommandRecordingContext mRecordingContext; CommandRecordingContext mRecordingContext;
MaybeError ImportExternalImage(const ExternalImageDescriptor* descriptor, MaybeError ImportExternalImage(const ExternalImageDescriptor* descriptor,

View File

@ -132,7 +132,7 @@ namespace dawn_native { namespace vulkan {
// Do the initial layout transition for all these images from an undefined layout to // Do the initial layout transition for all these images from an undefined layout to
// present so that it matches the "present" usage after the first GetNextTexture. // present so that it matches the "present" usage after the first GetNextTexture.
VkCommandBuffer commands = mDevice->GetPendingCommandBuffer(); CommandRecordingContext* recordingContext = mDevice->GetPendingRecordingContext();
for (VkImage image : mSwapChainImages) { for (VkImage image : mSwapChainImages) {
VkImageMemoryBarrier barrier; VkImageMemoryBarrier barrier;
barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
@ -150,9 +150,9 @@ namespace dawn_native { namespace vulkan {
barrier.subresourceRange.baseArrayLayer = 0; barrier.subresourceRange.baseArrayLayer = 0;
barrier.subresourceRange.layerCount = 1; barrier.subresourceRange.layerCount = 1;
mDevice->fn.CmdPipelineBarrier(commands, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, mDevice->fn.CmdPipelineBarrier(
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0, 0, nullptr, 0, recordingContext->commandBuffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
nullptr, 1, &barrier); VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier);
} }
if (oldSwapchain != VK_NULL_HANDLE) { if (oldSwapchain != VK_NULL_HANDLE) {