// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include #include #include #include #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(device)}, command_pool{std::make_unique(*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) { SubmitExecution(signal_semaphore, wait_semaphore); AllocateNewContext(); } void Scheduler::Finish(VkSemaphore signal_semaphore, VkSemaphore wait_semaphore) { const u64 presubmit_tick = CurrentTick(); SubmitExecution(signal_semaphore, wait_semaphore); WaitWorker(); Wait(presubmit_tick); AllocateNewContext(); } void Scheduler::WaitWorker() { MICROPROFILE_SCOPE(Vulkan_WaitForWorker); DispatchWork(); std::unique_lock lock{work_mutex}; wait_cv.wait(lock, [this] { return work_queue.empty(); }); } void Scheduler::DispatchWork() { if (chunk->Empty()) { return; } { std::scoped_lock lock{work_mutex}; work_queue.push(std::move(chunk)); } work_cv.notify_one(); 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"); do { std::unique_ptr work; bool has_submit{false}; { std::unique_lock lock{work_mutex}; if (work_queue.empty()) { wait_cv.notify_all(); } work_cv.wait(lock, stop_token, [this] { return !work_queue.empty(); }); if (stop_token.stop_requested()) { continue; } work = std::move(work_queue.front()); work_queue.pop(); has_submit = work->HasSubmit(); work->ExecuteAll(current_cmdbuf); } if (has_submit) { AllocateWorkerCommandBuffer(); } std::scoped_lock reserve_lock{reserve_mutex}; chunk_reserve.push_back(std::move(work)); } while (!stop_token.stop_requested()); } 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 wait_stage_masks{ VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, }; const VkTimelineSemaphoreSubmitInfoKHR timeline_si{ .sType = VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO_KHR, .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(), }; 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 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 lock{reserve_mutex}; if (chunk_reserve.empty()) { chunk = std::make_unique(); return; } chunk = std::move(chunk_reserve.back()); chunk_reserve.pop_back(); } } // namespace Vulkan