From 7b574f406b25c02a0e0efd8b7ec13d68ecb55497 Mon Sep 17 00:00:00 2001 From: bunnei Date: Wed, 23 Jan 2019 22:17:55 -0500 Subject: gpu: Move command processing to another thread. --- src/video_core/gpu_thread.cpp | 154 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 154 insertions(+) create mode 100644 src/video_core/gpu_thread.cpp (limited to 'src/video_core/gpu_thread.cpp') diff --git a/src/video_core/gpu_thread.cpp b/src/video_core/gpu_thread.cpp new file mode 100644 index 000000000..22c4cca4d --- /dev/null +++ b/src/video_core/gpu_thread.cpp @@ -0,0 +1,154 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/assert.h" +#include "common/microprofile.h" +#include "core/frontend/scope_acquire_window_context.h" +#include "core/settings.h" +#include "video_core/dma_pusher.h" +#include "video_core/gpu.h" +#include "video_core/gpu_thread.h" +#include "video_core/renderer_base.h" + +namespace VideoCommon::GPUThread { + +/// Executes a single GPU thread command +static void ExecuteCommand(CommandData* command, VideoCore::RendererBase& renderer, + Tegra::DmaPusher& dma_pusher) { + if (const auto submit_list = std::get_if(command)) { + dma_pusher.Push(std::move(submit_list->entries)); + dma_pusher.DispatchCalls(); + } else if (const auto data = std::get_if(command)) { + renderer.SwapBuffers(data->framebuffer); + } else if (const auto data = std::get_if(command)) { + renderer.Rasterizer().FlushRegion(data->addr, data->size); + } else if (const auto data = std::get_if(command)) { + renderer.Rasterizer().InvalidateRegion(data->addr, data->size); + } else if (const auto data = std::get_if(command)) { + renderer.Rasterizer().FlushAndInvalidateRegion(data->addr, data->size); + } else { + UNREACHABLE(); + } +} + +/// Runs the GPU thread +static void RunThread(VideoCore::RendererBase& renderer, Tegra::DmaPusher& dma_pusher, + SynchState& state) { + + MicroProfileOnThreadCreate("GpuThread"); + + auto WaitForWakeup = [&]() { + std::unique_lock lock{state.signal_mutex}; + state.signal_condition.wait(lock, [&] { return !state.IsIdle() || !state.is_running; }); + }; + + // Wait for first GPU command before acquiring the window context + WaitForWakeup(); + + // If emulation was stopped during disk shader loading, abort before trying to acquire context + if (!state.is_running) { + return; + } + + Core::Frontend::ScopeAcquireWindowContext acquire_context{renderer.GetRenderWindow()}; + + while (state.is_running) { + if (!state.is_running) { + return; + } + + { + // Thread has been woken up, so make the previous write queue the next read queue + std::lock_guard lock{state.signal_mutex}; + std::swap(state.push_queue, state.pop_queue); + } + + // Execute all of the GPU commands + while (!state.pop_queue->empty()) { + ExecuteCommand(&state.pop_queue->front(), renderer, dma_pusher); + state.pop_queue->pop(); + } + + // Signal that the GPU thread has finished processing commands + if (state.IsIdle()) { + state.idle_condition.notify_one(); + } + + // Wait for CPU thread to send more GPU commands + WaitForWakeup(); + } +} + +ThreadManager::ThreadManager(VideoCore::RendererBase& renderer, Tegra::DmaPusher& dma_pusher) + : renderer{renderer}, dma_pusher{dma_pusher}, thread{RunThread, std::ref(renderer), + std::ref(dma_pusher), std::ref(state)}, + thread_id{thread.get_id()} {} + +ThreadManager::~ThreadManager() { + { + // Notify GPU thread that a shutdown is pending + std::lock_guard lock{state.signal_mutex}; + state.is_running = false; + } + + state.signal_condition.notify_one(); + thread.join(); +} + +void ThreadManager::SubmitList(Tegra::CommandList&& entries) { + if (entries.empty()) { + return; + } + + PushCommand(SubmitListCommand(std::move(entries)), false, false); +} + +void ThreadManager::SwapBuffers( + std::optional> framebuffer) { + PushCommand(SwapBuffersCommand(std::move(framebuffer)), true, false); +} + +void ThreadManager::FlushRegion(VAddr addr, u64 size) { + if (Settings::values.use_accurate_gpu_emulation) { + PushCommand(FlushRegionCommand(addr, size), true, false); + } +} + +void ThreadManager::InvalidateRegion(VAddr addr, u64 size) { + PushCommand(InvalidateRegionCommand(addr, size), true, true); +} + +void ThreadManager::FlushAndInvalidateRegion(VAddr addr, u64 size) { + if (Settings::values.use_accurate_gpu_emulation) { + PushCommand(FlushAndInvalidateRegionCommand(addr, size), true, false); + } else { + InvalidateRegion(addr, size); + } +} + +void ThreadManager::PushCommand(CommandData&& command_data, bool wait_for_idle, bool allow_on_cpu) { + { + std::lock_guard lock{state.signal_mutex}; + + if ((allow_on_cpu && state.IsIdle()) || IsGpuThread()) { + // Execute the command synchronously on the current thread + ExecuteCommand(&command_data, renderer, dma_pusher); + return; + } + + // Push the command to the GPU thread + state.push_queue->emplace(command_data); + } + + // Signal the GPU thread that commands are pending + state.signal_condition.notify_one(); + + if (wait_for_idle) { + // Wait for the GPU to be idle (all commands to be executed) + std::unique_lock lock{state.idle_mutex}; + state.idle_condition.wait(lock, [this] { return state.IsIdle(); }); + } +} + +} // namespace VideoCommon::GPUThread -- cgit v1.2.3 From 3f1b4fb23ad7e689941b5a01afa15780bc50b77b Mon Sep 17 00:00:00 2001 From: bunnei Date: Sat, 9 Feb 2019 00:06:35 -0500 Subject: gpu: Always flush. --- src/video_core/gpu_thread.cpp | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) (limited to 'src/video_core/gpu_thread.cpp') diff --git a/src/video_core/gpu_thread.cpp b/src/video_core/gpu_thread.cpp index 22c4cca4d..7640da6c0 100644 --- a/src/video_core/gpu_thread.cpp +++ b/src/video_core/gpu_thread.cpp @@ -110,9 +110,8 @@ void ThreadManager::SwapBuffers( } void ThreadManager::FlushRegion(VAddr addr, u64 size) { - if (Settings::values.use_accurate_gpu_emulation) { - PushCommand(FlushRegionCommand(addr, size), true, false); - } + // Block the CPU when using accurate emulation + PushCommand(FlushRegionCommand(addr, size), Settings::values.use_accurate_gpu_emulation, false); } void ThreadManager::InvalidateRegion(VAddr addr, u64 size) { @@ -120,11 +119,9 @@ void ThreadManager::InvalidateRegion(VAddr addr, u64 size) { } void ThreadManager::FlushAndInvalidateRegion(VAddr addr, u64 size) { - if (Settings::values.use_accurate_gpu_emulation) { - PushCommand(FlushAndInvalidateRegionCommand(addr, size), true, false); - } else { - InvalidateRegion(addr, size); - } + // Block the CPU when using accurate emulation + PushCommand(FlushAndInvalidateRegionCommand(addr, size), + Settings::values.use_accurate_gpu_emulation, false); } void ThreadManager::PushCommand(CommandData&& command_data, bool wait_for_idle, bool allow_on_cpu) { -- cgit v1.2.3 From 63aa08acbe1da5b78d9f0b37088081a24591849f Mon Sep 17 00:00:00 2001 From: bunnei Date: Sat, 9 Feb 2019 02:55:45 -0500 Subject: gpu_thread: (HACK) Ignore flush on FlushAndInvalidateRegion. --- src/video_core/gpu_thread.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'src/video_core/gpu_thread.cpp') diff --git a/src/video_core/gpu_thread.cpp b/src/video_core/gpu_thread.cpp index 7640da6c0..4c25380eb 100644 --- a/src/video_core/gpu_thread.cpp +++ b/src/video_core/gpu_thread.cpp @@ -119,9 +119,7 @@ void ThreadManager::InvalidateRegion(VAddr addr, u64 size) { } void ThreadManager::FlushAndInvalidateRegion(VAddr addr, u64 size) { - // Block the CPU when using accurate emulation - PushCommand(FlushAndInvalidateRegionCommand(addr, size), - Settings::values.use_accurate_gpu_emulation, false); + InvalidateRegion(addr, size); } void ThreadManager::PushCommand(CommandData&& command_data, bool wait_for_idle, bool allow_on_cpu) { -- cgit v1.2.3 From 84ad81ee6798ece6c66016c4581b5fe57ce7b20e Mon Sep 17 00:00:00 2001 From: bunnei Date: Sat, 9 Feb 2019 11:37:11 -0500 Subject: gpu_thread: Fix deadlock with threading idle state check. --- src/video_core/gpu_thread.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) (limited to 'src/video_core/gpu_thread.cpp') diff --git a/src/video_core/gpu_thread.cpp b/src/video_core/gpu_thread.cpp index 4c25380eb..c5bdd2a17 100644 --- a/src/video_core/gpu_thread.cpp +++ b/src/video_core/gpu_thread.cpp @@ -40,7 +40,7 @@ static void RunThread(VideoCore::RendererBase& renderer, Tegra::DmaPusher& dma_p auto WaitForWakeup = [&]() { std::unique_lock lock{state.signal_mutex}; - state.signal_condition.wait(lock, [&] { return !state.IsIdle() || !state.is_running; }); + state.signal_condition.wait(lock, [&] { return !state.is_idle || !state.is_running; }); }; // Wait for first GPU command before acquiring the window context @@ -70,8 +70,10 @@ static void RunThread(VideoCore::RendererBase& renderer, Tegra::DmaPusher& dma_p state.pop_queue->pop(); } + state.UpdateIdleState(); + // Signal that the GPU thread has finished processing commands - if (state.IsIdle()) { + if (state.is_idle) { state.idle_condition.notify_one(); } @@ -126,13 +128,14 @@ void ThreadManager::PushCommand(CommandData&& command_data, bool wait_for_idle, { std::lock_guard lock{state.signal_mutex}; - if ((allow_on_cpu && state.IsIdle()) || IsGpuThread()) { + if ((allow_on_cpu && state.is_idle) || IsGpuThread()) { // Execute the command synchronously on the current thread ExecuteCommand(&command_data, renderer, dma_pusher); return; } // Push the command to the GPU thread + state.UpdateIdleState(); state.push_queue->emplace(command_data); } @@ -142,7 +145,7 @@ void ThreadManager::PushCommand(CommandData&& command_data, bool wait_for_idle, if (wait_for_idle) { // Wait for the GPU to be idle (all commands to be executed) std::unique_lock lock{state.idle_mutex}; - state.idle_condition.wait(lock, [this] { return state.IsIdle(); }); + state.idle_condition.wait(lock, [this] { return static_cast(state.is_idle); }); } } -- cgit v1.2.3