| // 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 "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/CommandRecordingContext.h" | |
| #include "dawn/native/vulkan/DeviceVk.h" | |
| #include "dawn/native/vulkan/FencedDeleter.h" | |
| #include "dawn/native/vulkan/SharedFenceVk.h" | |
| #include "dawn/native/vulkan/TextureVk.h" | |
| #include "dawn/native/vulkan/UtilsVulkan.h" | |
| #include "dawn/native/vulkan/VulkanError.h" | |
| #include "dawn/platform/DawnPlatform.h" | |
| #include "dawn/platform/tracing/TraceEvent.h" | |
| namespace dawn::native::vulkan { | |
| namespace { | |
| // Destroy the semaphore when out of scope. | |
| class ScopedSignalSemaphore : public NonCopyable { | |
| public: | |
| ScopedSignalSemaphore(Device* device, VkSemaphore semaphore) | |
| : mDevice(device), mSemaphore(semaphore) {} | |
| ScopedSignalSemaphore(ScopedSignalSemaphore&& other) | |
| : mDevice(other.mDevice), mSemaphore(std::exchange(other.mSemaphore, VK_NULL_HANDLE)) {} | |
| ~ScopedSignalSemaphore() { | |
| if (mSemaphore != VK_NULL_HANDLE) { | |
| mDevice->GetFencedDeleter()->DeleteWhenUnused(mSemaphore); | |
| } | |
| } | |
| VkSemaphore Get() { return mSemaphore; } | |
| VkSemaphore* InitializeInto() { return &mSemaphore; } | |
| private: | |
| Device* mDevice = nullptr; | |
| 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); | |
| } | |
| } | |
| } // 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(SubmitPendingCommands()); | |
| 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() { | |
| 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); | |
| DAWN_ASSERT(fenceSerial > GetCompletedCommandSerial()); | |
| fencesInFlight->pop_front(); | |
| } | |
| return fenceSerial; | |
| }); | |
| } | |
| void Queue::ForceEventualFlushOfCommands() { | |
| mRecordingContext.needsSubmit |= mRecordingContext.used; | |
| } | |
| MaybeError Queue::WaitForIdleForDestruction() { | |
| // 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()); | |
| VkDevice vkDevice = device->GetVkDevice(); | |
| VkResult waitIdleResult = VkResult::WrapUnsafe(device->fn.QueueWaitIdle(mQueue)); | |
| // 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. | |
| DAWN_UNUSED(waitIdleResult); | |
| // Make sure all fences are complete by explicitly waiting on them all | |
| mFencesInFlight.Use([&](auto fencesInFlight) { | |
| while (!fencesInFlight->empty()) { | |
| VkFence fence = fencesInFlight->front().first; | |
| ExecutionSerial fenceSerial = fencesInFlight->front().second; | |
| DAWN_ASSERT(fenceSerial > GetCompletedCommandSerial()); | |
| VkResult result = VkResult::WrapUnsafe(VK_TIMEOUT); | |
| do { | |
| // If WaitForIdleForDesctruction 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. | |
| if (GetDevice()->GetState() == Device::State::Disconnected) { | |
| result = VkResult::WrapUnsafe( | |
| device->fn.WaitForFences(vkDevice, 1, &*fence, true, UINT64_MAX)); | |
| continue; | |
| } | |
| result = VkResult::WrapUnsafe(INJECT_ERROR_OR_RUN( | |
| device->fn.WaitForFences(vkDevice, 1, &*fence, true, UINT64_MAX), | |
| VK_ERROR_DEVICE_LOST)); | |
| } while (result == VK_TIMEOUT); | |
| // Ignore errors from vkWaitForFences: it can be either OOM which we can't do anything | |
| // about (and we need to keep going with the destruction of all fences), or device | |
| // loss, which means the workload on the GPU is no longer accessible and we can | |
| // safely destroy the fence. | |
| device->fn.DestroyFence(vkDevice, fence, nullptr); | |
| fencesInFlight->pop_front(); | |
| } | |
| }); | |
| return {}; | |
| } | |
| CommandRecordingContext* Queue::GetPendingRecordingContext(Device::SubmitMode submitMode) { | |
| DAWN_ASSERT(mRecordingContext.commandBuffer != VK_NULL_HANDLE); | |
| mRecordingContext.needsSubmit |= (submitMode == DeviceBase::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(); | |
| CommandPoolAndBuffer commands; | |
| // First try to recycle unused command pools. | |
| if (!mUnusedCommands.empty()) { | |
| commands = mUnusedCommands.back(); | |
| mUnusedCommands.pop_back(); | |
| DAWN_TRY_WITH_CLEANUP( | |
| CheckVkSuccess(device->fn.ResetCommandPool(vkDevice, commands.pool, 0), | |
| "vkResetCommandPool"), | |
| { DestroyCommandPoolAndBuffer(device->fn, vkDevice, commands); }); | |
| } 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; | |
| } | |
| void Queue::RecycleCompletedCommands(ExecutionSerial completedSerial) { | |
| for (auto& commands : mCommandsInFlight.IterateUpTo(completedSerial)) { | |
| mUnusedCommands.push_back(commands); | |
| } | |
| mCommandsInFlight.ClearUpTo(completedSerial); | |
| } | |
| MaybeError Queue::SubmitPendingCommands() { | |
| 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->fn, &mRecordingContext, mRecordingContext.mappableBuffersForEagerTransition); | |
| } | |
| std::vector<ScopedSignalSemaphore> externalTextureSemaphores; | |
| for (size_t i = 0; i < mRecordingContext.externalTexturesForEagerTransition.size(); ++i) { | |
| // Create an external semaphore for each external textures that have been used in the | |
| // pending submit. | |
| auto& externalTextureSemaphore = | |
| externalTextureSemaphores.emplace_back(device, VK_NULL_HANDLE); | |
| DAWN_TRY_ASSIGN(*externalTextureSemaphore.InitializeInto(), | |
| device->GetExternalSemaphoreService()->CreateExportableSemaphore()); | |
| } | |
| // Transition eagerly all used external textures for export. | |
| for (auto* texture : mRecordingContext.externalTexturesForEagerTransition) { | |
| texture->TransitionEagerlyForExport(&mRecordingContext); | |
| std::vector<VkSemaphore> waitRequirements = texture->AcquireWaitRequirements(); | |
| mRecordingContext.waitSemaphores.insert(mRecordingContext.waitSemaphores.end(), | |
| waitRequirements.begin(), waitRequirements.end()); | |
| SharedTextureMemoryContents* contents = texture->GetSharedTextureMemoryContents(); | |
| if (contents != nullptr) { | |
| SharedTextureMemoryBase::PendingFenceList fences; | |
| contents->AcquirePendingFences(&fences); | |
| for (const auto& fence : fences) { | |
| // All semaphores are binary semaphores. | |
| DAWN_ASSERT(fence.signaledValue == 1u); | |
| ExternalSemaphoreHandle semaphoreHandle = [&]() { | |
| if constexpr (std::is_same_v<ExternalSemaphoreHandle, SystemHandle::Handle>) { | |
| return ToBackend(fence.object)->GetHandle().Get(); | |
| } else { | |
| // TODO(crbug.com/dawn/1745): Remove this path and make the semaphore | |
| // service use SystemHandle. | |
| DAWN_UNREACHABLE(); | |
| return ExternalSemaphoreHandle{}; | |
| } | |
| }(); | |
| VkSemaphore semaphore; | |
| DAWN_TRY_ASSIGN(semaphore, device->GetExternalSemaphoreService()->ImportSemaphore( | |
| semaphoreHandle)); | |
| mRecordingContext.waitSemaphores.push_back(semaphore); | |
| } | |
| } | |
| } | |
| DAWN_TRY(CheckVkSuccess(device->fn.EndCommandBuffer(mRecordingContext.commandBuffer), | |
| "vkEndCommandBuffer")); | |
| std::vector<VkPipelineStageFlags> dstStageMasks(mRecordingContext.waitSemaphores.size(), | |
| VK_PIPELINE_STAGE_ALL_COMMANDS_BIT); | |
| for (auto& externalTextureSemaphore : externalTextureSemaphores) { | |
| mRecordingContext.signalSemaphores.push_back(externalTextureSemaphore.Get()); | |
| } | |
| 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()); | |
| 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); | |
| }); | |
| // 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 submittedCommands = {mRecordingContext.commandPoolList[i], | |
| mRecordingContext.commandBufferList[i]}; | |
| mCommandsInFlight.Enqueue(submittedCommands, lastSubmittedSerial); | |
| } | |
| auto externalTextureSemaphoreIter = externalTextureSemaphores.begin(); | |
| for (auto* texture : mRecordingContext.externalTexturesForEagerTransition) { | |
| // Export the signal semaphore. | |
| ExternalSemaphoreHandle semaphoreHandle; | |
| DAWN_TRY_ASSIGN(semaphoreHandle, device->GetExternalSemaphoreService()->ExportSemaphore( | |
| externalTextureSemaphoreIter->Get())); | |
| ++externalTextureSemaphoreIter; | |
| // Update all external textures, eagerly transitioned in the submit, with the exported | |
| // handles. | |
| texture->UpdateExternalSemaphoreHandle(semaphoreHandle); | |
| } | |
| DAWN_ASSERT(externalTextureSemaphoreIter == externalTextureSemaphores.end()); | |
| mRecordingContext = CommandRecordingContext(); | |
| DAWN_TRY(PrepareRecordingContext()); | |
| return {}; | |
| } | |
| ResultOrError<VkFence> Queue::GetUnusedFence() { | |
| Device* device = ToBackend(GetDevice()); | |
| VkDevice vkDevice = device->GetVkDevice(); | |
| if (!mUnusedFences.empty()) { | |
| VkFence fence = mUnusedFences.back(); | |
| DAWN_TRY(CheckVkSuccess(device->fn.ResetFences(vkDevice, 1, &*fence), "vkResetFences")); | |
| mUnusedFences.pop_back(); | |
| return fence; | |
| } | |
| 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(); | |
| // Some commands might still be marked as in-flight if we shut down because of a device | |
| // loss. Recycle them as unused so that we free them below. | |
| RecycleCompletedCommands(kMaxExecutionSerial); | |
| DAWN_ASSERT(mCommandsInFlight.Empty()); | |
| for (const CommandPoolAndBuffer& commands : mUnusedCommands) { | |
| DestroyCommandPoolAndBuffer(device->fn, vkDevice, commands); | |
| } | |
| mUnusedCommands.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(); | |
| } | |
| }); | |
| for (VkFence fence : mUnusedFences) { | |
| device->fn.DestroyFence(vkDevice, fence, nullptr); | |
| } | |
| mUnusedFences.clear(); | |
| QueueBase::DestroyImpl(); | |
| } | |
| ResultOrError<bool> Queue::WaitForQueueSerial(ExecutionSerial serial, Nanoseconds timeout) { | |
| Device* device = ToBackend(GetDevice()); | |
| VkDevice vkDevice = device->GetVkDevice(); | |
| 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 >= serial) { | |
| waitFence = it->first; | |
| break; | |
| } | |
| } | |
| if (waitFence == VK_NULL_HANDLE) { | |
| // Fence not found. This serial must have already completed. | |
| // Return a VK_SUCCESS status. | |
| DAWN_ASSERT(serial <= GetCompletedCommandSerial()); | |
| return VkResult::WrapUnsafe(VK_SUCCESS); | |
| } | |
| // Wait for the fence. | |
| 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) { | |
| return false; | |
| } | |
| DAWN_TRY(CheckVkSuccess(::VkResult(waitResult), "vkWaitForFences")); | |
| return true; | |
| } | |
| } // namespace dawn::native::vulkan |