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