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 "common/thread.h"
8 #include "core/core.h"
9 #include "core/frontend/emu_window.h"
10 #include "core/settings.h"
11 #include "video_core/dma_pusher.h"
12 #include "video_core/gpu.h"
13 #include "video_core/gpu_thread.h"
14 #include "video_core/renderer_base.h"
15 
16 namespace VideoCommon::GPUThread {
17 
18 /// Runs the GPU thread
RunThread(Core::System & system,VideoCore::RendererBase & renderer,Core::Frontend::GraphicsContext & context,Tegra::DmaPusher & dma_pusher,SynchState & state,Tegra::CDmaPusher & cdma_pusher)19 static void RunThread(Core::System& system, VideoCore::RendererBase& renderer,
20                       Core::Frontend::GraphicsContext& context, Tegra::DmaPusher& dma_pusher,
21                       SynchState& state, Tegra::CDmaPusher& cdma_pusher) {
22     std::string name = "yuzu:GPU";
23     MicroProfileOnThreadCreate(name.c_str());
24     Common::SetCurrentThreadName(name.c_str());
25     Common::SetCurrentThreadPriority(Common::ThreadPriority::High);
26     system.RegisterHostThread();
27 
28     // Wait for first GPU command before acquiring the window context
29     while (state.queue.Empty())
30         ;
31 
32     // If emulation was stopped during disk shader loading, abort before trying to acquire context
33     if (!state.is_running) {
34         return;
35     }
36 
37     auto current_context = context.Acquire();
38 
39     CommandDataContainer next;
40     while (state.is_running) {
41         next = state.queue.PopWait();
42         if (auto* submit_list = std::get_if<SubmitListCommand>(&next.data)) {
43             dma_pusher.Push(std::move(submit_list->entries));
44             dma_pusher.DispatchCalls();
45         } else if (auto* command_list = std::get_if<SubmitChCommandEntries>(&next.data)) {
46             // NVDEC
47             cdma_pusher.Push(std::move(command_list->entries));
48             cdma_pusher.DispatchCalls();
49         } else if (const auto* data = std::get_if<SwapBuffersCommand>(&next.data)) {
50             renderer.SwapBuffers(data->framebuffer ? &*data->framebuffer : nullptr);
51         } else if (std::holds_alternative<OnCommandListEndCommand>(next.data)) {
52             renderer.Rasterizer().ReleaseFences();
53         } else if (std::holds_alternative<GPUTickCommand>(next.data)) {
54             system.GPU().TickWork();
55         } else if (const auto* flush = std::get_if<FlushRegionCommand>(&next.data)) {
56             renderer.Rasterizer().FlushRegion(flush->addr, flush->size);
57         } else if (const auto* invalidate = std::get_if<InvalidateRegionCommand>(&next.data)) {
58             renderer.Rasterizer().OnCPUWrite(invalidate->addr, invalidate->size);
59         } else if (std::holds_alternative<EndProcessingCommand>(next.data)) {
60             return;
61         } else {
62             UNREACHABLE();
63         }
64         state.signaled_fence.store(next.fence);
65     }
66 }
67 
ThreadManager(Core::System & system_)68 ThreadManager::ThreadManager(Core::System& system_) : system{system_} {}
69 
~ThreadManager()70 ThreadManager::~ThreadManager() {
71     if (!thread.joinable()) {
72         return;
73     }
74 
75     // Notify GPU thread that a shutdown is pending
76     PushCommand(EndProcessingCommand());
77     thread.join();
78 }
79 
StartThread(VideoCore::RendererBase & renderer,Core::Frontend::GraphicsContext & context,Tegra::DmaPusher & dma_pusher,Tegra::CDmaPusher & cdma_pusher)80 void ThreadManager::StartThread(VideoCore::RendererBase& renderer,
81                                 Core::Frontend::GraphicsContext& context,
82                                 Tegra::DmaPusher& dma_pusher, Tegra::CDmaPusher& cdma_pusher) {
83     thread = std::thread(RunThread, std::ref(system), std::ref(renderer), std::ref(context),
84                          std::ref(dma_pusher), std::ref(state), std::ref(cdma_pusher));
85 }
86 
SubmitList(Tegra::CommandList && entries)87 void ThreadManager::SubmitList(Tegra::CommandList&& entries) {
88     PushCommand(SubmitListCommand(std::move(entries)));
89 }
90 
SubmitCommandBuffer(Tegra::ChCommandHeaderList && entries)91 void ThreadManager::SubmitCommandBuffer(Tegra::ChCommandHeaderList&& entries) {
92     PushCommand(SubmitChCommandEntries(std::move(entries)));
93 }
94 
SwapBuffers(const Tegra::FramebufferConfig * framebuffer)95 void ThreadManager::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) {
96     PushCommand(SwapBuffersCommand(framebuffer ? std::make_optional(*framebuffer) : std::nullopt));
97 }
98 
FlushRegion(VAddr addr,u64 size)99 void ThreadManager::FlushRegion(VAddr addr, u64 size) {
100     if (!Settings::IsGPULevelHigh()) {
101         PushCommand(FlushRegionCommand(addr, size));
102         return;
103     }
104     if (!Settings::IsGPULevelExtreme()) {
105         return;
106     }
107     if (system.Renderer().Rasterizer().MustFlushRegion(addr, size)) {
108         auto& gpu = system.GPU();
109         u64 fence = gpu.RequestFlush(addr, size);
110         PushCommand(GPUTickCommand());
111         while (fence > gpu.CurrentFlushRequestFence()) {
112         }
113     }
114 }
115 
InvalidateRegion(VAddr addr,u64 size)116 void ThreadManager::InvalidateRegion(VAddr addr, u64 size) {
117     system.Renderer().Rasterizer().OnCPUWrite(addr, size);
118 }
119 
FlushAndInvalidateRegion(VAddr addr,u64 size)120 void ThreadManager::FlushAndInvalidateRegion(VAddr addr, u64 size) {
121     // Skip flush on asynch mode, as FlushAndInvalidateRegion is not used for anything too important
122     system.Renderer().Rasterizer().OnCPUWrite(addr, size);
123 }
124 
WaitIdle() const125 void ThreadManager::WaitIdle() const {
126     while (state.last_fence > state.signaled_fence.load(std::memory_order_relaxed)) {
127     }
128 }
129 
OnCommandListEnd()130 void ThreadManager::OnCommandListEnd() {
131     PushCommand(OnCommandListEndCommand());
132 }
133 
PushCommand(CommandData && command_data)134 u64 ThreadManager::PushCommand(CommandData&& command_data) {
135     const u64 fence{++state.last_fence};
136     state.queue.Push(CommandDataContainer(std::move(command_data), fence));
137     return fence;
138 }
139 
140 } // namespace VideoCommon::GPUThread
141