blob: 258cfb9a7c19d90555d080dd4068dfb349e02776 [file] [log] [blame] [edit]
// Copyright 2018 The Dawn & Tint Authors
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "dawn/native/vulkan/QueueVk.h"
#include <limits>
#include <optional>
#include <utility>
#include "dawn/common/Math.h"
#include "dawn/native/Buffer.h"
#include "dawn/native/CommandValidation.h"
#include "dawn/native/Commands.h"
#include "dawn/native/DynamicUploader.h"
#include "dawn/native/vulkan/CommandBufferVk.h"
#include "dawn/native/vulkan/CommandRecordingContextVk.h"
#include "dawn/native/vulkan/DeviceVk.h"
#include "dawn/native/vulkan/FencedDeleter.h"
#include "dawn/native/vulkan/TextureVk.h"
#include "dawn/native/vulkan/UniqueVkHandle.h"
#include "dawn/native/vulkan/UtilsVulkan.h"
#include "dawn/native/vulkan/VulkanError.h"
#include "dawn/platform/DawnPlatform.h"
#include "dawn/platform/tracing/TraceEvent.h"
#include "partition_alloc/pointers/raw_ptr.h"
namespace dawn::native::vulkan {
namespace {
// 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);
}
}
} // anonymous namespace
// static
ResultOrError<Ref<Queue>> Queue::Create(Device* device,
const QueueDescriptor* descriptor,
uint32_t family) {
Ref<Queue> queue = AcquireRef(new Queue(device, descriptor, family));
DAWN_TRY(queue->Initialize());
return queue;
}
Queue::Queue(Device* device, const QueueDescriptor* descriptor, uint32_t family)
: QueueBase(device, descriptor), mQueueFamily(family) {}
Queue::~Queue() {}
MaybeError Queue::Initialize() {
Device* device = ToBackend(GetDevice());
device->fn.GetDeviceQueue(device->GetVkDevice(), mQueueFamily, 0, &mQueue);
DAWN_TRY(PrepareRecordingContext());
SetLabelImpl();
return {};
}
MaybeError Queue::SubmitImpl(uint32_t commandCount, CommandBufferBase* const* commands) {
TRACE_EVENT_BEGIN0(GetDevice()->GetPlatform(), Recording, "CommandBufferVk::RecordCommands");
CommandRecordingContext* recordingContext = GetPendingRecordingContext();
for (uint32_t i = 0; i < commandCount; ++i) {
DAWN_TRY(ToBackend(commands[i])->RecordCommands(recordingContext));
}
TRACE_EVENT_END0(GetDevice()->GetPlatform(), Recording, "CommandBufferVk::RecordCommands");
DAWN_TRY(SubmitPendingCommandsImpl());
return {};
}
void Queue::SetLabelImpl() {
Device* device = ToBackend(GetDevice());
// TODO(crbug.com/dawn/1344): When we start using multiple queues this needs to be adjusted
// so it doesn't always change the default queue's label.
SetDebugName(device, VK_OBJECT_TYPE_QUEUE, mQueue, "Dawn_Queue", GetLabel());
}
bool Queue::HasPendingCommands() const {
return mRecordingContext.needsSubmit;
}
VkQueue Queue::GetVkQueue() const {
return mQueue;
}
ResultOrError<ExecutionSerial> Queue::CheckAndUpdateCompletedSerials() {
// TODO(crbug.com/40643114): Revisit whether this lock is needed for this backend.
auto deviceGuard = GetDevice()->GetGuard();
Device* device = ToBackend(GetDevice());
return mFencesInFlight.Use([&](auto fencesInFlight) -> ResultOrError<ExecutionSerial> {
ExecutionSerial fenceSerial(0);
while (!fencesInFlight->empty()) {
VkFence fence = fencesInFlight->front().first;
ExecutionSerial tentativeSerial = fencesInFlight->front().second;
VkResult result = VkResult::WrapUnsafe(INJECT_ERROR_OR_RUN(
device->fn.GetFenceStatus(device->GetVkDevice(), fence), VK_ERROR_DEVICE_LOST));
// Fence are added in order, so we can stop searching as soon
// as we see one that's not ready.
if (result == VK_NOT_READY) {
return fenceSerial;
} else {
DAWN_TRY(CheckVkSuccess(::VkResult(result), "GetFenceStatus"));
}
// Update fenceSerial since fence is ready.
fenceSerial = tentativeSerial;
mUnusedFences->push_back(fence);
fencesInFlight->pop_front();
}
return fenceSerial;
});
}
void Queue::ForceEventualFlushOfCommands() {
mRecordingContext.needsSubmit |= mRecordingContext.used;
}
MaybeError Queue::WaitForIdleForDestructionImpl() {
// Immediately tag the recording context as unused so we don't try to submit it in Tick.
// Move the mRecordingContext.used to mUnusedCommands so it can be cleaned up in
// ShutDownImpl
if (mRecordingContext.used) {
CommandPoolAndBuffer commands = {mRecordingContext.commandPool,
mRecordingContext.commandBuffer};
mUnusedCommands->push_back(commands);
mRecordingContext = CommandRecordingContext();
}
Device* device = ToBackend(GetDevice());
// Ignore the result of QueueWaitIdle: it can return OOM which we can't really do anything
// about, Device lost, which means workloads running on the GPU are no longer accessible
// (so they are as good as waited on) or success.
[[maybe_unused]] VkResult waitIdleResult =
VkResult::WrapUnsafe(device->fn.QueueWaitIdle(mQueue));
DAWN_TRY(WaitForQueueSerial(GetLastSubmittedCommandSerial(),
std::numeric_limits<Nanoseconds>::max()));
return {};
}
CommandRecordingContext* Queue::GetPendingRecordingContext(SubmitMode submitMode) {
DAWN_ASSERT(mRecordingContext.commandBuffer != VK_NULL_HANDLE);
mRecordingContext.needsSubmit |= (submitMode == SubmitMode::Normal);
mRecordingContext.used = true;
return &mRecordingContext;
}
MaybeError Queue::PrepareRecordingContext() {
DAWN_ASSERT(!mRecordingContext.needsSubmit);
DAWN_ASSERT(mRecordingContext.commandBuffer == VK_NULL_HANDLE);
DAWN_ASSERT(mRecordingContext.commandPool == VK_NULL_HANDLE);
CommandPoolAndBuffer commands;
DAWN_TRY_ASSIGN(commands, BeginVkCommandBuffer());
mRecordingContext.commandBuffer = commands.commandBuffer;
mRecordingContext.commandPool = commands.pool;
mRecordingContext.commandBufferList.push_back(commands.commandBuffer);
mRecordingContext.commandPoolList.push_back(commands.pool);
return {};
}
// Splits the recording context, ending the current command buffer and beginning a new one.
// This should not be necessary in most cases, and is provided only to work around driver issues
// on some hardware.
MaybeError Queue::SplitRecordingContext(CommandRecordingContext* recordingContext) {
DAWN_ASSERT(recordingContext->used);
Device* device = ToBackend(GetDevice());
DAWN_TRY(CheckVkSuccess(device->fn.EndCommandBuffer(recordingContext->commandBuffer),
"vkEndCommandBuffer"));
CommandPoolAndBuffer commands;
DAWN_TRY_ASSIGN(commands, BeginVkCommandBuffer());
recordingContext->commandBuffer = commands.commandBuffer;
recordingContext->commandPool = commands.pool;
recordingContext->commandBufferList.push_back(commands.commandBuffer);
recordingContext->commandPoolList.push_back(commands.pool);
recordingContext->hasRecordedRenderPass = false;
return {};
}
ResultOrError<CommandPoolAndBuffer> Queue::BeginVkCommandBuffer() {
Device* device = ToBackend(GetDevice());
VkDevice vkDevice = device->GetVkDevice();
// First try to recycle unused command pools.
auto result =
mUnusedCommands.Use([&](auto unusedCommands) -> std::optional<CommandPoolAndBuffer> {
if (!unusedCommands->empty()) {
CommandPoolAndBuffer recycledCommands = unusedCommands->back();
unusedCommands->pop_back();
return recycledCommands;
}
return std::nullopt;
});
CommandPoolAndBuffer commands;
if (result) {
commands = *result;
} 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(
device->fn.CreateCommandPool(vkDevice, &createInfo, nullptr, &*commands.pool),
"vkCreateCommandPool"));
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;
DAWN_TRY_WITH_CLEANUP(CheckVkSuccess(device->fn.AllocateCommandBuffers(
vkDevice, &allocateInfo, &commands.commandBuffer),
"vkAllocateCommandBuffers"),
{ DestroyCommandPoolAndBuffer(device->fn, vkDevice, 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;
DAWN_TRY_WITH_CLEANUP(
CheckVkSuccess(device->fn.BeginCommandBuffer(commands.commandBuffer, &beginInfo),
"vkBeginCommandBuffer"),
{ DestroyCommandPoolAndBuffer(device->fn, vkDevice, commands); });
return commands;
}
MaybeError Queue::SubmitPendingCommandsImpl() {
if (!mRecordingContext.needsSubmit) {
return {};
}
Device* device = ToBackend(GetDevice());
if (!mRecordingContext.mappableBuffersForEagerTransition.empty()) {
// Transition mappable buffers back to map usages with the submit.
Buffer::TransitionMappableBuffersEagerly(
device, &mRecordingContext, mRecordingContext.mappableBuffersForEagerTransition);
}
for (auto texture : mRecordingContext.specialSyncTextures) {
DAWN_TRY(texture->OnBeforeSubmit(&mRecordingContext));
}
DAWN_TRY(CheckVkSuccess(device->fn.EndCommandBuffer(mRecordingContext.commandBuffer),
"vkEndCommandBuffer"));
std::vector<VkPipelineStageFlags> dstStageMasks(mRecordingContext.waitSemaphores.size(),
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT);
VkSubmitInfo submitInfo;
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
submitInfo.pNext = nullptr;
submitInfo.waitSemaphoreCount = static_cast<uint32_t>(mRecordingContext.waitSemaphores.size());
submitInfo.pWaitSemaphores = AsVkArray(mRecordingContext.waitSemaphores.data());
submitInfo.pWaitDstStageMask = dstStageMasks.data();
submitInfo.commandBufferCount = mRecordingContext.commandBufferList.size();
submitInfo.pCommandBuffers = mRecordingContext.commandBufferList.data();
submitInfo.signalSemaphoreCount = mRecordingContext.signalSemaphores.size();
submitInfo.pSignalSemaphores = AsVkArray(mRecordingContext.signalSemaphores.data());
VkFence fence = VK_NULL_HANDLE;
DAWN_TRY_ASSIGN(fence, GetUnusedFence());
TRACE_EVENT_BEGIN0(device->GetPlatform(), Recording, "vkQueueSubmit");
DAWN_TRY_WITH_CLEANUP(
CheckVkSuccess(device->fn.QueueSubmit(mQueue, 1, &submitInfo, fence), "vkQueueSubmit"), {
// If submitting to the queue fails, move the fence back into the unused fence
// list, as if it were never acquired. Not doing so would leak the fence since
// it would be neither in the unused list nor in the in-flight list.
mUnusedFences->push_back(fence);
});
TRACE_EVENT_END0(device->GetPlatform(), Recording, "vkQueueSubmit");
// Enqueue the semaphores before incrementing the serial, so that they can be deleted as
// soon as the current submission is finished.
for (VkSemaphore semaphore : mRecordingContext.waitSemaphores) {
device->GetFencedDeleter()->DeleteWhenUnused(semaphore);
}
IncrementLastSubmittedCommandSerial();
ExecutionSerial lastSubmittedSerial = GetLastSubmittedCommandSerial();
mFencesInFlight->emplace_back(fence, lastSubmittedSerial);
for (size_t i = 0; i < mRecordingContext.commandBufferList.size(); ++i) {
CommandPoolAndBuffer commands = {mRecordingContext.commandPoolList[i],
mRecordingContext.commandBufferList[i]};
TrackSerialTask(lastSubmittedSerial, [commands, this]() {
Device* device = ToBackend(GetDevice());
VkDevice vkDevice = device->GetVkDevice();
MaybeError result = CheckVkSuccess(
device->fn.ResetCommandPool(vkDevice, commands.pool, 0), "vkResetCommandPool");
if (result.IsError()) {
result.AcquireError();
DestroyCommandPoolAndBuffer(device->fn, vkDevice, commands);
return;
}
mUnusedCommands->push_back(commands);
});
}
for (auto texture : mRecordingContext.specialSyncTextures) {
DAWN_TRY(texture->OnAfterSubmit());
}
mRecordingContext = CommandRecordingContext();
DAWN_TRY(PrepareRecordingContext());
return {};
}
ResultOrError<VkFence> Queue::GetUnusedFence() {
Device* device = ToBackend(GetDevice());
VkDevice vkDevice = device->GetVkDevice();
auto result =
mUnusedFences.Use([&](auto unusedFences) -> std::optional<ResultOrError<VkFence>> {
if (!unusedFences->empty()) {
VkFence fence = unusedFences->back();
DAWN_ASSERT(fence != VK_NULL_HANDLE);
DAWN_TRY(
CheckVkSuccess(device->fn.ResetFences(vkDevice, 1, &*fence), "vkResetFences"));
unusedFences->pop_back();
return fence;
}
return std::nullopt;
});
if (result) {
return std::move(*result);
}
VkFenceCreateInfo createInfo;
createInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
createInfo.pNext = nullptr;
createInfo.flags = 0;
VkFence fence = VK_NULL_HANDLE;
DAWN_TRY(CheckVkSuccess(device->fn.CreateFence(vkDevice, &createInfo, nullptr, &*fence),
"vkCreateFence"));
return fence;
}
void Queue::DestroyImpl() {
Device* device = ToBackend(GetDevice());
VkDevice vkDevice = device->GetVkDevice();
// 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) {
DestroyCommandPoolAndBuffer(
device->fn, vkDevice, {mRecordingContext.commandPool, mRecordingContext.commandBuffer});
}
for (VkSemaphore semaphore : mRecordingContext.waitSemaphores) {
device->fn.DestroySemaphore(vkDevice, semaphore, nullptr);
}
mRecordingContext.waitSemaphores.clear();
mRecordingContext.signalSemaphores.clear();
// Free any recycled command pools.
mUnusedCommands.Use([&](auto unusedCommands) {
for (const CommandPoolAndBuffer& commands : *unusedCommands) {
DestroyCommandPoolAndBuffer(device->fn, vkDevice, commands);
}
unusedCommands->clear();
});
// Some fences might still be marked as in-flight if we shut down because of a device loss.
// Delete them since at this point all commands are complete.
mFencesInFlight.Use([&](auto fencesInFlight) {
while (!fencesInFlight->empty()) {
device->fn.DestroyFence(vkDevice, *fencesInFlight->front().first, nullptr);
fencesInFlight->pop_front();
}
});
mUnusedFences.Use([&](auto unusedFences) {
for (VkFence fence : *unusedFences) {
device->fn.DestroyFence(vkDevice, fence, nullptr);
}
unusedFences->clear();
});
QueueBase::DestroyImpl();
}
ResultOrError<ExecutionSerial> Queue::WaitForQueueSerialImpl(ExecutionSerial waitSerial,
Nanoseconds timeout) {
Device* device = ToBackend(GetDevice());
VkDevice vkDevice = device->GetVkDevice();
// If the client has passed a finite timeout, the function will eventually return due to
// either (1) the fences being signaled, (2) the timeout being reached, or (3) the device
// being lost. If the client has passed an infinite timeout, this function might hang forever
// if the fences are never signaled (which matches the semantics that the client has
// specified).
// TODO(crbug.com/344798087): Handle the issue of timeouts in a more general way further up the
// stack.
while (1) {
ExecutionSerial completedSerial = kWaitSerialTimeout;
VkResult waitResult = mFencesInFlight.Use([&](auto fencesInFlight) {
// Search from for the first fence >= serial.
VkFence waitFence = VK_NULL_HANDLE;
for (auto it = fencesInFlight->begin(); it != fencesInFlight->end(); ++it) {
if (it->second >= waitSerial) {
waitFence = it->first;
completedSerial = it->second;
break;
}
}
if (waitFence == VK_NULL_HANDLE) {
// Fence not found. This serial must have already completed.
// Return a VK_SUCCESS status.
completedSerial = waitSerial;
return VkResult::WrapUnsafe(VK_SUCCESS);
}
// Wait for the fence.
if (GetDevice()->GetState() == Device::State::Disconnected) [[unlikely]] {
// If WaitForQueueSerialImpl is called while we are Disconnected, it means that
// the device lost came from the ErrorInjector and we need to wait without allowing
// any more error to be injected. This is because the device lost was "fake" and
// commands might still be running.
return VkResult::WrapUnsafe(device->fn.WaitForFences(
vkDevice, 1, &*waitFence, true, static_cast<uint64_t>(timeout)));
}
return VkResult::WrapUnsafe(
INJECT_ERROR_OR_RUN(device->fn.WaitForFences(vkDevice, 1, &*waitFence, true,
static_cast<uint64_t>(timeout)),
VK_ERROR_DEVICE_LOST));
});
if (waitResult == VK_TIMEOUT) {
// There is evidence that `VK_TIMEOUT` can get returned even when the
// client has specified an infinite timeout (e.g., due to signals). Retry
// waiting on the fence in this case in order to satisfy the semantics
// that the function should return only when either (a) the fences are
// signaled or (b) the passed-in timeout is reached. Note that this can
// result in this function busy-looping forever in this case, but the
// client has explicitly requested this behavior by passing in an infinite
// timeout.
// TODO(crbug.com/344798087): Handle the issue of timeouts in a more general way further
// up the stack.
if (static_cast<uint64_t>(timeout) == std::numeric_limits<uint64_t>::max()) {
continue;
}
return kWaitSerialTimeout;
}
DAWN_TRY(CheckVkSuccess(::VkResult(waitResult), "vkWaitForFences"));
return completedSerial;
}
}
} // namespace dawn::native::vulkan