1 // Copyright 2016 Dolphin Emulator Project 2 // Licensed under GPLv2+ 3 // Refer to the license.txt file included. 4 5 #pragma once 6 7 #include <array> 8 #include <cstddef> 9 #include <deque> 10 #include <functional> 11 #include <map> 12 #include <memory> 13 #include <mutex> 14 #include <thread> 15 #include <utility> 16 #include <vector> 17 18 #include "Common/BlockingLoop.h" 19 #include "Common/Flag.h" 20 #include "Common/Semaphore.h" 21 22 #include "VideoBackends/Vulkan/Constants.h" 23 24 namespace Vulkan 25 { 26 class CommandBufferManager 27 { 28 public: 29 explicit CommandBufferManager(bool use_threaded_submission); 30 ~CommandBufferManager(); 31 32 bool Initialize(); 33 34 // These command buffers are allocated per-frame. They are valid until the command buffer 35 // is submitted, after that you should call these functions again. GetCurrentInitCommandBuffer()36 VkCommandBuffer GetCurrentInitCommandBuffer() 37 { 38 m_frame_resources[m_current_frame].init_command_buffer_used = true; 39 return m_frame_resources[m_current_frame].command_buffers[0]; 40 } GetCurrentCommandBuffer()41 VkCommandBuffer GetCurrentCommandBuffer() const 42 { 43 return m_frame_resources[m_current_frame].command_buffers[1]; 44 } GetCurrentDescriptorPool()45 VkDescriptorPool GetCurrentDescriptorPool() const 46 { 47 return m_frame_resources[m_current_frame].descriptor_pool; 48 } 49 // Allocates a descriptors set from the pool reserved for the current frame. 50 VkDescriptorSet AllocateDescriptorSet(VkDescriptorSetLayout set_layout); 51 52 // Fence "counters" are used to track which commands have been completed by the GPU. 53 // If the last completed fence counter is greater or equal to N, it means that the work 54 // associated counter N has been completed by the GPU. The value of N to associate with 55 // commands can be retreived by calling GetCurrentFenceCounter(). GetCompletedFenceCounter()56 u64 GetCompletedFenceCounter() const { return m_completed_fence_counter; } 57 58 // Gets the fence that will be signaled when the currently executing command buffer is 59 // queued and executed. Do not wait for this fence before the buffer is executed. GetCurrentFenceCounter()60 u64 GetCurrentFenceCounter() const { return m_frame_resources[m_current_frame].fence_counter; } 61 62 // Returns the semaphore for the current command buffer, which can be used to ensure the 63 // swap chain image is ready before the command buffer executes. GetCurrentCommandBufferSemaphore()64 VkSemaphore GetCurrentCommandBufferSemaphore() 65 { 66 m_frame_resources[m_current_frame].semaphore_used = true; 67 return m_frame_resources[m_current_frame].semaphore; 68 } 69 70 // Ensure that the worker thread has submitted any previous command buffers and is idle. 71 void WaitForWorkerThreadIdle(); 72 73 // Wait for a fence to be completed. 74 // Also invokes callbacks for completion. 75 void WaitForFenceCounter(u64 fence_counter); 76 77 void SubmitCommandBuffer(bool submit_on_worker_thread, bool wait_for_completion, 78 VkSwapchainKHR present_swap_chain = VK_NULL_HANDLE, 79 uint32_t present_image_index = 0xFFFFFFFF); 80 81 // Was the last present submitted to the queue a failure? If so, we must recreate our swapchain. CheckLastPresentFail()82 bool CheckLastPresentFail() { return m_last_present_failed.TestAndClear(); } GetLastPresentResult()83 VkResult GetLastPresentResult() const { return m_last_present_result; } 84 85 // Schedule a vulkan resource for destruction later on. This will occur when the command buffer 86 // is next re-used, and the GPU has finished working with the specified resource. 87 void DeferBufferDestruction(VkBuffer object); 88 void DeferBufferViewDestruction(VkBufferView object); 89 void DeferDeviceMemoryDestruction(VkDeviceMemory object); 90 void DeferFramebufferDestruction(VkFramebuffer object); 91 void DeferImageDestruction(VkImage object); 92 void DeferImageViewDestruction(VkImageView object); 93 94 private: 95 bool CreateCommandBuffers(); 96 void DestroyCommandBuffers(); 97 98 bool CreateSubmitThread(); 99 100 void WaitForCommandBufferCompletion(u32 command_buffer_index); 101 void SubmitCommandBuffer(u32 command_buffer_index, VkSwapchainKHR present_swap_chain, 102 u32 present_image_index); 103 void BeginCommandBuffer(); 104 105 struct FrameResources 106 { 107 // [0] - Init (upload) command buffer, [1] - draw command buffer 108 VkCommandPool command_pool = VK_NULL_HANDLE; 109 std::array<VkCommandBuffer, 2> command_buffers = {}; 110 VkDescriptorPool descriptor_pool = VK_NULL_HANDLE; 111 VkFence fence = VK_NULL_HANDLE; 112 VkSemaphore semaphore = VK_NULL_HANDLE; 113 u64 fence_counter = 0; 114 bool init_command_buffer_used = false; 115 bool semaphore_used = false; 116 117 std::vector<std::function<void()>> cleanup_resources; 118 }; 119 120 u64 m_next_fence_counter = 1; 121 u64 m_completed_fence_counter = 0; 122 123 std::array<FrameResources, NUM_COMMAND_BUFFERS> m_frame_resources; 124 u32 m_current_frame; 125 126 // Threaded command buffer execution 127 // Semaphore determines when a command buffer can be queued 128 Common::Semaphore m_submit_semaphore; 129 std::thread m_submit_thread; 130 std::unique_ptr<Common::BlockingLoop> m_submit_loop; 131 struct PendingCommandBufferSubmit 132 { 133 VkSwapchainKHR present_swap_chain; 134 u32 present_image_index; 135 u32 command_buffer_index; 136 }; 137 VkSemaphore m_present_semaphore = VK_NULL_HANDLE; 138 std::deque<PendingCommandBufferSubmit> m_pending_submits; 139 std::mutex m_pending_submit_lock; 140 Common::Flag m_last_present_failed; 141 VkResult m_last_present_result = VK_SUCCESS; 142 bool m_use_threaded_submission = false; 143 }; 144 145 extern std::unique_ptr<CommandBufferManager> g_command_buffer_mgr; 146 147 } // namespace Vulkan 148