vk_scheduler.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  1. // SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
  2. // SPDX-License-Identifier: GPL-2.0-or-later
  3. #include <memory>
  4. #include <mutex>
  5. #include <thread>
  6. #include <utility>
  7. #include "common/microprofile.h"
  8. #include "common/thread.h"
  9. #include "video_core/renderer_vulkan/vk_command_pool.h"
  10. #include "video_core/renderer_vulkan/vk_master_semaphore.h"
  11. #include "video_core/renderer_vulkan/vk_query_cache.h"
  12. #include "video_core/renderer_vulkan/vk_scheduler.h"
  13. #include "video_core/renderer_vulkan/vk_state_tracker.h"
  14. #include "video_core/renderer_vulkan/vk_texture_cache.h"
  15. #include "video_core/vulkan_common/vulkan_device.h"
  16. #include "video_core/vulkan_common/vulkan_wrapper.h"
  17. namespace Vulkan {
  18. MICROPROFILE_DECLARE(Vulkan_WaitForWorker);
  19. void Scheduler::CommandChunk::ExecuteAll(vk::CommandBuffer cmdbuf) {
  20. auto command = first;
  21. while (command != nullptr) {
  22. auto next = command->GetNext();
  23. command->Execute(cmdbuf);
  24. command->~Command();
  25. command = next;
  26. }
  27. submit = false;
  28. command_offset = 0;
  29. first = nullptr;
  30. last = nullptr;
  31. }
  32. Scheduler::Scheduler(const Device& device_, StateTracker& state_tracker_)
  33. : device{device_}, state_tracker{state_tracker_},
  34. master_semaphore{std::make_unique<MasterSemaphore>(device)},
  35. command_pool{std::make_unique<CommandPool>(*master_semaphore, device)} {
  36. AcquireNewChunk();
  37. AllocateWorkerCommandBuffer();
  38. worker_thread = std::jthread([this](std::stop_token token) { WorkerThread(token); });
  39. }
  40. Scheduler::~Scheduler() = default;
  41. void Scheduler::Flush(VkSemaphore signal_semaphore, VkSemaphore wait_semaphore) {
  42. // When flushing, we only send data to the worker thread; no waiting is necessary.
  43. SubmitExecution(signal_semaphore, wait_semaphore);
  44. AllocateNewContext();
  45. }
  46. void Scheduler::Finish(VkSemaphore signal_semaphore, VkSemaphore wait_semaphore) {
  47. // When finishing, we need to wait for the submission to have executed on the device.
  48. const u64 presubmit_tick = CurrentTick();
  49. SubmitExecution(signal_semaphore, wait_semaphore);
  50. Wait(presubmit_tick);
  51. AllocateNewContext();
  52. }
  53. void Scheduler::WaitWorker() {
  54. MICROPROFILE_SCOPE(Vulkan_WaitForWorker);
  55. DispatchWork();
  56. // Ensure the queue is drained.
  57. {
  58. std::unique_lock ql{queue_mutex};
  59. event_cv.wait(ql, [this] { return work_queue.empty(); });
  60. }
  61. // Now wait for execution to finish.
  62. std::scoped_lock el{execution_mutex};
  63. }
  64. void Scheduler::DispatchWork() {
  65. if (chunk->Empty()) {
  66. return;
  67. }
  68. {
  69. std::scoped_lock ql{queue_mutex};
  70. work_queue.push(std::move(chunk));
  71. }
  72. event_cv.notify_all();
  73. AcquireNewChunk();
  74. }
  75. void Scheduler::RequestRenderpass(const Framebuffer* framebuffer) {
  76. const VkRenderPass renderpass = framebuffer->RenderPass();
  77. const VkFramebuffer framebuffer_handle = framebuffer->Handle();
  78. const VkExtent2D render_area = framebuffer->RenderArea();
  79. if (renderpass == state.renderpass && framebuffer_handle == state.framebuffer &&
  80. render_area.width == state.render_area.width &&
  81. render_area.height == state.render_area.height) {
  82. return;
  83. }
  84. EndRenderPass();
  85. state.renderpass = renderpass;
  86. state.framebuffer = framebuffer_handle;
  87. state.render_area = render_area;
  88. Record([renderpass, framebuffer_handle, render_area](vk::CommandBuffer cmdbuf) {
  89. const VkRenderPassBeginInfo renderpass_bi{
  90. .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
  91. .pNext = nullptr,
  92. .renderPass = renderpass,
  93. .framebuffer = framebuffer_handle,
  94. .renderArea =
  95. {
  96. .offset = {.x = 0, .y = 0},
  97. .extent = render_area,
  98. },
  99. .clearValueCount = 0,
  100. .pClearValues = nullptr,
  101. };
  102. cmdbuf.BeginRenderPass(renderpass_bi, VK_SUBPASS_CONTENTS_INLINE);
  103. });
  104. num_renderpass_images = framebuffer->NumImages();
  105. renderpass_images = framebuffer->Images();
  106. renderpass_image_ranges = framebuffer->ImageRanges();
  107. }
  108. void Scheduler::RequestOutsideRenderPassOperationContext() {
  109. EndRenderPass();
  110. }
  111. bool Scheduler::UpdateGraphicsPipeline(GraphicsPipeline* pipeline) {
  112. if (state.graphics_pipeline == pipeline) {
  113. return false;
  114. }
  115. state.graphics_pipeline = pipeline;
  116. return true;
  117. }
  118. bool Scheduler::UpdateRescaling(bool is_rescaling) {
  119. if (state.rescaling_defined && is_rescaling == state.is_rescaling) {
  120. return false;
  121. }
  122. state.rescaling_defined = true;
  123. state.is_rescaling = is_rescaling;
  124. return true;
  125. }
  126. void Scheduler::WorkerThread(std::stop_token stop_token) {
  127. Common::SetCurrentThreadName("VulkanWorker");
  128. const auto TryPopQueue{[this](auto& work) -> bool {
  129. if (work_queue.empty()) {
  130. return false;
  131. }
  132. work = std::move(work_queue.front());
  133. work_queue.pop();
  134. event_cv.notify_all();
  135. return true;
  136. }};
  137. while (!stop_token.stop_requested()) {
  138. std::unique_ptr<CommandChunk> work;
  139. {
  140. std::unique_lock lk{queue_mutex};
  141. // Wait for work.
  142. Common::CondvarWait(event_cv, lk, stop_token, [&] { return TryPopQueue(work); });
  143. // If we've been asked to stop, we're done.
  144. if (stop_token.stop_requested()) {
  145. return;
  146. }
  147. // Exchange lock ownership so that we take the execution lock before
  148. // the queue lock goes out of scope. This allows us to force execution
  149. // to complete in the next step.
  150. std::exchange(lk, std::unique_lock{execution_mutex});
  151. // Perform the work, tracking whether the chunk was a submission
  152. // before executing.
  153. const bool has_submit = work->HasSubmit();
  154. work->ExecuteAll(current_cmdbuf);
  155. // If the chunk was a submission, reallocate the command buffer.
  156. if (has_submit) {
  157. AllocateWorkerCommandBuffer();
  158. }
  159. }
  160. {
  161. std::scoped_lock rl{reserve_mutex};
  162. // Recycle the chunk back to the reserve.
  163. chunk_reserve.emplace_back(std::move(work));
  164. }
  165. }
  166. }
  167. void Scheduler::AllocateWorkerCommandBuffer() {
  168. current_cmdbuf = vk::CommandBuffer(command_pool->Commit(), device.GetDispatchLoader());
  169. current_cmdbuf.Begin({
  170. .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
  171. .pNext = nullptr,
  172. .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
  173. .pInheritanceInfo = nullptr,
  174. });
  175. }
  176. void Scheduler::SubmitExecution(VkSemaphore signal_semaphore, VkSemaphore wait_semaphore) {
  177. EndPendingOperations();
  178. InvalidateState();
  179. const u64 signal_value = master_semaphore->NextTick();
  180. Record([signal_semaphore, wait_semaphore, signal_value, this](vk::CommandBuffer cmdbuf) {
  181. cmdbuf.End();
  182. const VkSemaphore timeline_semaphore = master_semaphore->Handle();
  183. const u32 num_signal_semaphores = signal_semaphore ? 2U : 1U;
  184. const std::array signal_values{signal_value, u64(0)};
  185. const std::array signal_semaphores{timeline_semaphore, signal_semaphore};
  186. const u32 num_wait_semaphores = wait_semaphore ? 2U : 1U;
  187. const std::array wait_values{signal_value - 1, u64(1)};
  188. const std::array wait_semaphores{timeline_semaphore, wait_semaphore};
  189. static constexpr std::array<VkPipelineStageFlags, 2> wait_stage_masks{
  190. VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
  191. VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
  192. };
  193. const VkTimelineSemaphoreSubmitInfo timeline_si{
  194. .sType = VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO,
  195. .pNext = nullptr,
  196. .waitSemaphoreValueCount = num_wait_semaphores,
  197. .pWaitSemaphoreValues = wait_values.data(),
  198. .signalSemaphoreValueCount = num_signal_semaphores,
  199. .pSignalSemaphoreValues = signal_values.data(),
  200. };
  201. const VkSubmitInfo submit_info{
  202. .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
  203. .pNext = &timeline_si,
  204. .waitSemaphoreCount = num_wait_semaphores,
  205. .pWaitSemaphores = wait_semaphores.data(),
  206. .pWaitDstStageMask = wait_stage_masks.data(),
  207. .commandBufferCount = 1,
  208. .pCommandBuffers = cmdbuf.address(),
  209. .signalSemaphoreCount = num_signal_semaphores,
  210. .pSignalSemaphores = signal_semaphores.data(),
  211. };
  212. if (on_submit) {
  213. on_submit();
  214. }
  215. switch (const VkResult result = device.GetGraphicsQueue().Submit(submit_info)) {
  216. case VK_SUCCESS:
  217. break;
  218. case VK_ERROR_DEVICE_LOST:
  219. device.ReportLoss();
  220. [[fallthrough]];
  221. default:
  222. vk::Check(result);
  223. break;
  224. }
  225. });
  226. chunk->MarkSubmit();
  227. DispatchWork();
  228. }
  229. void Scheduler::AllocateNewContext() {
  230. // Enable counters once again. These are disabled when a command buffer is finished.
  231. if (query_cache) {
  232. query_cache->UpdateCounters();
  233. }
  234. }
  235. void Scheduler::InvalidateState() {
  236. state.graphics_pipeline = nullptr;
  237. state.rescaling_defined = false;
  238. state_tracker.InvalidateCommandBufferState();
  239. }
  240. void Scheduler::EndPendingOperations() {
  241. query_cache->DisableStreams();
  242. EndRenderPass();
  243. }
  244. void Scheduler::EndRenderPass() {
  245. if (!state.renderpass) {
  246. return;
  247. }
  248. Record([num_images = num_renderpass_images, images = renderpass_images,
  249. ranges = renderpass_image_ranges](vk::CommandBuffer cmdbuf) {
  250. std::array<VkImageMemoryBarrier, 9> barriers;
  251. for (size_t i = 0; i < num_images; ++i) {
  252. barriers[i] = VkImageMemoryBarrier{
  253. .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
  254. .pNext = nullptr,
  255. .srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT |
  256. VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT,
  257. .dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT |
  258. VK_ACCESS_COLOR_ATTACHMENT_READ_BIT |
  259. VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT |
  260. VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT |
  261. VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT,
  262. .oldLayout = VK_IMAGE_LAYOUT_GENERAL,
  263. .newLayout = VK_IMAGE_LAYOUT_GENERAL,
  264. .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
  265. .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
  266. .image = images[i],
  267. .subresourceRange = ranges[i],
  268. };
  269. }
  270. cmdbuf.EndRenderPass();
  271. cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT |
  272. VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT |
  273. VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
  274. VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, nullptr, nullptr,
  275. vk::Span(barriers.data(), num_images));
  276. });
  277. state.renderpass = nullptr;
  278. num_renderpass_images = 0;
  279. }
  280. void Scheduler::AcquireNewChunk() {
  281. std::scoped_lock rl{reserve_mutex};
  282. if (chunk_reserve.empty()) {
  283. // If we don't have anything reserved, we need to make a new chunk.
  284. chunk = std::make_unique<CommandChunk>();
  285. } else {
  286. // Otherwise, we can just take from the reserve.
  287. chunk = std::make_unique<CommandChunk>();
  288. chunk_reserve.pop_back();
  289. }
  290. }
  291. } // namespace Vulkan