1 // Copyright 2016 Dolphin Emulator Project
2 // Licensed under GPLv2+
3 // Refer to the license.txt file included.
4 
5 #include "VideoBackends/Vulkan/CommandBufferManager.h"
6 
7 #include <array>
8 #include <cstdint>
9 
10 #include "Common/Assert.h"
11 #include "Common/MsgHandler.h"
12 #include "Common/Thread.h"
13 
14 #include "VideoBackends/Vulkan/VulkanContext.h"
15 
16 namespace Vulkan
17 {
CommandBufferManager(bool use_threaded_submission)18 CommandBufferManager::CommandBufferManager(bool use_threaded_submission)
19     : m_submit_semaphore(1, 1), m_use_threaded_submission(use_threaded_submission)
20 {
21 }
22 
~CommandBufferManager()23 CommandBufferManager::~CommandBufferManager()
24 {
25   // If the worker thread is enabled, stop and block until it exits.
26   if (m_use_threaded_submission)
27   {
28     m_submit_loop->Stop();
29     m_submit_thread.join();
30   }
31 
32   DestroyCommandBuffers();
33 }
34 
Initialize()35 bool CommandBufferManager::Initialize()
36 {
37   if (!CreateCommandBuffers())
38     return false;
39 
40   if (m_use_threaded_submission && !CreateSubmitThread())
41     return false;
42 
43   return true;
44 }
45 
CreateCommandBuffers()46 bool CommandBufferManager::CreateCommandBuffers()
47 {
48   static constexpr VkSemaphoreCreateInfo semaphore_create_info = {
49       VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, nullptr, 0};
50 
51   VkDevice device = g_vulkan_context->GetDevice();
52   VkResult res;
53 
54   for (FrameResources& resources : m_frame_resources)
55   {
56     resources.init_command_buffer_used = false;
57     resources.semaphore_used = false;
58 
59     VkCommandPoolCreateInfo pool_info = {VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, nullptr, 0,
60                                          g_vulkan_context->GetGraphicsQueueFamilyIndex()};
61     res = vkCreateCommandPool(g_vulkan_context->GetDevice(), &pool_info, nullptr,
62                               &resources.command_pool);
63     if (res != VK_SUCCESS)
64     {
65       LOG_VULKAN_ERROR(res, "vkCreateCommandPool failed: ");
66       return false;
67     }
68 
69     VkCommandBufferAllocateInfo buffer_info = {
70         VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, nullptr, resources.command_pool,
71         VK_COMMAND_BUFFER_LEVEL_PRIMARY, static_cast<uint32_t>(resources.command_buffers.size())};
72 
73     res = vkAllocateCommandBuffers(device, &buffer_info, resources.command_buffers.data());
74     if (res != VK_SUCCESS)
75     {
76       LOG_VULKAN_ERROR(res, "vkAllocateCommandBuffers failed: ");
77       return false;
78     }
79 
80     VkFenceCreateInfo fence_info = {VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, nullptr,
81                                     VK_FENCE_CREATE_SIGNALED_BIT};
82 
83     res = vkCreateFence(device, &fence_info, nullptr, &resources.fence);
84     if (res != VK_SUCCESS)
85     {
86       LOG_VULKAN_ERROR(res, "vkCreateFence failed: ");
87       return false;
88     }
89 
90     res = vkCreateSemaphore(device, &semaphore_create_info, nullptr, &resources.semaphore);
91     if (res != VK_SUCCESS)
92     {
93       LOG_VULKAN_ERROR(res, "vkCreateSemaphore failed: ");
94       return false;
95     }
96 
97     // TODO: A better way to choose the number of descriptors.
98     const std::array<VkDescriptorPoolSize, 5> pool_sizes{{
99         {VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 500000},
100         {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 500000},
101         {VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 16},
102         {VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, 16384},
103         {VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 16384},
104     }};
105 
106     const VkDescriptorPoolCreateInfo pool_create_info = {
107         VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
108         nullptr,
109         0,
110         100000,  // tweak this
111         static_cast<u32>(pool_sizes.size()),
112         pool_sizes.data(),
113     };
114 
115     res = vkCreateDescriptorPool(device, &pool_create_info, nullptr, &resources.descriptor_pool);
116     if (res != VK_SUCCESS)
117     {
118       LOG_VULKAN_ERROR(res, "vkCreateDescriptorPool failed: ");
119       return false;
120     }
121   }
122 
123   res = vkCreateSemaphore(device, &semaphore_create_info, nullptr, &m_present_semaphore);
124   if (res != VK_SUCCESS)
125   {
126     LOG_VULKAN_ERROR(res, "vkCreateSemaphore failed: ");
127     return false;
128   }
129 
130   // Activate the first command buffer. ActivateCommandBuffer moves forward, so start with the last
131   m_current_frame = static_cast<u32>(m_frame_resources.size()) - 1;
132   BeginCommandBuffer();
133   return true;
134 }
135 
DestroyCommandBuffers()136 void CommandBufferManager::DestroyCommandBuffers()
137 {
138   VkDevice device = g_vulkan_context->GetDevice();
139 
140   for (FrameResources& resources : m_frame_resources)
141   {
142     // The Vulkan spec section 5.2 says: "When a pool is destroyed, all command buffers allocated
143     // from the pool are freed.". So we don't need to free the command buffers, just the pools.
144     // We destroy the command pool first, to avoid any warnings from the validation layers about
145     // objects which are pending destruction being in-use.
146     if (resources.command_pool != VK_NULL_HANDLE)
147       vkDestroyCommandPool(device, resources.command_pool, nullptr);
148 
149     // Destroy any pending objects.
150     for (auto& it : resources.cleanup_resources)
151       it();
152 
153     if (resources.semaphore != VK_NULL_HANDLE)
154       vkDestroySemaphore(device, resources.semaphore, nullptr);
155 
156     if (resources.fence != VK_NULL_HANDLE)
157       vkDestroyFence(device, resources.fence, nullptr);
158 
159     if (resources.descriptor_pool != VK_NULL_HANDLE)
160       vkDestroyDescriptorPool(device, resources.descriptor_pool, nullptr);
161   }
162 
163   vkDestroySemaphore(device, m_present_semaphore, nullptr);
164 }
165 
AllocateDescriptorSet(VkDescriptorSetLayout set_layout)166 VkDescriptorSet CommandBufferManager::AllocateDescriptorSet(VkDescriptorSetLayout set_layout)
167 {
168   VkDescriptorSetAllocateInfo allocate_info = {
169       VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, nullptr,
170       m_frame_resources[m_current_frame].descriptor_pool, 1, &set_layout};
171 
172   VkDescriptorSet descriptor_set;
173   VkResult res =
174       vkAllocateDescriptorSets(g_vulkan_context->GetDevice(), &allocate_info, &descriptor_set);
175   if (res != VK_SUCCESS)
176   {
177     // Failing to allocate a descriptor set is not a fatal error, we can
178     // recover by moving to the next command buffer.
179     return VK_NULL_HANDLE;
180   }
181 
182   return descriptor_set;
183 }
184 
CreateSubmitThread()185 bool CommandBufferManager::CreateSubmitThread()
186 {
187   m_submit_loop = std::make_unique<Common::BlockingLoop>();
188   m_submit_thread = std::thread([this]() {
189     Common::SetCurrentThreadName("Vulkan CommandBufferManager SubmitThread");
190 
191     m_submit_loop->Run([this]() {
192       PendingCommandBufferSubmit submit;
193       {
194         std::lock_guard<std::mutex> guard(m_pending_submit_lock);
195         if (m_pending_submits.empty())
196         {
197           m_submit_loop->AllowSleep();
198           return;
199         }
200 
201         submit = m_pending_submits.front();
202         m_pending_submits.pop_front();
203       }
204 
205       SubmitCommandBuffer(submit.command_buffer_index, submit.present_swap_chain,
206                           submit.present_image_index);
207     });
208   });
209 
210   return true;
211 }
212 
WaitForWorkerThreadIdle()213 void CommandBufferManager::WaitForWorkerThreadIdle()
214 {
215   // Drain the semaphore, then allow another request in the future.
216   m_submit_semaphore.Wait();
217   m_submit_semaphore.Post();
218 }
219 
WaitForFenceCounter(u64 fence_counter)220 void CommandBufferManager::WaitForFenceCounter(u64 fence_counter)
221 {
222   if (m_completed_fence_counter >= fence_counter)
223     return;
224 
225   // Find the first command buffer which covers this counter value.
226   u32 index = (m_current_frame + 1) % NUM_COMMAND_BUFFERS;
227   while (index != m_current_frame)
228   {
229     if (m_frame_resources[index].fence_counter >= fence_counter)
230       break;
231 
232     index = (index + 1) % NUM_COMMAND_BUFFERS;
233   }
234 
235   ASSERT(index != m_current_frame);
236   WaitForCommandBufferCompletion(index);
237 }
238 
WaitForCommandBufferCompletion(u32 index)239 void CommandBufferManager::WaitForCommandBufferCompletion(u32 index)
240 {
241   // Ensure this command buffer has been submitted.
242   WaitForWorkerThreadIdle();
243 
244   // Wait for this command buffer to be completed.
245   VkResult res = vkWaitForFences(g_vulkan_context->GetDevice(), 1, &m_frame_resources[index].fence,
246                                  VK_TRUE, UINT64_MAX);
247   if (res != VK_SUCCESS)
248     LOG_VULKAN_ERROR(res, "vkWaitForFences failed: ");
249 
250   // Clean up any resources for command buffers between the last known completed buffer and this
251   // now-completed command buffer. If we use >2 buffers, this may be more than one buffer.
252   const u64 now_completed_counter = m_frame_resources[index].fence_counter;
253   u32 cleanup_index = (m_current_frame + 1) % NUM_COMMAND_BUFFERS;
254   while (cleanup_index != m_current_frame)
255   {
256     FrameResources& resources = m_frame_resources[cleanup_index];
257     if (resources.fence_counter > now_completed_counter)
258       break;
259 
260     if (resources.fence_counter > m_completed_fence_counter)
261     {
262       for (auto& it : resources.cleanup_resources)
263         it();
264       resources.cleanup_resources.clear();
265     }
266 
267     cleanup_index = (cleanup_index + 1) % NUM_COMMAND_BUFFERS;
268   }
269 
270   m_completed_fence_counter = now_completed_counter;
271 }
272 
SubmitCommandBuffer(bool submit_on_worker_thread,bool wait_for_completion,VkSwapchainKHR present_swap_chain,uint32_t present_image_index)273 void CommandBufferManager::SubmitCommandBuffer(bool submit_on_worker_thread,
274                                                bool wait_for_completion,
275                                                VkSwapchainKHR present_swap_chain,
276                                                uint32_t present_image_index)
277 {
278   // End the current command buffer.
279   FrameResources& resources = m_frame_resources[m_current_frame];
280   for (VkCommandBuffer command_buffer : resources.command_buffers)
281   {
282     VkResult res = vkEndCommandBuffer(command_buffer);
283     if (res != VK_SUCCESS)
284     {
285       LOG_VULKAN_ERROR(res, "vkEndCommandBuffer failed: ");
286       PanicAlert("Failed to end command buffer");
287     }
288   }
289 
290   // Grab the semaphore before submitting command buffer either on-thread or off-thread.
291   // This prevents a race from occurring where a second command buffer is executed
292   // before the worker thread has woken and executed the first one yet.
293   m_submit_semaphore.Wait();
294 
295   // Submitting off-thread?
296   if (m_use_threaded_submission && submit_on_worker_thread && !wait_for_completion)
297   {
298     // Push to the pending submit queue.
299     {
300       std::lock_guard<std::mutex> guard(m_pending_submit_lock);
301       m_pending_submits.push_back({present_swap_chain, present_image_index, m_current_frame});
302     }
303 
304     // Wake up the worker thread for a single iteration.
305     m_submit_loop->Wakeup();
306   }
307   else
308   {
309     // Pass through to normal submission path.
310     SubmitCommandBuffer(m_current_frame, present_swap_chain, present_image_index);
311     if (wait_for_completion)
312       WaitForCommandBufferCompletion(m_current_frame);
313   }
314 
315   // Switch to next cmdbuffer.
316   BeginCommandBuffer();
317 }
318 
SubmitCommandBuffer(u32 command_buffer_index,VkSwapchainKHR present_swap_chain,u32 present_image_index)319 void CommandBufferManager::SubmitCommandBuffer(u32 command_buffer_index,
320                                                VkSwapchainKHR present_swap_chain,
321                                                u32 present_image_index)
322 {
323   FrameResources& resources = m_frame_resources[command_buffer_index];
324 
325   // This may be executed on the worker thread, so don't modify any state of the manager class.
326   uint32_t wait_bits = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
327   VkSubmitInfo submit_info = {VK_STRUCTURE_TYPE_SUBMIT_INFO,
328                               nullptr,
329                               0,
330                               nullptr,
331                               &wait_bits,
332                               static_cast<u32>(resources.command_buffers.size()),
333                               resources.command_buffers.data(),
334                               0,
335                               nullptr};
336 
337   // If the init command buffer did not have any commands recorded, don't submit it.
338   if (!resources.init_command_buffer_used)
339   {
340     submit_info.commandBufferCount = 1;
341     submit_info.pCommandBuffers = &resources.command_buffers[1];
342   }
343 
344   if (resources.semaphore_used)
345   {
346     submit_info.pWaitSemaphores = &resources.semaphore;
347     submit_info.waitSemaphoreCount = 1;
348   }
349 
350   if (present_swap_chain != VK_NULL_HANDLE)
351   {
352     submit_info.signalSemaphoreCount = 1;
353     submit_info.pSignalSemaphores = &m_present_semaphore;
354   }
355 
356   VkResult res =
357       vkQueueSubmit(g_vulkan_context->GetGraphicsQueue(), 1, &submit_info, resources.fence);
358   if (res != VK_SUCCESS)
359   {
360     LOG_VULKAN_ERROR(res, "vkQueueSubmit failed: ");
361     PanicAlert("Failed to submit command buffer.");
362   }
363 
364   // Do we have a swap chain to present?
365   if (present_swap_chain != VK_NULL_HANDLE)
366   {
367     // Should have a signal semaphore.
368     VkPresentInfoKHR present_info = {VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
369                                      nullptr,
370                                      1,
371                                      &m_present_semaphore,
372                                      1,
373                                      &present_swap_chain,
374                                      &present_image_index,
375                                      nullptr};
376 
377     m_last_present_result = vkQueuePresentKHR(g_vulkan_context->GetPresentQueue(), &present_info);
378     if (m_last_present_result != VK_SUCCESS)
379     {
380       // VK_ERROR_OUT_OF_DATE_KHR is not fatal, just means we need to recreate our swap chain.
381       if (m_last_present_result != VK_ERROR_OUT_OF_DATE_KHR &&
382           m_last_present_result != VK_SUBOPTIMAL_KHR &&
383           m_last_present_result != VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT)
384       {
385         LOG_VULKAN_ERROR(m_last_present_result, "vkQueuePresentKHR failed: ");
386       }
387 
388       // Don't treat VK_SUBOPTIMAL_KHR as fatal on Android. Android 10+ requires prerotation.
389       // See https://twitter.com/Themaister/status/1207062674011574273
390 #ifdef VK_USE_PLATFORM_ANDROID_KHR
391       if (m_last_present_result != VK_SUBOPTIMAL_KHR)
392         m_last_present_failed.Set();
393 #else
394       m_last_present_failed.Set();
395 #endif
396     }
397   }
398 
399   // Command buffer has been queued, so permit the next one.
400   m_submit_semaphore.Post();
401 }
402 
BeginCommandBuffer()403 void CommandBufferManager::BeginCommandBuffer()
404 {
405   // Move to the next command buffer.
406   const u32 next_buffer_index = (m_current_frame + 1) % NUM_COMMAND_BUFFERS;
407   FrameResources& resources = m_frame_resources[next_buffer_index];
408 
409   // Wait for the GPU to finish with all resources for this command buffer.
410   if (resources.fence_counter > m_completed_fence_counter)
411     WaitForCommandBufferCompletion(next_buffer_index);
412 
413   // Reset fence to unsignaled before starting.
414   VkResult res = vkResetFences(g_vulkan_context->GetDevice(), 1, &resources.fence);
415   if (res != VK_SUCCESS)
416     LOG_VULKAN_ERROR(res, "vkResetFences failed: ");
417 
418   // Reset command pools to beginning since we can re-use the memory now
419   res = vkResetCommandPool(g_vulkan_context->GetDevice(), resources.command_pool, 0);
420   if (res != VK_SUCCESS)
421     LOG_VULKAN_ERROR(res, "vkResetCommandPool failed: ");
422 
423   // Enable commands to be recorded to the two buffers again.
424   VkCommandBufferBeginInfo begin_info = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, nullptr,
425                                          VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, nullptr};
426   for (VkCommandBuffer command_buffer : resources.command_buffers)
427   {
428     res = vkBeginCommandBuffer(command_buffer, &begin_info);
429     if (res != VK_SUCCESS)
430       LOG_VULKAN_ERROR(res, "vkBeginCommandBuffer failed: ");
431   }
432 
433   // Also can do the same for the descriptor pools
434   res = vkResetDescriptorPool(g_vulkan_context->GetDevice(), resources.descriptor_pool, 0);
435   if (res != VK_SUCCESS)
436     LOG_VULKAN_ERROR(res, "vkResetDescriptorPool failed: ");
437 
438   // Reset upload command buffer state
439   resources.init_command_buffer_used = false;
440   resources.semaphore_used = false;
441   resources.fence_counter = m_next_fence_counter++;
442   m_current_frame = next_buffer_index;
443 }
444 
DeferBufferDestruction(VkBuffer object)445 void CommandBufferManager::DeferBufferDestruction(VkBuffer object)
446 {
447   FrameResources& resources = m_frame_resources[m_current_frame];
448   resources.cleanup_resources.push_back(
449       [object]() { vkDestroyBuffer(g_vulkan_context->GetDevice(), object, nullptr); });
450 }
451 
DeferBufferViewDestruction(VkBufferView object)452 void CommandBufferManager::DeferBufferViewDestruction(VkBufferView object)
453 {
454   FrameResources& resources = m_frame_resources[m_current_frame];
455   resources.cleanup_resources.push_back(
456       [object]() { vkDestroyBufferView(g_vulkan_context->GetDevice(), object, nullptr); });
457 }
458 
DeferDeviceMemoryDestruction(VkDeviceMemory object)459 void CommandBufferManager::DeferDeviceMemoryDestruction(VkDeviceMemory object)
460 {
461   FrameResources& resources = m_frame_resources[m_current_frame];
462   resources.cleanup_resources.push_back(
463       [object]() { vkFreeMemory(g_vulkan_context->GetDevice(), object, nullptr); });
464 }
465 
DeferFramebufferDestruction(VkFramebuffer object)466 void CommandBufferManager::DeferFramebufferDestruction(VkFramebuffer object)
467 {
468   FrameResources& resources = m_frame_resources[m_current_frame];
469   resources.cleanup_resources.push_back(
470       [object]() { vkDestroyFramebuffer(g_vulkan_context->GetDevice(), object, nullptr); });
471 }
472 
DeferImageDestruction(VkImage object)473 void CommandBufferManager::DeferImageDestruction(VkImage object)
474 {
475   FrameResources& resources = m_frame_resources[m_current_frame];
476   resources.cleanup_resources.push_back(
477       [object]() { vkDestroyImage(g_vulkan_context->GetDevice(), object, nullptr); });
478 }
479 
DeferImageViewDestruction(VkImageView object)480 void CommandBufferManager::DeferImageViewDestruction(VkImageView object)
481 {
482   FrameResources& resources = m_frame_resources[m_current_frame];
483   resources.cleanup_resources.push_back(
484       [object]() { vkDestroyImageView(g_vulkan_context->GetDevice(), object, nullptr); });
485 }
486 
487 std::unique_ptr<CommandBufferManager> g_command_buffer_mgr;
488 }  // namespace Vulkan
489