summaryrefslogtreecommitdiff
path: root/src/video_core/gpu_thread.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/video_core/gpu_thread.cpp')
-rw-r--r--src/video_core/gpu_thread.cpp154
1 files changed, 154 insertions, 0 deletions
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 @@
1// Copyright 2019 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include "common/assert.h"
6#include "common/microprofile.h"
7#include "core/frontend/scope_acquire_window_context.h"
8#include "core/settings.h"
9#include "video_core/dma_pusher.h"
10#include "video_core/gpu.h"
11#include "video_core/gpu_thread.h"
12#include "video_core/renderer_base.h"
13
14namespace VideoCommon::GPUThread {
15
16/// Executes a single GPU thread command
17static void ExecuteCommand(CommandData* command, VideoCore::RendererBase& renderer,
18 Tegra::DmaPusher& dma_pusher) {
19 if (const auto submit_list = std::get_if<SubmitListCommand>(command)) {
20 dma_pusher.Push(std::move(submit_list->entries));
21 dma_pusher.DispatchCalls();
22 } else if (const auto data = std::get_if<SwapBuffersCommand>(command)) {
23 renderer.SwapBuffers(data->framebuffer);
24 } else if (const auto data = std::get_if<FlushRegionCommand>(command)) {
25 renderer.Rasterizer().FlushRegion(data->addr, data->size);
26 } else if (const auto data = std::get_if<InvalidateRegionCommand>(command)) {
27 renderer.Rasterizer().InvalidateRegion(data->addr, data->size);
28 } else if (const auto data = std::get_if<FlushAndInvalidateRegionCommand>(command)) {
29 renderer.Rasterizer().FlushAndInvalidateRegion(data->addr, data->size);
30 } else {
31 UNREACHABLE();
32 }
33}
34
35/// Runs the GPU thread
36static void RunThread(VideoCore::RendererBase& renderer, Tegra::DmaPusher& dma_pusher,
37 SynchState& state) {
38
39 MicroProfileOnThreadCreate("GpuThread");
40
41 auto WaitForWakeup = [&]() {
42 std::unique_lock<std::mutex> lock{state.signal_mutex};
43 state.signal_condition.wait(lock, [&] { return !state.IsIdle() || !state.is_running; });
44 };
45
46 // Wait for first GPU command before acquiring the window context
47 WaitForWakeup();
48
49 // If emulation was stopped during disk shader loading, abort before trying to acquire context
50 if (!state.is_running) {
51 return;
52 }
53
54 Core::Frontend::ScopeAcquireWindowContext acquire_context{renderer.GetRenderWindow()};
55
56 while (state.is_running) {
57 if (!state.is_running) {
58 return;
59 }
60
61 {
62 // Thread has been woken up, so make the previous write queue the next read queue
63 std::lock_guard<std::mutex> lock{state.signal_mutex};
64 std::swap(state.push_queue, state.pop_queue);
65 }
66
67 // Execute all of the GPU commands
68 while (!state.pop_queue->empty()) {
69 ExecuteCommand(&state.pop_queue->front(), renderer, dma_pusher);
70 state.pop_queue->pop();
71 }
72
73 // Signal that the GPU thread has finished processing commands
74 if (state.IsIdle()) {
75 state.idle_condition.notify_one();
76 }
77
78 // Wait for CPU thread to send more GPU commands
79 WaitForWakeup();
80 }
81}
82
83ThreadManager::ThreadManager(VideoCore::RendererBase& renderer, Tegra::DmaPusher& dma_pusher)
84 : renderer{renderer}, dma_pusher{dma_pusher}, thread{RunThread, std::ref(renderer),
85 std::ref(dma_pusher), std::ref(state)},
86 thread_id{thread.get_id()} {}
87
88ThreadManager::~ThreadManager() {
89 {
90 // Notify GPU thread that a shutdown is pending
91 std::lock_guard<std::mutex> lock{state.signal_mutex};
92 state.is_running = false;
93 }
94
95 state.signal_condition.notify_one();
96 thread.join();
97}
98
99void ThreadManager::SubmitList(Tegra::CommandList&& entries) {
100 if (entries.empty()) {
101 return;
102 }
103
104 PushCommand(SubmitListCommand(std::move(entries)), false, false);
105}
106
107void ThreadManager::SwapBuffers(
108 std::optional<std::reference_wrapper<const Tegra::FramebufferConfig>> framebuffer) {
109 PushCommand(SwapBuffersCommand(std::move(framebuffer)), true, false);
110}
111
112void ThreadManager::FlushRegion(VAddr addr, u64 size) {
113 if (Settings::values.use_accurate_gpu_emulation) {
114 PushCommand(FlushRegionCommand(addr, size), true, false);
115 }
116}
117
118void ThreadManager::InvalidateRegion(VAddr addr, u64 size) {
119 PushCommand(InvalidateRegionCommand(addr, size), true, true);
120}
121
122void ThreadManager::FlushAndInvalidateRegion(VAddr addr, u64 size) {
123 if (Settings::values.use_accurate_gpu_emulation) {
124 PushCommand(FlushAndInvalidateRegionCommand(addr, size), true, false);
125 } else {
126 InvalidateRegion(addr, size);
127 }
128}
129
130void ThreadManager::PushCommand(CommandData&& command_data, bool wait_for_idle, bool allow_on_cpu) {
131 {
132 std::lock_guard<std::mutex> lock{state.signal_mutex};
133
134 if ((allow_on_cpu && state.IsIdle()) || IsGpuThread()) {
135 // Execute the command synchronously on the current thread
136 ExecuteCommand(&command_data, renderer, dma_pusher);
137 return;
138 }
139
140 // Push the command to the GPU thread
141 state.push_queue->emplace(command_data);
142 }
143
144 // Signal the GPU thread that commands are pending
145 state.signal_condition.notify_one();
146
147 if (wait_for_idle) {
148 // Wait for the GPU to be idle (all commands to be executed)
149 std::unique_lock<std::mutex> lock{state.idle_mutex};
150 state.idle_condition.wait(lock, [this] { return state.IsIdle(); });
151 }
152}
153
154} // namespace VideoCommon::GPUThread