diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index 327db68a54..929b8b7443 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt @@ -17,6 +17,8 @@ add_library(video_core STATIC engines/shader_header.h gpu.cpp gpu.h + gpu_thread.cpp + gpu_thread.h macro_interpreter.cpp macro_interpreter.h memory_manager.cpp diff --git a/src/video_core/gpu.cpp b/src/video_core/gpu.cpp index 48e88fd04b..7947bbff39 100644 --- a/src/video_core/gpu.cpp +++ b/src/video_core/gpu.cpp @@ -3,12 +3,14 @@ // Refer to the license.txt file included. #include "common/assert.h" +#include "core/settings.h" #include "video_core/engines/fermi_2d.h" #include "video_core/engines/kepler_memory.h" #include "video_core/engines/maxwell_3d.h" #include "video_core/engines/maxwell_compute.h" #include "video_core/engines/maxwell_dma.h" #include "video_core/gpu.h" +#include "video_core/gpu_thread.h" #include "video_core/renderer_base.h" namespace Tegra { @@ -33,6 +35,10 @@ GPU::GPU(VideoCore::RendererBase& renderer) : renderer{renderer} { maxwell_compute = std::make_unique(); maxwell_dma = std::make_unique(rasterizer, *memory_manager); kepler_memory = std::make_unique(rasterizer, *memory_manager); + + if (Settings::values.use_asynchronous_gpu_emulation) { + gpu_thread = std::make_unique(renderer, *dma_pusher); + } } GPU::~GPU() = default; @@ -62,14 +68,30 @@ const DmaPusher& GPU::DmaPusher() const { } void GPU::PushGPUEntries(Tegra::CommandList&& entries) { - dma_pusher->Push(std::move(entries)); - dma_pusher->QueuePendingCalls(); - dma_pusher->DispatchCalls(); + if (Settings::values.use_asynchronous_gpu_emulation) { + gpu_thread->PushGPUEntries(std::move(entries)); + } else { + dma_pusher->Push(std::move(entries)); + dma_pusher->QueuePendingCalls(); + dma_pusher->DispatchCalls(); + } } void GPU::SwapBuffers( std::optional> framebuffer) { - renderer.SwapBuffers(std::move(framebuffer)); + if (Settings::values.use_asynchronous_gpu_emulation) { + gpu_thread->SwapBuffers(std::move(framebuffer)); + } else { + renderer.SwapBuffers(std::move(framebuffer)); + } +} + +void GPU::WaitUntilIdle(std::function callback) { + if (Settings::values.use_asynchronous_gpu_emulation) { + gpu_thread->WaitUntilIdle(std::move(callback)); + } else { + callback(); + } } u32 RenderTargetBytesPerPixel(RenderTargetFormat format) { diff --git a/src/video_core/gpu.h b/src/video_core/gpu.h index accae9eebc..2a15a649f2 100644 --- a/src/video_core/gpu.h +++ b/src/video_core/gpu.h @@ -5,6 +5,7 @@ #pragma once #include +#include #include #include #include "common/common_types.h" @@ -13,6 +14,7 @@ #include "video_core/memory_manager.h" namespace VideoCore { +class GPUThread; class RendererBase; } // namespace VideoCore @@ -163,9 +165,13 @@ public: void SwapBuffers( std::optional> framebuffer); + /// Waits the caller until the thread is idle, and then calls the callback + void WaitUntilIdle(std::function callback); + private: std::unique_ptr dma_pusher; std::unique_ptr memory_manager; + std::unique_ptr gpu_thread; /// Mapping of command subchannels to their bound engine ids. std::array bound_engines = {}; diff --git a/src/video_core/gpu_thread.cpp b/src/video_core/gpu_thread.cpp new file mode 100644 index 0000000000..03e8a8370b --- /dev/null +++ b/src/video_core/gpu_thread.cpp @@ -0,0 +1,115 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/frontend/scope_acquire_window_context.h" +#include "video_core/gpu.h" +#include "video_core/gpu_thread.h" +#include "video_core/renderer_base.h" + +namespace { +static void RunThread(VideoCore::RendererBase& renderer, Tegra::DmaPusher& dma_pusher, + VideoCore::GPUThreadState& state) { + + Core::Frontend::ScopeAcquireWindowContext acquire_context{renderer.GetRenderWindow()}; + + while (state.is_running) { + bool is_dma_pending{}; + bool is_swapbuffers_pending{}; + + { + // Wait for CPU thread to send GPU commands + std::unique_lock lock{state.signal_mutex}; + state.signal_condition.wait(lock, [&] { + return state.is_dma_pending || state.is_swapbuffers_pending || !state.is_running; + }); + + if (!state.is_running) { + return; + } + + is_dma_pending = state.is_dma_pending; + is_swapbuffers_pending = state.is_swapbuffers_pending; + + if (is_dma_pending) { + dma_pusher.QueuePendingCalls(); + state.is_dma_pending = false; + } + } + + if (is_dma_pending) { + // Process pending DMA pushbuffer commands + std::lock_guard lock{state.running_mutex}; + dma_pusher.DispatchCalls(); + } + + if (is_swapbuffers_pending) { + // Process pending SwapBuffers + renderer.SwapBuffers(state.pending_swapbuffers_config); + state.is_swapbuffers_pending = false; + state.signal_condition.notify_one(); + } + } +} +} // Anonymous namespace + +namespace VideoCore { + +GPUThread::GPUThread(RendererBase& renderer, Tegra::DmaPusher& dma_pusher) + : dma_pusher{dma_pusher} { + thread = std::make_unique(RunThread, std::ref(renderer), std::ref(dma_pusher), + std::ref(state)); +} + +GPUThread::~GPUThread() { + { + // 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 GPUThread::PushGPUEntries(Tegra::CommandList&& entries) { + if (entries.empty()) { + return; + } + + { + // Notify GPU thread that data is available + std::lock_guard lock{state.signal_mutex}; + dma_pusher.Push(std::move(entries)); + state.is_dma_pending = true; + } + + state.signal_condition.notify_one(); +} + +void GPUThread::SwapBuffers( + std::optional> framebuffer) { + + { + // Notify GPU thread that we should SwapBuffers + std::lock_guard lock{state.signal_mutex}; + state.pending_swapbuffers_config = framebuffer; + state.is_swapbuffers_pending = true; + } + + state.signal_condition.notify_one(); + + { + // Wait for SwapBuffers + std::unique_lock lock{state.signal_mutex}; + state.signal_condition.wait(lock, [this] { return !state.is_swapbuffers_pending; }); + } +} + +void GPUThread::WaitUntilIdle(std::function callback) { + // Needs to be a recursive mutex, as this can be called by the GPU thread + std::unique_lock lock{state.running_mutex}; + callback(); +} + +} // namespace VideoCore diff --git a/src/video_core/gpu_thread.h b/src/video_core/gpu_thread.h new file mode 100644 index 0000000000..c8876738cd --- /dev/null +++ b/src/video_core/gpu_thread.h @@ -0,0 +1,56 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "video_core/dma_pusher.h" + +namespace Tegra { +struct FramebufferConfig; +} + +namespace VideoCore { + +class RendererBase; + +struct GPUThreadState final { + bool is_running{true}; + bool is_dma_pending{}; + bool is_swapbuffers_pending{}; + std::optional pending_swapbuffers_config; + std::condition_variable signal_condition; + std::condition_variable running_condition; + std::mutex signal_mutex; + std::recursive_mutex running_mutex; +}; + +class GPUThread final { +public: + explicit GPUThread(RendererBase& renderer, Tegra::DmaPusher& dma_pusher); + ~GPUThread(); + + /// Push GPU command entries to be processed + void PushGPUEntries(Tegra::CommandList&& entries); + + /// Swap buffers (render frame) + void SwapBuffers( + std::optional> framebuffer); + + /// Waits the caller until the thread is idle, and then calls the callback + void WaitUntilIdle(std::function callback); + +private: + GPUThreadState state; + std::unique_ptr thread; + Tegra::DmaPusher& dma_pusher; +}; + +} // namespace VideoCore diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp index 40db7a5e92..fd466c90ec 100644 --- a/src/yuzu/bootmanager.cpp +++ b/src/yuzu/bootmanager.cpp @@ -21,7 +21,7 @@ EmuThread::EmuThread(GRenderWindow* render_window) : render_window(render_window) {} void EmuThread::run() { - if (!Settings::values.use_multi_core) { + if (!Settings::values.use_asynchronous_gpu_emulation) { // Single core mode must acquire OpenGL context for entire emulation session render_window->MakeCurrent(); }