| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336 |
- // SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
- // SPDX-License-Identifier: GPL-2.0-or-later
- #include <memory>
- #include <mutex>
- #include <thread>
- #include <utility>
- #include "common/microprofile.h"
- #include "common/thread.h"
- #include "video_core/renderer_vulkan/vk_command_pool.h"
- #include "video_core/renderer_vulkan/vk_master_semaphore.h"
- #include "video_core/renderer_vulkan/vk_query_cache.h"
- #include "video_core/renderer_vulkan/vk_scheduler.h"
- #include "video_core/renderer_vulkan/vk_state_tracker.h"
- #include "video_core/renderer_vulkan/vk_texture_cache.h"
- #include "video_core/vulkan_common/vulkan_device.h"
- #include "video_core/vulkan_common/vulkan_wrapper.h"
- namespace Vulkan {
- MICROPROFILE_DECLARE(Vulkan_WaitForWorker);
- void Scheduler::CommandChunk::ExecuteAll(vk::CommandBuffer cmdbuf) {
- auto command = first;
- while (command != nullptr) {
- auto next = command->GetNext();
- command->Execute(cmdbuf);
- command->~Command();
- command = next;
- }
- submit = false;
- command_offset = 0;
- first = nullptr;
- last = nullptr;
- }
- Scheduler::Scheduler(const Device& device_, StateTracker& state_tracker_)
- : device{device_}, state_tracker{state_tracker_},
- master_semaphore{std::make_unique<MasterSemaphore>(device)},
- command_pool{std::make_unique<CommandPool>(*master_semaphore, device)} {
- AcquireNewChunk();
- AllocateWorkerCommandBuffer();
- worker_thread = std::jthread([this](std::stop_token token) { WorkerThread(token); });
- }
- Scheduler::~Scheduler() = default;
- void Scheduler::Flush(VkSemaphore signal_semaphore, VkSemaphore wait_semaphore) {
- // When flushing, we only send data to the worker thread; no waiting is necessary.
- SubmitExecution(signal_semaphore, wait_semaphore);
- AllocateNewContext();
- }
- void Scheduler::Finish(VkSemaphore signal_semaphore, VkSemaphore wait_semaphore) {
- // When finishing, we need to wait for the submission to have executed on the device.
- const u64 presubmit_tick = CurrentTick();
- SubmitExecution(signal_semaphore, wait_semaphore);
- Wait(presubmit_tick);
- AllocateNewContext();
- }
- void Scheduler::WaitWorker() {
- MICROPROFILE_SCOPE(Vulkan_WaitForWorker);
- DispatchWork();
- // Ensure the queue is drained.
- {
- std::unique_lock ql{queue_mutex};
- event_cv.wait(ql, [this] { return work_queue.empty(); });
- }
- // Now wait for execution to finish.
- std::scoped_lock el{execution_mutex};
- }
- void Scheduler::DispatchWork() {
- if (chunk->Empty()) {
- return;
- }
- {
- std::scoped_lock ql{queue_mutex};
- work_queue.push(std::move(chunk));
- }
- event_cv.notify_all();
- AcquireNewChunk();
- }
- void Scheduler::RequestRenderpass(const Framebuffer* framebuffer) {
- const VkRenderPass renderpass = framebuffer->RenderPass();
- const VkFramebuffer framebuffer_handle = framebuffer->Handle();
- const VkExtent2D render_area = framebuffer->RenderArea();
- if (renderpass == state.renderpass && framebuffer_handle == state.framebuffer &&
- render_area.width == state.render_area.width &&
- render_area.height == state.render_area.height) {
- return;
- }
- EndRenderPass();
- state.renderpass = renderpass;
- state.framebuffer = framebuffer_handle;
- state.render_area = render_area;
- Record([renderpass, framebuffer_handle, render_area](vk::CommandBuffer cmdbuf) {
- const VkRenderPassBeginInfo renderpass_bi{
- .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
- .pNext = nullptr,
- .renderPass = renderpass,
- .framebuffer = framebuffer_handle,
- .renderArea =
- {
- .offset = {.x = 0, .y = 0},
- .extent = render_area,
- },
- .clearValueCount = 0,
- .pClearValues = nullptr,
- };
- cmdbuf.BeginRenderPass(renderpass_bi, VK_SUBPASS_CONTENTS_INLINE);
- });
- num_renderpass_images = framebuffer->NumImages();
- renderpass_images = framebuffer->Images();
- renderpass_image_ranges = framebuffer->ImageRanges();
- }
- void Scheduler::RequestOutsideRenderPassOperationContext() {
- EndRenderPass();
- }
- bool Scheduler::UpdateGraphicsPipeline(GraphicsPipeline* pipeline) {
- if (state.graphics_pipeline == pipeline) {
- return false;
- }
- state.graphics_pipeline = pipeline;
- return true;
- }
- bool Scheduler::UpdateRescaling(bool is_rescaling) {
- if (state.rescaling_defined && is_rescaling == state.is_rescaling) {
- return false;
- }
- state.rescaling_defined = true;
- state.is_rescaling = is_rescaling;
- return true;
- }
- void Scheduler::WorkerThread(std::stop_token stop_token) {
- Common::SetCurrentThreadName("VulkanWorker");
- const auto TryPopQueue{[this](auto& work) -> bool {
- if (work_queue.empty()) {
- return false;
- }
- work = std::move(work_queue.front());
- work_queue.pop();
- event_cv.notify_all();
- return true;
- }};
- while (!stop_token.stop_requested()) {
- std::unique_ptr<CommandChunk> work;
- {
- std::unique_lock lk{queue_mutex};
- // Wait for work.
- Common::CondvarWait(event_cv, lk, stop_token, [&] { return TryPopQueue(work); });
- // If we've been asked to stop, we're done.
- if (stop_token.stop_requested()) {
- return;
- }
- // Exchange lock ownership so that we take the execution lock before
- // the queue lock goes out of scope. This allows us to force execution
- // to complete in the next step.
- std::exchange(lk, std::unique_lock{execution_mutex});
- // Perform the work, tracking whether the chunk was a submission
- // before executing.
- const bool has_submit = work->HasSubmit();
- work->ExecuteAll(current_cmdbuf);
- // If the chunk was a submission, reallocate the command buffer.
- if (has_submit) {
- AllocateWorkerCommandBuffer();
- }
- }
- {
- std::scoped_lock rl{reserve_mutex};
- // Recycle the chunk back to the reserve.
- chunk_reserve.emplace_back(std::move(work));
- }
- }
- }
- void Scheduler::AllocateWorkerCommandBuffer() {
- current_cmdbuf = vk::CommandBuffer(command_pool->Commit(), device.GetDispatchLoader());
- current_cmdbuf.Begin({
- .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
- .pNext = nullptr,
- .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
- .pInheritanceInfo = nullptr,
- });
- }
- void Scheduler::SubmitExecution(VkSemaphore signal_semaphore, VkSemaphore wait_semaphore) {
- EndPendingOperations();
- InvalidateState();
- const u64 signal_value = master_semaphore->NextTick();
- Record([signal_semaphore, wait_semaphore, signal_value, this](vk::CommandBuffer cmdbuf) {
- cmdbuf.End();
- const VkSemaphore timeline_semaphore = master_semaphore->Handle();
- const u32 num_signal_semaphores = signal_semaphore ? 2U : 1U;
- const std::array signal_values{signal_value, u64(0)};
- const std::array signal_semaphores{timeline_semaphore, signal_semaphore};
- const u32 num_wait_semaphores = wait_semaphore ? 2U : 1U;
- const std::array wait_values{signal_value - 1, u64(1)};
- const std::array wait_semaphores{timeline_semaphore, wait_semaphore};
- static constexpr std::array<VkPipelineStageFlags, 2> wait_stage_masks{
- VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
- VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
- };
- const VkTimelineSemaphoreSubmitInfo timeline_si{
- .sType = VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO,
- .pNext = nullptr,
- .waitSemaphoreValueCount = num_wait_semaphores,
- .pWaitSemaphoreValues = wait_values.data(),
- .signalSemaphoreValueCount = num_signal_semaphores,
- .pSignalSemaphoreValues = signal_values.data(),
- };
- const VkSubmitInfo submit_info{
- .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
- .pNext = &timeline_si,
- .waitSemaphoreCount = num_wait_semaphores,
- .pWaitSemaphores = wait_semaphores.data(),
- .pWaitDstStageMask = wait_stage_masks.data(),
- .commandBufferCount = 1,
- .pCommandBuffers = cmdbuf.address(),
- .signalSemaphoreCount = num_signal_semaphores,
- .pSignalSemaphores = signal_semaphores.data(),
- };
- if (on_submit) {
- on_submit();
- }
- switch (const VkResult result = device.GetGraphicsQueue().Submit(submit_info)) {
- case VK_SUCCESS:
- break;
- case VK_ERROR_DEVICE_LOST:
- device.ReportLoss();
- [[fallthrough]];
- default:
- vk::Check(result);
- break;
- }
- });
- chunk->MarkSubmit();
- DispatchWork();
- }
- void Scheduler::AllocateNewContext() {
- // Enable counters once again. These are disabled when a command buffer is finished.
- if (query_cache) {
- query_cache->UpdateCounters();
- }
- }
- void Scheduler::InvalidateState() {
- state.graphics_pipeline = nullptr;
- state.rescaling_defined = false;
- state_tracker.InvalidateCommandBufferState();
- }
- void Scheduler::EndPendingOperations() {
- query_cache->DisableStreams();
- EndRenderPass();
- }
- void Scheduler::EndRenderPass() {
- if (!state.renderpass) {
- return;
- }
- Record([num_images = num_renderpass_images, images = renderpass_images,
- ranges = renderpass_image_ranges](vk::CommandBuffer cmdbuf) {
- std::array<VkImageMemoryBarrier, 9> barriers;
- for (size_t i = 0; i < num_images; ++i) {
- barriers[i] = VkImageMemoryBarrier{
- .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
- .pNext = nullptr,
- .srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT |
- VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT,
- .dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT |
- VK_ACCESS_COLOR_ATTACHMENT_READ_BIT |
- VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT |
- VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT |
- VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT,
- .oldLayout = VK_IMAGE_LAYOUT_GENERAL,
- .newLayout = VK_IMAGE_LAYOUT_GENERAL,
- .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
- .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
- .image = images[i],
- .subresourceRange = ranges[i],
- };
- }
- cmdbuf.EndRenderPass();
- cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT |
- VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT |
- VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
- VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, nullptr, nullptr,
- vk::Span(barriers.data(), num_images));
- });
- state.renderpass = nullptr;
- num_renderpass_images = 0;
- }
- void Scheduler::AcquireNewChunk() {
- std::scoped_lock rl{reserve_mutex};
- if (chunk_reserve.empty()) {
- // If we don't have anything reserved, we need to make a new chunk.
- chunk = std::make_unique<CommandChunk>();
- } else {
- // Otherwise, we can just take from the reserve.
- chunk = std::make_unique<CommandChunk>();
- chunk_reserve.pop_back();
- }
- }
- } // namespace Vulkan
|