| // Copyright (c) 2019 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "gpu/vulkan/vulkan_fence_helper.h" |
| |
| #include "base/bind.h" |
| #include "gpu/vulkan/vulkan_device_queue.h" |
| #include "gpu/vulkan/vulkan_function_pointers.h" |
| |
| namespace gpu { |
| |
| VulkanFenceHelper::FenceHandle::FenceHandle() = default; |
| VulkanFenceHelper::FenceHandle::FenceHandle(VkFence fence, |
| uint64_t generation_id) |
| : fence_(fence), generation_id_(generation_id) {} |
| VulkanFenceHelper::FenceHandle::FenceHandle(const FenceHandle& other) = default; |
| VulkanFenceHelper::FenceHandle& VulkanFenceHelper::FenceHandle::operator=( |
| const FenceHandle& other) = default; |
| |
| VulkanFenceHelper::VulkanFenceHelper(VulkanDeviceQueue* device_queue) |
| : device_queue_(device_queue) {} |
| |
| VulkanFenceHelper::~VulkanFenceHelper() { |
| PerformImmediateCleanup(); |
| } |
| |
| // TODO(ericrk): Handle recycling fences. |
| VkResult VulkanFenceHelper::GetFence(VkFence* fence) { |
| VkFenceCreateInfo create_info{ |
| .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, |
| .pNext = nullptr, |
| .flags = 0, |
| }; |
| return vkCreateFence(device_queue_->GetVulkanDevice(), &create_info, |
| nullptr /* pAllocator */, fence); |
| } |
| |
| VulkanFenceHelper::FenceHandle VulkanFenceHelper::EnqueueFence(VkFence fence) { |
| FenceHandle handle(fence, next_generation_++); |
| cleanup_tasks_.emplace(handle, std::move(tasks_pending_fence_)); |
| tasks_pending_fence_ = std::vector<CleanupTask>(); |
| |
| return handle; |
| } |
| |
| bool VulkanFenceHelper::Wait(FenceHandle handle, |
| uint64_t timeout_in_nanoseconds) { |
| if (HasPassed(handle)) |
| return true; |
| |
| VkResult result = |
| vkWaitForFences(device_queue_->GetVulkanDevice(), 1, &handle.fence_, true, |
| timeout_in_nanoseconds); |
| |
| // After waiting, we can process cleanup tasks. |
| ProcessCleanupTasks(); |
| |
| return result == VK_SUCCESS; |
| } |
| |
| bool VulkanFenceHelper::HasPassed(FenceHandle handle) { |
| // Process cleanup tasks which advances our |current_generation_|. |
| ProcessCleanupTasks(); |
| |
| return current_generation_ >= handle.generation_id_; |
| } |
| |
| void VulkanFenceHelper::EnqueueCleanupTaskForSubmittedWork(CleanupTask task) { |
| tasks_pending_fence_.emplace_back(std::move(task)); |
| } |
| |
| void VulkanFenceHelper::ProcessCleanupTasks() { |
| VkDevice device = device_queue_->GetVulkanDevice(); |
| |
| // Iterate over our pending cleanup fences / tasks, advancing |
| // |current_generation_| as far as possible. This assumes that fences pass in |
| // order, which isn't a hard API guarantee, but should be close enough / |
| // efficient enough for the purpose or processing cleanup tasks. |
| // |
| // Also runs any cleanup tasks for generations that have passed. Create a |
| // temporary vector of tasks to run to avoid reentrancy issues. |
| std::vector<CleanupTask> tasks_to_run; |
| while (!cleanup_tasks_.empty()) { |
| TasksForFence& tasks_for_fence = cleanup_tasks_.front(); |
| VkResult result = vkGetFenceStatus(device, tasks_for_fence.handle.fence_); |
| if (result == VK_NOT_READY) |
| break; |
| if (result != VK_SUCCESS) { |
| PerformImmediateCleanup(); |
| return; |
| } |
| current_generation_ = tasks_for_fence.handle.generation_id_; |
| vkDestroyFence(device, tasks_for_fence.handle.fence_, nullptr); |
| |
| tasks_to_run.insert(tasks_to_run.end(), |
| std::make_move_iterator(tasks_for_fence.tasks.begin()), |
| std::make_move_iterator(tasks_for_fence.tasks.end())); |
| cleanup_tasks_.pop(); |
| } |
| |
| for (auto& task : tasks_to_run) |
| std::move(task).Run(device_queue_, false /* device_lost */); |
| } |
| |
| VulkanFenceHelper::FenceHandle VulkanFenceHelper::GenerateCleanupFence() { |
| if (tasks_pending_fence_.empty()) |
| return FenceHandle(); |
| |
| VkFence fence = VK_NULL_HANDLE; |
| VkResult result = GetFence(&fence); |
| if (result != VK_SUCCESS) { |
| PerformImmediateCleanup(); |
| return FenceHandle(); |
| } |
| result = vkQueueSubmit(device_queue_->GetVulkanQueue(), 0, nullptr, fence); |
| if (result != VK_SUCCESS) { |
| vkDestroyFence(device_queue_->GetVulkanDevice(), fence, nullptr); |
| PerformImmediateCleanup(); |
| return FenceHandle(); |
| } |
| |
| return EnqueueFence(fence); |
| } |
| |
| void VulkanFenceHelper::EnqueueSemaphoreCleanupForSubmittedWork( |
| VkSemaphore semaphore) { |
| if (semaphore == VK_NULL_HANDLE) |
| return; |
| |
| EnqueueSemaphoresCleanupForSubmittedWork({semaphore}); |
| } |
| |
| void VulkanFenceHelper::EnqueueSemaphoresCleanupForSubmittedWork( |
| std::vector<VkSemaphore> semaphores) { |
| if (semaphores.empty()) |
| return; |
| |
| EnqueueCleanupTaskForSubmittedWork(base::BindOnce( |
| [](std::vector<VkSemaphore> semaphores, VulkanDeviceQueue* device_queue, |
| bool /* is_lost */) { |
| for (VkSemaphore semaphore : semaphores) { |
| vkDestroySemaphore(device_queue->GetVulkanDevice(), semaphore, |
| nullptr); |
| } |
| }, |
| std::move(semaphores))); |
| } |
| |
| void VulkanFenceHelper::EnqueueImageCleanupForSubmittedWork( |
| VkImage image, |
| VkDeviceMemory memory) { |
| if (image == VK_NULL_HANDLE && memory == VK_NULL_HANDLE) |
| return; |
| |
| EnqueueCleanupTaskForSubmittedWork(base::BindOnce( |
| [](VkImage image, VkDeviceMemory memory, VulkanDeviceQueue* device_queue, |
| bool /* is_lost */) { |
| if (image != VK_NULL_HANDLE) |
| vkDestroyImage(device_queue->GetVulkanDevice(), image, nullptr); |
| if (memory != VK_NULL_HANDLE) |
| vkFreeMemory(device_queue->GetVulkanDevice(), memory, nullptr); |
| }, |
| image, memory)); |
| } |
| |
| void VulkanFenceHelper::PerformImmediateCleanup() { |
| // Rather than caring about fences, just wait for queue idle if possible. |
| VkResult result = vkQueueWaitIdle(device_queue_->GetVulkanQueue()); |
| // Wait can only fail for three reasons - device loss, host OOM, device OOM. |
| // If we hit an OOM, treat this as a crash. There isn't a great way to |
| // recover from this. |
| CHECK(result == VK_SUCCESS || result == VK_ERROR_DEVICE_LOST); |
| bool device_lost = result == VK_ERROR_DEVICE_LOST; |
| |
| // Run all cleanup tasks. Create a temporary vector of tasks to run to avoid |
| // reentrancy issues. |
| std::vector<CleanupTask> tasks_to_run; |
| tasks_to_run.insert(tasks_to_run.end(), |
| std::make_move_iterator(tasks_pending_fence_.begin()), |
| std::make_move_iterator(tasks_pending_fence_.end())); |
| tasks_pending_fence_.clear(); |
| while (!cleanup_tasks_.empty()) { |
| auto& tasks_for_fence = cleanup_tasks_.front(); |
| vkDestroyFence(device_queue_->GetVulkanDevice(), |
| tasks_for_fence.handle.fence_, nullptr); |
| tasks_to_run.insert(tasks_to_run.end(), |
| std::make_move_iterator(tasks_for_fence.tasks.begin()), |
| std::make_move_iterator(tasks_for_fence.tasks.end())); |
| cleanup_tasks_.pop(); |
| } |
| for (auto& task : tasks_to_run) |
| std::move(task).Run(device_queue_, device_lost); |
| } |
| |
| VulkanFenceHelper::TasksForFence::TasksForFence(FenceHandle handle, |
| std::vector<CleanupTask> tasks) |
| : handle(handle), tasks(std::move(tasks)) {} |
| VulkanFenceHelper::TasksForFence::~TasksForFence() = default; |
| VulkanFenceHelper::TasksForFence::TasksForFence(TasksForFence&& other) = |
| default; |
| VulkanFenceHelper::TasksForFence& |
| VulkanFenceHelper::TasksForFence::TasksForFence::operator=( |
| TasksForFence&& other) = default; |
| |
| } // namespace gpu |