vulkan_fence_helper.cc 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. // Copyright (c) 2019 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "gpu/vulkan/vulkan_fence_helper.h"
  5. #include "base/bind.h"
  6. #include "base/logging.h"
  7. #include "gpu/vulkan/vulkan_device_queue.h"
  8. #include "gpu/vulkan/vulkan_function_pointers.h"
  9. namespace gpu {
  10. VulkanFenceHelper::FenceHandle::FenceHandle() = default;
  11. VulkanFenceHelper::FenceHandle::FenceHandle(VkFence fence,
  12. uint64_t generation_id)
  13. : fence_(fence), generation_id_(generation_id) {}
  14. VulkanFenceHelper::FenceHandle::FenceHandle(const FenceHandle& other) = default;
  15. VulkanFenceHelper::FenceHandle& VulkanFenceHelper::FenceHandle::operator=(
  16. const FenceHandle& other) = default;
  17. VulkanFenceHelper::VulkanFenceHelper(VulkanDeviceQueue* device_queue)
  18. : device_queue_(device_queue) {}
  19. VulkanFenceHelper::~VulkanFenceHelper() {
  20. DCHECK(tasks_pending_fence_.empty());
  21. DCHECK(cleanup_tasks_.empty());
  22. }
  23. void VulkanFenceHelper::Destroy() {
  24. PerformImmediateCleanup();
  25. }
  26. // TODO(ericrk): Handle recycling fences.
  27. VkResult VulkanFenceHelper::GetFence(VkFence* fence) {
  28. VkFenceCreateInfo create_info{
  29. .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
  30. .pNext = nullptr,
  31. .flags = 0,
  32. };
  33. return vkCreateFence(device_queue_->GetVulkanDevice(), &create_info,
  34. nullptr /* pAllocator */, fence);
  35. }
  36. VulkanFenceHelper::FenceHandle VulkanFenceHelper::EnqueueFence(VkFence fence) {
  37. FenceHandle handle(fence, next_generation_++);
  38. cleanup_tasks_.emplace_back(handle, std::move(tasks_pending_fence_));
  39. tasks_pending_fence_ = std::vector<CleanupTask>();
  40. return handle;
  41. }
  42. bool VulkanFenceHelper::Wait(FenceHandle handle,
  43. uint64_t timeout_in_nanoseconds) {
  44. if (HasPassed(handle))
  45. return true;
  46. VkResult result =
  47. vkWaitForFences(device_queue_->GetVulkanDevice(), 1, &handle.fence_, true,
  48. timeout_in_nanoseconds);
  49. // After waiting, we can process cleanup tasks.
  50. ProcessCleanupTasks();
  51. return result == VK_SUCCESS;
  52. }
  53. bool VulkanFenceHelper::HasPassed(FenceHandle handle) {
  54. // Process cleanup tasks which advances our |current_generation_|.
  55. ProcessCleanupTasks();
  56. return current_generation_ >= handle.generation_id_;
  57. }
  58. void VulkanFenceHelper::EnqueueCleanupTaskForSubmittedWork(CleanupTask task) {
  59. tasks_pending_fence_.emplace_back(std::move(task));
  60. }
  61. void VulkanFenceHelper::ProcessCleanupTasks(uint64_t retired_generation_id) {
  62. VkDevice device = device_queue_->GetVulkanDevice();
  63. if (!retired_generation_id)
  64. retired_generation_id = current_generation_;
  65. // Iterate over our pending cleanup fences / tasks, advancing
  66. // |current_generation_| as far as possible.
  67. for (const auto& tasks_for_fence : cleanup_tasks_) {
  68. // Callback based tasks have no actual fence to wait on, keep checking
  69. // future fences, as a callback may be delayed.
  70. if (tasks_for_fence.UsingCallback())
  71. continue;
  72. VkResult result = vkGetFenceStatus(device, tasks_for_fence.fence);
  73. if (result == VK_NOT_READY) {
  74. retired_generation_id =
  75. std::min(retired_generation_id, tasks_for_fence.generation_id - 1);
  76. break;
  77. }
  78. if (result == VK_SUCCESS) {
  79. retired_generation_id =
  80. std::max(tasks_for_fence.generation_id, retired_generation_id);
  81. continue;
  82. }
  83. DLOG(ERROR) << "vkGetFenceStatus() failed: " << result;
  84. PerformImmediateCleanup();
  85. return;
  86. }
  87. current_generation_ = retired_generation_id;
  88. // Runs any cleanup tasks for generations that have passed. Create a temporary
  89. // vector of tasks to run to avoid reentrancy issues.
  90. std::vector<CleanupTask> tasks_to_run;
  91. while (!cleanup_tasks_.empty()) {
  92. TasksForFence& tasks_for_fence = cleanup_tasks_.front();
  93. if (tasks_for_fence.generation_id > current_generation_)
  94. break;
  95. if (tasks_for_fence.fence != VK_NULL_HANDLE) {
  96. DCHECK_EQ(vkGetFenceStatus(device, tasks_for_fence.fence), VK_SUCCESS);
  97. vkDestroyFence(device, tasks_for_fence.fence, nullptr);
  98. }
  99. tasks_to_run.insert(tasks_to_run.end(),
  100. std::make_move_iterator(tasks_for_fence.tasks.begin()),
  101. std::make_move_iterator(tasks_for_fence.tasks.end()));
  102. cleanup_tasks_.pop_front();
  103. }
  104. for (auto& task : tasks_to_run)
  105. std::move(task).Run(device_queue_.get(), false /* device_lost */);
  106. }
  107. VulkanFenceHelper::FenceHandle VulkanFenceHelper::GenerateCleanupFence() {
  108. if (tasks_pending_fence_.empty())
  109. return FenceHandle();
  110. VkFence fence = VK_NULL_HANDLE;
  111. VkResult result = GetFence(&fence);
  112. if (result != VK_SUCCESS) {
  113. PerformImmediateCleanup();
  114. return FenceHandle();
  115. }
  116. result = vkQueueSubmit(device_queue_->GetVulkanQueue(), 0, nullptr, fence);
  117. if (result != VK_SUCCESS) {
  118. vkDestroyFence(device_queue_->GetVulkanDevice(), fence, nullptr);
  119. PerformImmediateCleanup();
  120. return FenceHandle();
  121. }
  122. return EnqueueFence(fence);
  123. }
  124. base::OnceClosure VulkanFenceHelper::CreateExternalCallback() {
  125. // No need to do callback tracking if there are no cleanup tasks to run.
  126. if (tasks_pending_fence_.empty())
  127. return base::OnceClosure();
  128. // Get a generation ID for this callback and associate existing cleanup
  129. // tasks.
  130. uint64_t generation_id = next_generation_++;
  131. cleanup_tasks_.emplace_back(generation_id, std::move(tasks_pending_fence_));
  132. tasks_pending_fence_ = std::vector<CleanupTask>();
  133. return base::BindOnce(
  134. [](base::WeakPtr<VulkanFenceHelper> fence_helper,
  135. uint64_t generation_id) {
  136. if (!fence_helper)
  137. return;
  138. // If |current_generation_| is ahead of the callback's
  139. // |generation_id|, the callback came late. Ignore it.
  140. if (generation_id > fence_helper->current_generation_) {
  141. fence_helper->ProcessCleanupTasks(generation_id);
  142. }
  143. },
  144. weak_factory_.GetWeakPtr(), generation_id);
  145. }
  146. void VulkanFenceHelper::EnqueueSemaphoreCleanupForSubmittedWork(
  147. VkSemaphore semaphore) {
  148. if (semaphore == VK_NULL_HANDLE)
  149. return;
  150. EnqueueSemaphoresCleanupForSubmittedWork({semaphore});
  151. }
  152. void VulkanFenceHelper::EnqueueSemaphoresCleanupForSubmittedWork(
  153. std::vector<VkSemaphore> semaphores) {
  154. if (semaphores.empty())
  155. return;
  156. EnqueueCleanupTaskForSubmittedWork(base::BindOnce(
  157. [](std::vector<VkSemaphore> semaphores, VulkanDeviceQueue* device_queue,
  158. bool /* is_lost */) {
  159. for (VkSemaphore semaphore : semaphores) {
  160. vkDestroySemaphore(device_queue->GetVulkanDevice(), semaphore,
  161. nullptr);
  162. }
  163. },
  164. std::move(semaphores)));
  165. }
  166. void VulkanFenceHelper::EnqueueImageCleanupForSubmittedWork(
  167. VkImage image,
  168. VkDeviceMemory memory) {
  169. if (image == VK_NULL_HANDLE && memory == VK_NULL_HANDLE)
  170. return;
  171. EnqueueCleanupTaskForSubmittedWork(base::BindOnce(
  172. [](VkImage image, VkDeviceMemory memory, VulkanDeviceQueue* device_queue,
  173. bool /* is_lost */) {
  174. if (image != VK_NULL_HANDLE)
  175. vkDestroyImage(device_queue->GetVulkanDevice(), image, nullptr);
  176. if (memory != VK_NULL_HANDLE)
  177. vkFreeMemory(device_queue->GetVulkanDevice(), memory, nullptr);
  178. },
  179. image, memory));
  180. }
  181. void VulkanFenceHelper::EnqueueBufferCleanupForSubmittedWork(
  182. VkBuffer buffer,
  183. VmaAllocation allocation) {
  184. if (buffer == VK_NULL_HANDLE && allocation == VK_NULL_HANDLE)
  185. return;
  186. DCHECK(buffer != VK_NULL_HANDLE);
  187. DCHECK(allocation != VK_NULL_HANDLE);
  188. EnqueueCleanupTaskForSubmittedWork(base::BindOnce(
  189. [](VkBuffer buffer, VmaAllocation allocation,
  190. VulkanDeviceQueue* device_queue, bool /* is_lost */) {
  191. vma::DestroyBuffer(device_queue->vma_allocator(), buffer, allocation);
  192. },
  193. buffer, allocation));
  194. }
  195. void VulkanFenceHelper::PerformImmediateCleanup() {
  196. if (cleanup_tasks_.empty() && tasks_pending_fence_.empty())
  197. return;
  198. // We want to run all tasks immediately, so just use vkQueueWaitIdle which
  199. // ensures that all fences have passed.
  200. // Even if exclusively using callbacks, the callbacks use WeakPtr and will
  201. // not keep this class alive, so it's important to wait / run all cleanup
  202. // immediately.
  203. VkResult result = vkQueueWaitIdle(device_queue_->GetVulkanQueue());
  204. // Wait can only fail for three reasons - device loss, host OOM, device OOM.
  205. // If we hit an OOM, treat this as a crash. There isn't a great way to
  206. // recover from this.
  207. CHECK(result == VK_SUCCESS || result == VK_ERROR_DEVICE_LOST);
  208. bool device_lost = result == VK_ERROR_DEVICE_LOST;
  209. // We're going to destroy all fences below, so we should consider them as
  210. // passed.
  211. current_generation_ = next_generation_ - 1;
  212. // Run all cleanup tasks. Create a temporary vector of tasks to run to avoid
  213. // reentrancy issues.
  214. std::vector<CleanupTask> tasks_to_run;
  215. while (!cleanup_tasks_.empty()) {
  216. auto& tasks_for_fence = cleanup_tasks_.front();
  217. vkDestroyFence(device_queue_->GetVulkanDevice(), tasks_for_fence.fence,
  218. nullptr);
  219. tasks_to_run.insert(tasks_to_run.end(),
  220. std::make_move_iterator(tasks_for_fence.tasks.begin()),
  221. std::make_move_iterator(tasks_for_fence.tasks.end()));
  222. cleanup_tasks_.pop_front();
  223. }
  224. tasks_to_run.insert(tasks_to_run.end(),
  225. std::make_move_iterator(tasks_pending_fence_.begin()),
  226. std::make_move_iterator(tasks_pending_fence_.end()));
  227. tasks_pending_fence_.clear();
  228. for (auto& task : tasks_to_run)
  229. std::move(task).Run(device_queue_.get(), device_lost);
  230. }
  231. VulkanFenceHelper::TasksForFence::TasksForFence(FenceHandle handle,
  232. std::vector<CleanupTask> tasks)
  233. : fence(handle.fence_),
  234. generation_id(handle.generation_id_),
  235. tasks(std::move(tasks)) {}
  236. VulkanFenceHelper::TasksForFence::TasksForFence(uint64_t generation_id,
  237. std::vector<CleanupTask> tasks)
  238. : generation_id(generation_id), tasks(std::move(tasks)) {}
  239. VulkanFenceHelper::TasksForFence::~TasksForFence() = default;
  240. VulkanFenceHelper::TasksForFence::TasksForFence(TasksForFence&& other) =
  241. default;
  242. } // namespace gpu