summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGravatar bunnei2020-02-27 19:51:55 -0500
committerGravatar GitHub2020-02-27 19:51:55 -0500
commit969357af1a26e74eaa5b0cec677d929bca94dc57 (patch)
tree59a8bb28215dfbdc817227e4b0b7ea37e6f5ef70 /src
parentAM/ICommonStateGetter: Stub SetLcdBacklighOffEnabled (#3454) (diff)
parentrenderer_opengl: Reduce swap chain size to 3. (diff)
downloadyuzu-969357af1a26e74eaa5b0cec677d929bca94dc57.tar.gz
yuzu-969357af1a26e74eaa5b0cec677d929bca94dc57.tar.xz
yuzu-969357af1a26e74eaa5b0cec677d929bca94dc57.zip
Merge pull request #3430 from bunnei/split-presenter
Port citra-emu/citra#4940: "Split Presentation thread from Render thread"
Diffstat (limited to 'src')
-rw-r--r--src/core/CMakeLists.txt4
-rw-r--r--src/core/core.cpp3
-rw-r--r--src/core/frontend/emu_window.h3
-rw-r--r--src/core/frontend/framebuffer_layout.h1
-rw-r--r--src/core/frontend/scope_acquire_context.cpp18
-rw-r--r--src/core/frontend/scope_acquire_context.h (renamed from src/core/frontend/scope_acquire_window_context.h)10
-rw-r--r--src/core/frontend/scope_acquire_window_context.cpp18
-rw-r--r--src/core/settings.cpp1
-rw-r--r--src/core/settings.h1
-rw-r--r--src/core/telemetry_session.cpp1
-rw-r--r--src/video_core/gpu_thread.cpp4
-rw-r--r--src/video_core/renderer_base.h10
-rw-r--r--src/video_core/renderer_opengl/gl_resource_manager.cpp18
-rw-r--r--src/video_core/renderer_opengl/gl_resource_manager.h25
-rw-r--r--src/video_core/renderer_opengl/gl_state.cpp15
-rw-r--r--src/video_core/renderer_opengl/gl_state.h4
-rw-r--r--src/video_core/renderer_opengl/renderer_opengl.cpp272
-rw-r--r--src/video_core/renderer_opengl/renderer_opengl.h29
-rw-r--r--src/video_core/renderer_vulkan/renderer_vulkan.cpp15
-rw-r--r--src/video_core/renderer_vulkan/renderer_vulkan.h8
-rw-r--r--src/yuzu/bootmanager.cpp420
-rw-r--r--src/yuzu/bootmanager.h67
-rw-r--r--src/yuzu/configuration/config.cpp2
-rw-r--r--src/yuzu/configuration/configure_graphics.cpp3
-rw-r--r--src/yuzu/configuration/configure_graphics.ui10
-rw-r--r--src/yuzu/main.cpp49
-rw-r--r--src/yuzu/main.h7
-rw-r--r--src/yuzu_cmd/config.cpp2
-rw-r--r--src/yuzu_cmd/default_ini.h5
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2.cpp2
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2.h14
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp54
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h18
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp9
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2_vk.h11
-rw-r--r--src/yuzu_cmd/yuzu.cpp25
-rw-r--r--src/yuzu_tester/emu_window/emu_window_sdl2_hide.cpp4
-rw-r--r--src/yuzu_tester/emu_window/emu_window_sdl2_hide.h3
38 files changed, 787 insertions, 378 deletions
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 88c06b2ce..54be7dc0c 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -131,8 +131,8 @@ add_library(core STATIC
131 frontend/framebuffer_layout.cpp 131 frontend/framebuffer_layout.cpp
132 frontend/framebuffer_layout.h 132 frontend/framebuffer_layout.h
133 frontend/input.h 133 frontend/input.h
134 frontend/scope_acquire_window_context.cpp 134 frontend/scope_acquire_context.cpp
135 frontend/scope_acquire_window_context.h 135 frontend/scope_acquire_context.h
136 gdbstub/gdbstub.cpp 136 gdbstub/gdbstub.cpp
137 gdbstub/gdbstub.h 137 gdbstub/gdbstub.h
138 hardware_interrupt_manager.cpp 138 hardware_interrupt_manager.cpp
diff --git a/src/core/core.cpp b/src/core/core.cpp
index 86e314c94..a82faf127 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -24,6 +24,7 @@
24#include "core/file_sys/sdmc_factory.h" 24#include "core/file_sys/sdmc_factory.h"
25#include "core/file_sys/vfs_concat.h" 25#include "core/file_sys/vfs_concat.h"
26#include "core/file_sys/vfs_real.h" 26#include "core/file_sys/vfs_real.h"
27#include "core/frontend/scope_acquire_context.h"
27#include "core/gdbstub/gdbstub.h" 28#include "core/gdbstub/gdbstub.h"
28#include "core/hardware_interrupt_manager.h" 29#include "core/hardware_interrupt_manager.h"
29#include "core/hle/kernel/client_port.h" 30#include "core/hle/kernel/client_port.h"
@@ -184,6 +185,8 @@ struct System::Impl {
184 185
185 ResultStatus Load(System& system, Frontend::EmuWindow& emu_window, 186 ResultStatus Load(System& system, Frontend::EmuWindow& emu_window,
186 const std::string& filepath) { 187 const std::string& filepath) {
188 Core::Frontend::ScopeAcquireContext acquire_context{emu_window};
189
187 app_loader = Loader::GetLoader(GetGameFileFromPath(virtual_filesystem, filepath)); 190 app_loader = Loader::GetLoader(GetGameFileFromPath(virtual_filesystem, filepath));
188 if (!app_loader) { 191 if (!app_loader) {
189 LOG_CRITICAL(Core, "Failed to obtain loader for {}!", filepath); 192 LOG_CRITICAL(Core, "Failed to obtain loader for {}!", filepath);
diff --git a/src/core/frontend/emu_window.h b/src/core/frontend/emu_window.h
index 3376eedc5..5eb87fb63 100644
--- a/src/core/frontend/emu_window.h
+++ b/src/core/frontend/emu_window.h
@@ -26,9 +26,6 @@ public:
26 26
27 /// Releases (dunno if this is the "right" word) the context from the caller thread 27 /// Releases (dunno if this is the "right" word) the context from the caller thread
28 virtual void DoneCurrent() = 0; 28 virtual void DoneCurrent() = 0;
29
30 /// Swap buffers to display the next frame
31 virtual void SwapBuffers() = 0;
32}; 29};
33 30
34/** 31/**
diff --git a/src/core/frontend/framebuffer_layout.h b/src/core/frontend/framebuffer_layout.h
index 1d39c1faf..e9d0a40d3 100644
--- a/src/core/frontend/framebuffer_layout.h
+++ b/src/core/frontend/framebuffer_layout.h
@@ -29,6 +29,7 @@ enum class AspectRatio {
29struct FramebufferLayout { 29struct FramebufferLayout {
30 u32 width{ScreenUndocked::Width}; 30 u32 width{ScreenUndocked::Width};
31 u32 height{ScreenUndocked::Height}; 31 u32 height{ScreenUndocked::Height};
32 bool is_srgb{};
32 33
33 Common::Rectangle<u32> screen; 34 Common::Rectangle<u32> screen;
34 35
diff --git a/src/core/frontend/scope_acquire_context.cpp b/src/core/frontend/scope_acquire_context.cpp
new file mode 100644
index 000000000..878c3157c
--- /dev/null
+++ b/src/core/frontend/scope_acquire_context.cpp
@@ -0,0 +1,18 @@
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 "core/frontend/emu_window.h"
6#include "core/frontend/scope_acquire_context.h"
7
8namespace Core::Frontend {
9
10ScopeAcquireContext::ScopeAcquireContext(Core::Frontend::GraphicsContext& context)
11 : context{context} {
12 context.MakeCurrent();
13}
14ScopeAcquireContext::~ScopeAcquireContext() {
15 context.DoneCurrent();
16}
17
18} // namespace Core::Frontend
diff --git a/src/core/frontend/scope_acquire_window_context.h b/src/core/frontend/scope_acquire_context.h
index 2d9f6e825..7a65c0623 100644
--- a/src/core/frontend/scope_acquire_window_context.h
+++ b/src/core/frontend/scope_acquire_context.h
@@ -8,16 +8,16 @@
8 8
9namespace Core::Frontend { 9namespace Core::Frontend {
10 10
11class EmuWindow; 11class GraphicsContext;
12 12
13/// Helper class to acquire/release window context within a given scope 13/// Helper class to acquire/release window context within a given scope
14class ScopeAcquireWindowContext : NonCopyable { 14class ScopeAcquireContext : NonCopyable {
15public: 15public:
16 explicit ScopeAcquireWindowContext(Core::Frontend::EmuWindow& window); 16 explicit ScopeAcquireContext(Core::Frontend::GraphicsContext& context);
17 ~ScopeAcquireWindowContext(); 17 ~ScopeAcquireContext();
18 18
19private: 19private:
20 Core::Frontend::EmuWindow& emu_window; 20 Core::Frontend::GraphicsContext& context;
21}; 21};
22 22
23} // namespace Core::Frontend 23} // namespace Core::Frontend
diff --git a/src/core/frontend/scope_acquire_window_context.cpp b/src/core/frontend/scope_acquire_window_context.cpp
deleted file mode 100644
index 3663dad17..000000000
--- a/src/core/frontend/scope_acquire_window_context.cpp
+++ /dev/null
@@ -1,18 +0,0 @@
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 "core/frontend/emu_window.h"
6#include "core/frontend/scope_acquire_window_context.h"
7
8namespace Core::Frontend {
9
10ScopeAcquireWindowContext::ScopeAcquireWindowContext(Core::Frontend::EmuWindow& emu_window_)
11 : emu_window{emu_window_} {
12 emu_window.MakeCurrent();
13}
14ScopeAcquireWindowContext::~ScopeAcquireWindowContext() {
15 emu_window.DoneCurrent();
16}
17
18} // namespace Core::Frontend
diff --git a/src/core/settings.cpp b/src/core/settings.cpp
index d1fc94060..7c0303684 100644
--- a/src/core/settings.cpp
+++ b/src/core/settings.cpp
@@ -94,6 +94,7 @@ void LogSettings() {
94 LogSetting("Renderer_UseAccurateGpuEmulation", Settings::values.use_accurate_gpu_emulation); 94 LogSetting("Renderer_UseAccurateGpuEmulation", Settings::values.use_accurate_gpu_emulation);
95 LogSetting("Renderer_UseAsynchronousGpuEmulation", 95 LogSetting("Renderer_UseAsynchronousGpuEmulation",
96 Settings::values.use_asynchronous_gpu_emulation); 96 Settings::values.use_asynchronous_gpu_emulation);
97 LogSetting("Renderer_UseVsync", Settings::values.use_vsync);
97 LogSetting("Audio_OutputEngine", Settings::values.sink_id); 98 LogSetting("Audio_OutputEngine", Settings::values.sink_id);
98 LogSetting("Audio_EnableAudioStretching", Settings::values.enable_audio_stretching); 99 LogSetting("Audio_EnableAudioStretching", Settings::values.enable_audio_stretching);
99 LogSetting("Audio_OutputDevice", Settings::values.audio_device_id); 100 LogSetting("Audio_OutputDevice", Settings::values.audio_device_id);
diff --git a/src/core/settings.h b/src/core/settings.h
index f837d3fbc..15b691342 100644
--- a/src/core/settings.h
+++ b/src/core/settings.h
@@ -435,6 +435,7 @@ struct Values {
435 bool use_disk_shader_cache; 435 bool use_disk_shader_cache;
436 bool use_accurate_gpu_emulation; 436 bool use_accurate_gpu_emulation;
437 bool use_asynchronous_gpu_emulation; 437 bool use_asynchronous_gpu_emulation;
438 bool use_vsync;
438 bool force_30fps_mode; 439 bool force_30fps_mode;
439 440
440 float bg_red; 441 float bg_red;
diff --git a/src/core/telemetry_session.cpp b/src/core/telemetry_session.cpp
index 0e72d31cd..0f3685d1c 100644
--- a/src/core/telemetry_session.cpp
+++ b/src/core/telemetry_session.cpp
@@ -188,6 +188,7 @@ void TelemetrySession::AddInitialInfo(Loader::AppLoader& app_loader) {
188 Settings::values.use_accurate_gpu_emulation); 188 Settings::values.use_accurate_gpu_emulation);
189 AddField(field_type, "Renderer_UseAsynchronousGpuEmulation", 189 AddField(field_type, "Renderer_UseAsynchronousGpuEmulation",
190 Settings::values.use_asynchronous_gpu_emulation); 190 Settings::values.use_asynchronous_gpu_emulation);
191 AddField(field_type, "Renderer_UseVsync", Settings::values.use_vsync);
191 AddField(field_type, "System_UseDockedMode", Settings::values.use_docked_mode); 192 AddField(field_type, "System_UseDockedMode", Settings::values.use_docked_mode);
192} 193}
193 194
diff --git a/src/video_core/gpu_thread.cpp b/src/video_core/gpu_thread.cpp
index 2cdf1aa7f..b1088af3d 100644
--- a/src/video_core/gpu_thread.cpp
+++ b/src/video_core/gpu_thread.cpp
@@ -5,7 +5,7 @@
5#include "common/assert.h" 5#include "common/assert.h"
6#include "common/microprofile.h" 6#include "common/microprofile.h"
7#include "core/core.h" 7#include "core/core.h"
8#include "core/frontend/scope_acquire_window_context.h" 8#include "core/frontend/scope_acquire_context.h"
9#include "video_core/dma_pusher.h" 9#include "video_core/dma_pusher.h"
10#include "video_core/gpu.h" 10#include "video_core/gpu.h"
11#include "video_core/gpu_thread.h" 11#include "video_core/gpu_thread.h"
@@ -27,7 +27,7 @@ static void RunThread(VideoCore::RendererBase& renderer, Tegra::DmaPusher& dma_p
27 return; 27 return;
28 } 28 }
29 29
30 Core::Frontend::ScopeAcquireWindowContext acquire_context{renderer.GetRenderWindow()}; 30 Core::Frontend::ScopeAcquireContext acquire_context{renderer.GetRenderWindow()};
31 31
32 CommandDataContainer next; 32 CommandDataContainer next;
33 while (state.is_running) { 33 while (state.is_running) {
diff --git a/src/video_core/renderer_base.h b/src/video_core/renderer_base.h
index af1bebc4f..5ec99a126 100644
--- a/src/video_core/renderer_base.h
+++ b/src/video_core/renderer_base.h
@@ -35,15 +35,19 @@ public:
35 explicit RendererBase(Core::Frontend::EmuWindow& window); 35 explicit RendererBase(Core::Frontend::EmuWindow& window);
36 virtual ~RendererBase(); 36 virtual ~RendererBase();
37 37
38 /// Swap buffers (render frame)
39 virtual void SwapBuffers(const Tegra::FramebufferConfig* framebuffer) = 0;
40
41 /// Initialize the renderer 38 /// Initialize the renderer
42 virtual bool Init() = 0; 39 virtual bool Init() = 0;
43 40
44 /// Shutdown the renderer 41 /// Shutdown the renderer
45 virtual void ShutDown() = 0; 42 virtual void ShutDown() = 0;
46 43
44 /// Finalize rendering the guest frame and draw into the presentation texture
45 virtual void SwapBuffers(const Tegra::FramebufferConfig* framebuffer) = 0;
46
47 /// Draws the latest frame to the window waiting timeout_ms for a frame to arrive (Renderer
48 /// specific implementation)
49 virtual void TryPresent(int timeout_ms) = 0;
50
47 // Getter/setter functions: 51 // Getter/setter functions:
48 // ------------------------ 52 // ------------------------
49 53
diff --git a/src/video_core/renderer_opengl/gl_resource_manager.cpp b/src/video_core/renderer_opengl/gl_resource_manager.cpp
index f0ddfb276..c0aee770f 100644
--- a/src/video_core/renderer_opengl/gl_resource_manager.cpp
+++ b/src/video_core/renderer_opengl/gl_resource_manager.cpp
@@ -15,6 +15,24 @@ MICROPROFILE_DEFINE(OpenGL_ResourceDeletion, "OpenGL", "Resource Deletion", MP_R
15 15
16namespace OpenGL { 16namespace OpenGL {
17 17
18void OGLRenderbuffer::Create() {
19 if (handle != 0)
20 return;
21
22 MICROPROFILE_SCOPE(OpenGL_ResourceCreation);
23 glGenRenderbuffers(1, &handle);
24}
25
26void OGLRenderbuffer::Release() {
27 if (handle == 0)
28 return;
29
30 MICROPROFILE_SCOPE(OpenGL_ResourceDeletion);
31 glDeleteRenderbuffers(1, &handle);
32 OpenGLState::GetCurState().ResetRenderbuffer(handle).Apply();
33 handle = 0;
34}
35
18void OGLTexture::Create(GLenum target) { 36void OGLTexture::Create(GLenum target) {
19 if (handle != 0) 37 if (handle != 0)
20 return; 38 return;
diff --git a/src/video_core/renderer_opengl/gl_resource_manager.h b/src/video_core/renderer_opengl/gl_resource_manager.h
index 514d1d165..995a4e45e 100644
--- a/src/video_core/renderer_opengl/gl_resource_manager.h
+++ b/src/video_core/renderer_opengl/gl_resource_manager.h
@@ -11,6 +11,31 @@
11 11
12namespace OpenGL { 12namespace OpenGL {
13 13
14class OGLRenderbuffer : private NonCopyable {
15public:
16 OGLRenderbuffer() = default;
17
18 OGLRenderbuffer(OGLRenderbuffer&& o) noexcept : handle(std::exchange(o.handle, 0)) {}
19
20 ~OGLRenderbuffer() {
21 Release();
22 }
23
24 OGLRenderbuffer& operator=(OGLRenderbuffer&& o) noexcept {
25 Release();
26 handle = std::exchange(o.handle, 0);
27 return *this;
28 }
29
30 /// Creates a new internal OpenGL resource and stores the handle
31 void Create();
32
33 /// Deletes the internal OpenGL resource
34 void Release();
35
36 GLuint handle = 0;
37};
38
14class OGLTexture : private NonCopyable { 39class OGLTexture : private NonCopyable {
15public: 40public:
16 OGLTexture() = default; 41 OGLTexture() = default;
diff --git a/src/video_core/renderer_opengl/gl_state.cpp b/src/video_core/renderer_opengl/gl_state.cpp
index ab1f7983c..7d3bc1a1f 100644
--- a/src/video_core/renderer_opengl/gl_state.cpp
+++ b/src/video_core/renderer_opengl/gl_state.cpp
@@ -423,6 +423,13 @@ void OpenGLState::ApplyClipControl() {
423 } 423 }
424} 424}
425 425
426void OpenGLState::ApplyRenderBuffer() {
427 if (cur_state.renderbuffer != renderbuffer) {
428 cur_state.renderbuffer = renderbuffer;
429 glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer);
430 }
431}
432
426void OpenGLState::ApplyTextures() { 433void OpenGLState::ApplyTextures() {
427 const std::size_t size = std::size(textures); 434 const std::size_t size = std::size(textures);
428 for (std::size_t i = 0; i < size; ++i) { 435 for (std::size_t i = 0; i < size; ++i) {
@@ -478,6 +485,7 @@ void OpenGLState::Apply() {
478 ApplyPolygonOffset(); 485 ApplyPolygonOffset();
479 ApplyAlphaTest(); 486 ApplyAlphaTest();
480 ApplyClipControl(); 487 ApplyClipControl();
488 ApplyRenderBuffer();
481} 489}
482 490
483void OpenGLState::EmulateViewportWithScissor() { 491void OpenGLState::EmulateViewportWithScissor() {
@@ -551,4 +559,11 @@ OpenGLState& OpenGLState::ResetFramebuffer(GLuint handle) {
551 return *this; 559 return *this;
552} 560}
553 561
562OpenGLState& OpenGLState::ResetRenderbuffer(GLuint handle) {
563 if (renderbuffer == handle) {
564 renderbuffer = 0;
565 }
566 return *this;
567}
568
554} // namespace OpenGL 569} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/gl_state.h b/src/video_core/renderer_opengl/gl_state.h
index 4953eeda2..bce662f2c 100644
--- a/src/video_core/renderer_opengl/gl_state.h
+++ b/src/video_core/renderer_opengl/gl_state.h
@@ -158,6 +158,8 @@ public:
158 GLenum depth_mode = GL_NEGATIVE_ONE_TO_ONE; 158 GLenum depth_mode = GL_NEGATIVE_ONE_TO_ONE;
159 } clip_control; 159 } clip_control;
160 160
161 GLuint renderbuffer{}; // GL_RENDERBUFFER_BINDING
162
161 OpenGLState(); 163 OpenGLState();
162 164
163 /// Get the currently active OpenGL state 165 /// Get the currently active OpenGL state
@@ -196,6 +198,7 @@ public:
196 void ApplyPolygonOffset(); 198 void ApplyPolygonOffset();
197 void ApplyAlphaTest(); 199 void ApplyAlphaTest();
198 void ApplyClipControl(); 200 void ApplyClipControl();
201 void ApplyRenderBuffer();
199 202
200 /// Resets any references to the given resource 203 /// Resets any references to the given resource
201 OpenGLState& UnbindTexture(GLuint handle); 204 OpenGLState& UnbindTexture(GLuint handle);
@@ -204,6 +207,7 @@ public:
204 OpenGLState& ResetPipeline(GLuint handle); 207 OpenGLState& ResetPipeline(GLuint handle);
205 OpenGLState& ResetVertexArray(GLuint handle); 208 OpenGLState& ResetVertexArray(GLuint handle);
206 OpenGLState& ResetFramebuffer(GLuint handle); 209 OpenGLState& ResetFramebuffer(GLuint handle);
210 OpenGLState& ResetRenderbuffer(GLuint handle);
207 211
208 /// Viewport does not affects glClearBuffer so emulate viewport using scissor test 212 /// Viewport does not affects glClearBuffer so emulate viewport using scissor test
209 void EmulateViewportWithScissor(); 213 void EmulateViewportWithScissor();
diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp
index bba16afaf..447f69d4d 100644
--- a/src/video_core/renderer_opengl/renderer_opengl.cpp
+++ b/src/video_core/renderer_opengl/renderer_opengl.cpp
@@ -9,11 +9,11 @@
9#include <glad/glad.h> 9#include <glad/glad.h>
10#include "common/assert.h" 10#include "common/assert.h"
11#include "common/logging/log.h" 11#include "common/logging/log.h"
12#include "common/microprofile.h"
12#include "common/telemetry.h" 13#include "common/telemetry.h"
13#include "core/core.h" 14#include "core/core.h"
14#include "core/core_timing.h" 15#include "core/core_timing.h"
15#include "core/frontend/emu_window.h" 16#include "core/frontend/emu_window.h"
16#include "core/frontend/scope_acquire_window_context.h"
17#include "core/memory.h" 17#include "core/memory.h"
18#include "core/perf_stats.h" 18#include "core/perf_stats.h"
19#include "core/settings.h" 19#include "core/settings.h"
@@ -24,6 +24,144 @@
24 24
25namespace OpenGL { 25namespace OpenGL {
26 26
27// If the size of this is too small, it ends up creating a soft cap on FPS as the renderer will have
28// to wait on available presentation frames.
29constexpr std::size_t SWAP_CHAIN_SIZE = 3;
30
31struct Frame {
32 u32 width{}; /// Width of the frame (to detect resize)
33 u32 height{}; /// Height of the frame
34 bool color_reloaded{}; /// Texture attachment was recreated (ie: resized)
35 OpenGL::OGLRenderbuffer color{}; /// Buffer shared between the render/present FBO
36 OpenGL::OGLFramebuffer render{}; /// FBO created on the render thread
37 OpenGL::OGLFramebuffer present{}; /// FBO created on the present thread
38 GLsync render_fence{}; /// Fence created on the render thread
39 GLsync present_fence{}; /// Fence created on the presentation thread
40 bool is_srgb{}; /// Framebuffer is sRGB or RGB
41};
42
43/**
44 * For smooth Vsync rendering, we want to always present the latest frame that the core generates,
45 * but also make sure that rendering happens at the pace that the frontend dictates. This is a
46 * helper class that the renderer uses to sync frames between the render thread and the presentation
47 * thread
48 */
49class FrameMailbox {
50public:
51 std::mutex swap_chain_lock;
52 std::condition_variable present_cv;
53 std::array<Frame, SWAP_CHAIN_SIZE> swap_chain{};
54 std::queue<Frame*> free_queue;
55 std::deque<Frame*> present_queue;
56 Frame* previous_frame{};
57
58 FrameMailbox() {
59 for (auto& frame : swap_chain) {
60 free_queue.push(&frame);
61 }
62 }
63
64 ~FrameMailbox() {
65 // lock the mutex and clear out the present and free_queues and notify any people who are
66 // blocked to prevent deadlock on shutdown
67 std::scoped_lock lock{swap_chain_lock};
68 std::queue<Frame*>().swap(free_queue);
69 present_queue.clear();
70 present_cv.notify_all();
71 }
72
73 void ReloadPresentFrame(Frame* frame, u32 height, u32 width) {
74 frame->present.Release();
75 frame->present.Create();
76 GLint previous_draw_fbo{};
77 glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &previous_draw_fbo);
78 glBindFramebuffer(GL_FRAMEBUFFER, frame->present.handle);
79 glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER,
80 frame->color.handle);
81 if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
82 LOG_CRITICAL(Render_OpenGL, "Failed to recreate present FBO!");
83 }
84 glBindFramebuffer(GL_DRAW_FRAMEBUFFER, previous_draw_fbo);
85 frame->color_reloaded = false;
86 }
87
88 void ReloadRenderFrame(Frame* frame, u32 width, u32 height) {
89 OpenGLState prev_state = OpenGLState::GetCurState();
90 OpenGLState state = OpenGLState::GetCurState();
91
92 // Recreate the color texture attachment
93 frame->color.Release();
94 frame->color.Create();
95 state.renderbuffer = frame->color.handle;
96 state.Apply();
97 glRenderbufferStorage(GL_RENDERBUFFER, frame->is_srgb ? GL_SRGB8 : GL_RGB8, width, height);
98
99 // Recreate the FBO for the render target
100 frame->render.Release();
101 frame->render.Create();
102 state.draw.read_framebuffer = frame->render.handle;
103 state.draw.draw_framebuffer = frame->render.handle;
104 state.Apply();
105 glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER,
106 frame->color.handle);
107 if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
108 LOG_CRITICAL(Render_OpenGL, "Failed to recreate render FBO!");
109 }
110 prev_state.Apply();
111 frame->width = width;
112 frame->height = height;
113 frame->color_reloaded = true;
114 }
115
116 Frame* GetRenderFrame() {
117 std::unique_lock lock{swap_chain_lock};
118
119 // If theres no free frames, we will reuse the oldest render frame
120 if (free_queue.empty()) {
121 auto frame = present_queue.back();
122 present_queue.pop_back();
123 return frame;
124 }
125
126 Frame* frame = free_queue.front();
127 free_queue.pop();
128 return frame;
129 }
130
131 void ReleaseRenderFrame(Frame* frame) {
132 std::unique_lock lock{swap_chain_lock};
133 present_queue.push_front(frame);
134 present_cv.notify_one();
135 }
136
137 Frame* TryGetPresentFrame(int timeout_ms) {
138 std::unique_lock lock{swap_chain_lock};
139 // wait for new entries in the present_queue
140 present_cv.wait_for(lock, std::chrono::milliseconds(timeout_ms),
141 [&] { return !present_queue.empty(); });
142 if (present_queue.empty()) {
143 // timed out waiting for a frame to draw so return the previous frame
144 return previous_frame;
145 }
146
147 // free the previous frame and add it back to the free queue
148 if (previous_frame) {
149 free_queue.push(previous_frame);
150 }
151
152 // the newest entries are pushed to the front of the queue
153 Frame* frame = present_queue.front();
154 present_queue.pop_front();
155 // remove all old entries from the present queue and move them back to the free_queue
156 for (auto f : present_queue) {
157 free_queue.push(f);
158 }
159 present_queue.clear();
160 previous_frame = frame;
161 return frame;
162 }
163};
164
27namespace { 165namespace {
28 166
29constexpr char vertex_shader[] = R"( 167constexpr char vertex_shader[] = R"(
@@ -158,21 +296,91 @@ void APIENTRY DebugHandler(GLenum source, GLenum type, GLuint id, GLenum severit
158} // Anonymous namespace 296} // Anonymous namespace
159 297
160RendererOpenGL::RendererOpenGL(Core::Frontend::EmuWindow& emu_window, Core::System& system) 298RendererOpenGL::RendererOpenGL(Core::Frontend::EmuWindow& emu_window, Core::System& system)
161 : VideoCore::RendererBase{emu_window}, emu_window{emu_window}, system{system} {} 299 : VideoCore::RendererBase{emu_window}, emu_window{emu_window}, system{system},
300 frame_mailbox{std::make_unique<FrameMailbox>()} {}
162 301
163RendererOpenGL::~RendererOpenGL() = default; 302RendererOpenGL::~RendererOpenGL() = default;
164 303
304MICROPROFILE_DEFINE(OpenGL_RenderFrame, "OpenGL", "Render Frame", MP_RGB(128, 128, 64));
305MICROPROFILE_DEFINE(OpenGL_WaitPresent, "OpenGL", "Wait For Present", MP_RGB(128, 128, 128));
306
165void RendererOpenGL::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) { 307void RendererOpenGL::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) {
308 render_window.PollEvents();
309
310 if (!framebuffer) {
311 return;
312 }
313
166 // Maintain the rasterizer's state as a priority 314 // Maintain the rasterizer's state as a priority
167 OpenGLState prev_state = OpenGLState::GetCurState(); 315 OpenGLState prev_state = OpenGLState::GetCurState();
168 state.AllDirty(); 316 state.AllDirty();
169 state.Apply(); 317 state.Apply();
170 318
319 PrepareRendertarget(framebuffer);
320 RenderScreenshot();
321
322 Frame* frame;
323 {
324 MICROPROFILE_SCOPE(OpenGL_WaitPresent);
325
326 frame = frame_mailbox->GetRenderFrame();
327
328 // Clean up sync objects before drawing
329
330 // INTEL driver workaround. We can't delete the previous render sync object until we are
331 // sure that the presentation is done
332 if (frame->present_fence) {
333 glClientWaitSync(frame->present_fence, 0, GL_TIMEOUT_IGNORED);
334 }
335
336 // delete the draw fence if the frame wasn't presented
337 if (frame->render_fence) {
338 glDeleteSync(frame->render_fence);
339 frame->render_fence = 0;
340 }
341
342 // wait for the presentation to be done
343 if (frame->present_fence) {
344 glWaitSync(frame->present_fence, 0, GL_TIMEOUT_IGNORED);
345 glDeleteSync(frame->present_fence);
346 frame->present_fence = 0;
347 }
348 }
349
350 {
351 MICROPROFILE_SCOPE(OpenGL_RenderFrame);
352 const auto& layout = render_window.GetFramebufferLayout();
353
354 // Recreate the frame if the size of the window has changed
355 if (layout.width != frame->width || layout.height != frame->height ||
356 is_srgb != frame->is_srgb) {
357 LOG_DEBUG(Render_OpenGL, "Reloading render frame");
358 is_srgb = frame->is_srgb = screen_info.display_srgb;
359 frame_mailbox->ReloadRenderFrame(frame, layout.width, layout.height);
360 }
361 state.draw.draw_framebuffer = frame->render.handle;
362 state.Apply();
363 DrawScreen(layout);
364 // Create a fence for the frontend to wait on and swap this frame to OffTex
365 frame->render_fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
366 glFlush();
367 frame_mailbox->ReleaseRenderFrame(frame);
368 m_current_frame++;
369 rasterizer->TickFrame();
370 }
371
372 // Restore the rasterizer state
373 prev_state.AllDirty();
374 prev_state.Apply();
375}
376
377void RendererOpenGL::PrepareRendertarget(const Tegra::FramebufferConfig* framebuffer) {
171 if (framebuffer) { 378 if (framebuffer) {
172 // If framebuffer is provided, reload it from memory to a texture 379 // If framebuffer is provided, reload it from memory to a texture
173 if (screen_info.texture.width != static_cast<GLsizei>(framebuffer->width) || 380 if (screen_info.texture.width != static_cast<GLsizei>(framebuffer->width) ||
174 screen_info.texture.height != static_cast<GLsizei>(framebuffer->height) || 381 screen_info.texture.height != static_cast<GLsizei>(framebuffer->height) ||
175 screen_info.texture.pixel_format != framebuffer->pixel_format) { 382 screen_info.texture.pixel_format != framebuffer->pixel_format ||
383 gl_framebuffer_data.empty()) {
176 // Reallocate texture if the framebuffer size has changed. 384 // Reallocate texture if the framebuffer size has changed.
177 // This is expected to not happen very often and hence should not be a 385 // This is expected to not happen very often and hence should not be a
178 // performance problem. 386 // performance problem.
@@ -181,22 +389,7 @@ void RendererOpenGL::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) {
181 389
182 // Load the framebuffer from memory, draw it to the screen, and swap buffers 390 // Load the framebuffer from memory, draw it to the screen, and swap buffers
183 LoadFBToScreenInfo(*framebuffer); 391 LoadFBToScreenInfo(*framebuffer);
184
185 if (renderer_settings.screenshot_requested)
186 CaptureScreenshot();
187
188 DrawScreen(render_window.GetFramebufferLayout());
189
190 rasterizer->TickFrame();
191
192 render_window.SwapBuffers();
193 } 392 }
194
195 render_window.PollEvents();
196
197 // Restore the rasterizer state
198 prev_state.AllDirty();
199 prev_state.Apply();
200} 393}
201 394
202void RendererOpenGL::LoadFBToScreenInfo(const Tegra::FramebufferConfig& framebuffer) { 395void RendererOpenGL::LoadFBToScreenInfo(const Tegra::FramebufferConfig& framebuffer) {
@@ -418,13 +611,48 @@ void RendererOpenGL::DrawScreen(const Layout::FramebufferLayout& layout) {
418 DrawScreenTriangles(screen_info, static_cast<float>(screen.left), 611 DrawScreenTriangles(screen_info, static_cast<float>(screen.left),
419 static_cast<float>(screen.top), static_cast<float>(screen.GetWidth()), 612 static_cast<float>(screen.top), static_cast<float>(screen.GetWidth()),
420 static_cast<float>(screen.GetHeight())); 613 static_cast<float>(screen.GetHeight()));
614}
421 615
422 m_current_frame++; 616void RendererOpenGL::TryPresent(int timeout_ms) {
617 const auto& layout = render_window.GetFramebufferLayout();
618 auto frame = frame_mailbox->TryGetPresentFrame(timeout_ms);
619 if (!frame) {
620 LOG_DEBUG(Render_OpenGL, "TryGetPresentFrame returned no frame to present");
621 return;
622 }
623
624 // Clearing before a full overwrite of a fbo can signal to drivers that they can avoid a
625 // readback since we won't be doing any blending
626 glClear(GL_COLOR_BUFFER_BIT);
627
628 // Recreate the presentation FBO if the color attachment was changed
629 if (frame->color_reloaded) {
630 LOG_DEBUG(Render_OpenGL, "Reloading present frame");
631 frame_mailbox->ReloadPresentFrame(frame, layout.width, layout.height);
632 }
633 glWaitSync(frame->render_fence, 0, GL_TIMEOUT_IGNORED);
634 // INTEL workaround.
635 // Normally we could just delete the draw fence here, but due to driver bugs, we can just delete
636 // it on the emulation thread without too much penalty
637 // glDeleteSync(frame.render_sync);
638 // frame.render_sync = 0;
639
640 glBindFramebuffer(GL_READ_FRAMEBUFFER, frame->present.handle);
641 glBlitFramebuffer(0, 0, frame->width, frame->height, 0, 0, layout.width, layout.height,
642 GL_COLOR_BUFFER_BIT, GL_LINEAR);
643
644 // Insert fence for the main thread to block on
645 frame->present_fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
646 glFlush();
647
648 glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
423} 649}
424 650
425void RendererOpenGL::UpdateFramerate() {} 651void RendererOpenGL::RenderScreenshot() {
652 if (!renderer_settings.screenshot_requested) {
653 return;
654 }
426 655
427void RendererOpenGL::CaptureScreenshot() {
428 // Draw the current frame to the screenshot framebuffer 656 // Draw the current frame to the screenshot framebuffer
429 screenshot_framebuffer.Create(); 657 screenshot_framebuffer.Create();
430 GLuint old_read_fb = state.draw.read_framebuffer; 658 GLuint old_read_fb = state.draw.read_framebuffer;
@@ -459,8 +687,6 @@ void RendererOpenGL::CaptureScreenshot() {
459} 687}
460 688
461bool RendererOpenGL::Init() { 689bool RendererOpenGL::Init() {
462 Core::Frontend::ScopeAcquireWindowContext acquire_context{render_window};
463
464 if (GLAD_GL_KHR_debug) { 690 if (GLAD_GL_KHR_debug) {
465 glEnable(GL_DEBUG_OUTPUT); 691 glEnable(GL_DEBUG_OUTPUT);
466 glDebugMessageCallback(DebugHandler, nullptr); 692 glDebugMessageCallback(DebugHandler, nullptr);
diff --git a/src/video_core/renderer_opengl/renderer_opengl.h b/src/video_core/renderer_opengl/renderer_opengl.h
index b56328a7f..4107e10a9 100644
--- a/src/video_core/renderer_opengl/renderer_opengl.h
+++ b/src/video_core/renderer_opengl/renderer_opengl.h
@@ -44,19 +44,23 @@ struct ScreenInfo {
44 TextureInfo texture; 44 TextureInfo texture;
45}; 45};
46 46
47struct PresentationTexture {
48 u32 width = 0;
49 u32 height = 0;
50 OGLTexture texture;
51};
52
53class FrameMailbox;
54
47class RendererOpenGL final : public VideoCore::RendererBase { 55class RendererOpenGL final : public VideoCore::RendererBase {
48public: 56public:
49 explicit RendererOpenGL(Core::Frontend::EmuWindow& emu_window, Core::System& system); 57 explicit RendererOpenGL(Core::Frontend::EmuWindow& emu_window, Core::System& system);
50 ~RendererOpenGL() override; 58 ~RendererOpenGL() override;
51 59
52 /// Swap buffers (render frame)
53 void SwapBuffers(const Tegra::FramebufferConfig* framebuffer) override;
54
55 /// Initialize the renderer
56 bool Init() override; 60 bool Init() override;
57
58 /// Shutdown the renderer
59 void ShutDown() override; 61 void ShutDown() override;
62 void SwapBuffers(const Tegra::FramebufferConfig* framebuffer) override;
63 void TryPresent(int timeout_ms) override;
60 64
61private: 65private:
62 /// Initializes the OpenGL state and creates persistent objects. 66 /// Initializes the OpenGL state and creates persistent objects.
@@ -74,10 +78,7 @@ private:
74 78
75 void DrawScreenTriangles(const ScreenInfo& screen_info, float x, float y, float w, float h); 79 void DrawScreenTriangles(const ScreenInfo& screen_info, float x, float y, float w, float h);
76 80
77 /// Updates the framerate. 81 void RenderScreenshot();
78 void UpdateFramerate();
79
80 void CaptureScreenshot();
81 82
82 /// Loads framebuffer from emulated memory into the active OpenGL texture. 83 /// Loads framebuffer from emulated memory into the active OpenGL texture.
83 void LoadFBToScreenInfo(const Tegra::FramebufferConfig& framebuffer); 84 void LoadFBToScreenInfo(const Tegra::FramebufferConfig& framebuffer);
@@ -87,6 +88,8 @@ private:
87 void LoadColorToActiveGLTexture(u8 color_r, u8 color_g, u8 color_b, u8 color_a, 88 void LoadColorToActiveGLTexture(u8 color_r, u8 color_g, u8 color_b, u8 color_a,
88 const TextureInfo& texture); 89 const TextureInfo& texture);
89 90
91 void PrepareRendertarget(const Tegra::FramebufferConfig* framebuffer);
92
90 Core::Frontend::EmuWindow& emu_window; 93 Core::Frontend::EmuWindow& emu_window;
91 Core::System& system; 94 Core::System& system;
92 95
@@ -107,6 +110,12 @@ private:
107 /// Used for transforming the framebuffer orientation 110 /// Used for transforming the framebuffer orientation
108 Tegra::FramebufferConfig::TransformFlags framebuffer_transform_flags; 111 Tegra::FramebufferConfig::TransformFlags framebuffer_transform_flags;
109 Common::Rectangle<int> framebuffer_crop_rect; 112 Common::Rectangle<int> framebuffer_crop_rect;
113
114 /// Represents if the final render frame is sRGB
115 bool is_srgb{};
116
117 /// Frame presentation mailbox
118 std::unique_ptr<FrameMailbox> frame_mailbox;
110}; 119};
111 120
112} // namespace OpenGL 121} // namespace OpenGL
diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp
index d5032b432..ddc62bc97 100644
--- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp
+++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp
@@ -106,8 +106,14 @@ RendererVulkan::~RendererVulkan() {
106} 106}
107 107
108void RendererVulkan::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) { 108void RendererVulkan::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) {
109 render_window.PollEvents();
110
111 if (!framebuffer) {
112 return;
113 }
114
109 const auto& layout = render_window.GetFramebufferLayout(); 115 const auto& layout = render_window.GetFramebufferLayout();
110 if (framebuffer && layout.width > 0 && layout.height > 0 && render_window.IsShown()) { 116 if (layout.width > 0 && layout.height > 0 && render_window.IsShown()) {
111 const VAddr framebuffer_addr = framebuffer->address + framebuffer->offset; 117 const VAddr framebuffer_addr = framebuffer->address + framebuffer->offset;
112 const bool use_accelerated = 118 const bool use_accelerated =
113 rasterizer->AccelerateDisplay(*framebuffer, framebuffer_addr, framebuffer->stride); 119 rasterizer->AccelerateDisplay(*framebuffer, framebuffer_addr, framebuffer->stride);
@@ -128,13 +134,16 @@ void RendererVulkan::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) {
128 blit_screen->Recreate(); 134 blit_screen->Recreate();
129 } 135 }
130 136
131 render_window.SwapBuffers();
132 rasterizer->TickFrame(); 137 rasterizer->TickFrame();
133 } 138 }
134 139
135 render_window.PollEvents(); 140 render_window.PollEvents();
136} 141}
137 142
143void RendererVulkan::TryPresent(int /*timeout_ms*/) {
144 // TODO (bunnei): ImplementMe
145}
146
138bool RendererVulkan::Init() { 147bool RendererVulkan::Init() {
139 PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr{}; 148 PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr{};
140 render_window.RetrieveVulkanHandlers(&vkGetInstanceProcAddr, &instance, &surface); 149 render_window.RetrieveVulkanHandlers(&vkGetInstanceProcAddr, &instance, &surface);
@@ -262,4 +271,4 @@ void RendererVulkan::Report() const {
262 telemetry_session.AddField(field, "GPU_Vulkan_Extensions", extensions); 271 telemetry_session.AddField(field, "GPU_Vulkan_Extensions", extensions);
263} 272}
264 273
265} // namespace Vulkan \ No newline at end of file 274} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.h b/src/video_core/renderer_vulkan/renderer_vulkan.h
index a472c5dc9..f513397f0 100644
--- a/src/video_core/renderer_vulkan/renderer_vulkan.h
+++ b/src/video_core/renderer_vulkan/renderer_vulkan.h
@@ -36,14 +36,10 @@ public:
36 explicit RendererVulkan(Core::Frontend::EmuWindow& window, Core::System& system); 36 explicit RendererVulkan(Core::Frontend::EmuWindow& window, Core::System& system);
37 ~RendererVulkan() override; 37 ~RendererVulkan() override;
38 38
39 /// Swap buffers (render frame)
40 void SwapBuffers(const Tegra::FramebufferConfig* framebuffer) override;
41
42 /// Initialize the renderer
43 bool Init() override; 39 bool Init() override;
44
45 /// Shutdown the renderer
46 void ShutDown() override; 40 void ShutDown() override;
41 void SwapBuffers(const Tegra::FramebufferConfig* framebuffer) override;
42 void TryPresent(int timeout_ms) override;
47 43
48private: 44private:
49 std::optional<vk::DebugUtilsMessengerEXT> CreateDebugCallback( 45 std::optional<vk::DebugUtilsMessengerEXT> CreateDebugCallback(
diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp
index 55a37fffa..c3dbb1a88 100644
--- a/src/yuzu/bootmanager.cpp
+++ b/src/yuzu/bootmanager.cpp
@@ -9,6 +9,9 @@
9#include <QKeyEvent> 9#include <QKeyEvent>
10#include <QMessageBox> 10#include <QMessageBox>
11#include <QOffscreenSurface> 11#include <QOffscreenSurface>
12#include <QOpenGLContext>
13#include <QOpenGLFunctions>
14#include <QOpenGLFunctions_4_3_Core>
12#include <QOpenGLWindow> 15#include <QOpenGLWindow>
13#include <QPainter> 16#include <QPainter>
14#include <QScreen> 17#include <QScreen>
@@ -23,9 +26,10 @@
23#include "common/assert.h" 26#include "common/assert.h"
24#include "common/microprofile.h" 27#include "common/microprofile.h"
25#include "common/scm_rev.h" 28#include "common/scm_rev.h"
29#include "common/scope_exit.h"
26#include "core/core.h" 30#include "core/core.h"
27#include "core/frontend/framebuffer_layout.h" 31#include "core/frontend/framebuffer_layout.h"
28#include "core/frontend/scope_acquire_window_context.h" 32#include "core/frontend/scope_acquire_context.h"
29#include "core/settings.h" 33#include "core/settings.h"
30#include "input_common/keyboard.h" 34#include "input_common/keyboard.h"
31#include "input_common/main.h" 35#include "input_common/main.h"
@@ -35,15 +39,27 @@
35#include "yuzu/bootmanager.h" 39#include "yuzu/bootmanager.h"
36#include "yuzu/main.h" 40#include "yuzu/main.h"
37 41
38EmuThread::EmuThread(GRenderWindow* render_window) : render_window(render_window) {} 42EmuThread::EmuThread(GRenderWindow& window)
43 : shared_context{window.CreateSharedContext()},
44 context{(Settings::values.use_asynchronous_gpu_emulation && shared_context) ? *shared_context
45 : window} {}
39 46
40EmuThread::~EmuThread() = default; 47EmuThread::~EmuThread() = default;
41 48
42void EmuThread::run() { 49static GMainWindow* GetMainWindow() {
43 render_window->MakeCurrent(); 50 for (QWidget* w : qApp->topLevelWidgets()) {
51 if (GMainWindow* main = qobject_cast<GMainWindow*>(w)) {
52 return main;
53 }
54 }
55 return nullptr;
56}
44 57
58void EmuThread::run() {
45 MicroProfileOnThreadCreate("EmuThread"); 59 MicroProfileOnThreadCreate("EmuThread");
46 60
61 Core::Frontend::ScopeAcquireContext acquire_context{context};
62
47 emit LoadProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0); 63 emit LoadProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0);
48 64
49 Core::System::GetInstance().Renderer().Rasterizer().LoadDiskResources( 65 Core::System::GetInstance().Renderer().Rasterizer().LoadDiskResources(
@@ -53,11 +69,6 @@ void EmuThread::run() {
53 69
54 emit LoadProgress(VideoCore::LoadCallbackStage::Complete, 0, 0); 70 emit LoadProgress(VideoCore::LoadCallbackStage::Complete, 0, 0);
55 71
56 if (Settings::values.use_asynchronous_gpu_emulation) {
57 // Release OpenGL context for the GPU thread
58 render_window->DoneCurrent();
59 }
60
61 // Holds whether the cpu was running during the last iteration, 72 // Holds whether the cpu was running during the last iteration,
62 // so that the DebugModeLeft signal can be emitted before the 73 // so that the DebugModeLeft signal can be emitted before the
63 // next execution step 74 // next execution step
@@ -98,190 +109,202 @@ void EmuThread::run() {
98#if MICROPROFILE_ENABLED 109#if MICROPROFILE_ENABLED
99 MicroProfileOnThreadExit(); 110 MicroProfileOnThreadExit();
100#endif 111#endif
101
102 render_window->moveContext();
103} 112}
104 113
105class GGLContext : public Core::Frontend::GraphicsContext { 114class GGLContext : public Core::Frontend::GraphicsContext {
106public: 115public:
107 explicit GGLContext(QOpenGLContext* shared_context) : shared_context{shared_context} { 116 explicit GGLContext(QOpenGLContext* shared_context)
108 context.setFormat(shared_context->format()); 117 : context(new QOpenGLContext(shared_context->parent())),
109 context.setShareContext(shared_context); 118 surface(new QOffscreenSurface(nullptr)) {
110 context.create(); 119
120 // disable vsync for any shared contexts
121 auto format = shared_context->format();
122 format.setSwapInterval(0);
123
124 context->setShareContext(shared_context);
125 context->setFormat(format);
126 context->create();
127 surface->setParent(shared_context->parent());
128 surface->setFormat(format);
129 surface->create();
111 } 130 }
112 131
113 void MakeCurrent() override { 132 void MakeCurrent() override {
114 context.makeCurrent(shared_context->surface()); 133 context->makeCurrent(surface);
115 } 134 }
116 135
117 void DoneCurrent() override { 136 void DoneCurrent() override {
118 context.doneCurrent(); 137 context->doneCurrent();
119 } 138 }
120 139
121 void SwapBuffers() override {}
122
123private: 140private:
124 QOpenGLContext* shared_context; 141 QOpenGLContext* context;
125 QOpenGLContext context; 142 QOffscreenSurface* surface;
126}; 143};
127 144
128class GWidgetInternal : public QWindow { 145class ChildRenderWindow : public QWindow {
129public: 146public:
130 GWidgetInternal(GRenderWindow* parent) : parent(parent) {} 147 ChildRenderWindow(QWindow* parent, QWidget* event_handler)
131 virtual ~GWidgetInternal() = default; 148 : QWindow{parent}, event_handler{event_handler} {}
132 149
133 void resizeEvent(QResizeEvent* ev) override { 150 virtual ~ChildRenderWindow() = default;
134 parent->OnClientAreaResized(ev->size().width(), ev->size().height());
135 parent->OnFramebufferSizeChanged();
136 }
137 151
138 void keyPressEvent(QKeyEvent* event) override { 152 virtual void Present() = 0;
139 InputCommon::GetKeyboard()->PressKey(event->key());
140 }
141 153
142 void keyReleaseEvent(QKeyEvent* event) override { 154protected:
143 InputCommon::GetKeyboard()->ReleaseKey(event->key()); 155 bool event(QEvent* event) override {
156 switch (event->type()) {
157 case QEvent::UpdateRequest:
158 Present();
159 return true;
160 case QEvent::MouseButtonPress:
161 case QEvent::MouseButtonRelease:
162 case QEvent::MouseButtonDblClick:
163 case QEvent::MouseMove:
164 case QEvent::KeyPress:
165 case QEvent::KeyRelease:
166 case QEvent::FocusIn:
167 case QEvent::FocusOut:
168 case QEvent::FocusAboutToChange:
169 case QEvent::Enter:
170 case QEvent::Leave:
171 case QEvent::Wheel:
172 case QEvent::TabletMove:
173 case QEvent::TabletPress:
174 case QEvent::TabletRelease:
175 case QEvent::TabletEnterProximity:
176 case QEvent::TabletLeaveProximity:
177 case QEvent::TouchBegin:
178 case QEvent::TouchUpdate:
179 case QEvent::TouchEnd:
180 case QEvent::InputMethodQuery:
181 case QEvent::TouchCancel:
182 return QCoreApplication::sendEvent(event_handler, event);
183 case QEvent::Drop:
184 GetMainWindow()->DropAction(static_cast<QDropEvent*>(event));
185 return true;
186 case QEvent::DragResponse:
187 case QEvent::DragEnter:
188 case QEvent::DragLeave:
189 case QEvent::DragMove:
190 GetMainWindow()->AcceptDropEvent(static_cast<QDropEvent*>(event));
191 return true;
192 default:
193 return QWindow::event(event);
194 }
144 } 195 }
145 196
146 void mousePressEvent(QMouseEvent* event) override { 197 void exposeEvent(QExposeEvent* event) override {
147 if (event->source() == Qt::MouseEventSynthesizedBySystem) 198 QWindow::requestUpdate();
148 return; // touch input is handled in TouchBeginEvent 199 QWindow::exposeEvent(event);
149
150 const auto pos{event->pos()};
151 if (event->button() == Qt::LeftButton) {
152 const auto [x, y] = parent->ScaleTouch(pos);
153 parent->TouchPressed(x, y);
154 } else if (event->button() == Qt::RightButton) {
155 InputCommon::GetMotionEmu()->BeginTilt(pos.x(), pos.y());
156 }
157 } 200 }
158 201
159 void mouseMoveEvent(QMouseEvent* event) override { 202private:
160 if (event->source() == Qt::MouseEventSynthesizedBySystem) 203 QWidget* event_handler{};
161 return; // touch input is handled in TouchUpdateEvent 204};
162 205
163 const auto pos{event->pos()}; 206class OpenGLWindow final : public ChildRenderWindow {
164 const auto [x, y] = parent->ScaleTouch(pos); 207public:
165 parent->TouchMoved(x, y); 208 OpenGLWindow(QWindow* parent, QWidget* event_handler, QOpenGLContext* shared_context)
166 InputCommon::GetMotionEmu()->Tilt(pos.x(), pos.y()); 209 : ChildRenderWindow{parent, event_handler},
167 } 210 context(new QOpenGLContext(shared_context->parent())) {
168 211
169 void mouseReleaseEvent(QMouseEvent* event) override { 212 // disable vsync for any shared contexts
170 if (event->source() == Qt::MouseEventSynthesizedBySystem) 213 auto format = shared_context->format();
171 return; // touch input is handled in TouchEndEvent 214 format.setSwapInterval(Settings::values.use_vsync ? 1 : 0);
215 this->setFormat(format);
172 216
173 if (event->button() == Qt::LeftButton) 217 context->setShareContext(shared_context);
174 parent->TouchReleased(); 218 context->setScreen(this->screen());
175 else if (event->button() == Qt::RightButton) 219 context->setFormat(format);
176 InputCommon::GetMotionEmu()->EndTilt(); 220 context->create();
177 }
178 221
179 void DisablePainting() { 222 setSurfaceType(QWindow::OpenGLSurface);
180 do_painting = false;
181 }
182 223
183 void EnablePainting() { 224 // TODO: One of these flags might be interesting: WA_OpaquePaintEvent, WA_NoBackground,
184 do_painting = true; 225 // WA_DontShowOnScreen, WA_DeleteOnClose
185 } 226 }
186 227
187 std::pair<unsigned, unsigned> GetSize() const { 228 ~OpenGLWindow() override {
188 return std::make_pair(width(), height()); 229 context->doneCurrent();
189 } 230 }
190 231
191protected: 232 void Present() override {
192 bool IsPaintingEnabled() const { 233 if (!isExposed()) {
193 return do_painting; 234 return;
235 }
236
237 context->makeCurrent(this);
238 Core::System::GetInstance().Renderer().TryPresent(100);
239 context->swapBuffers(this);
240 auto f = context->versionFunctions<QOpenGLFunctions_4_3_Core>();
241 f->glFinish();
242 QWindow::requestUpdate();
194 } 243 }
195 244
196private: 245private:
197 GRenderWindow* parent; 246 QOpenGLContext* context{};
198 bool do_painting = false;
199};
200
201// This class overrides paintEvent and resizeEvent to prevent the GUI thread from stealing GL
202// context.
203// The corresponding functionality is handled in EmuThread instead
204class GGLWidgetInternal final : public GWidgetInternal, public QOpenGLWindow {
205public:
206 GGLWidgetInternal(GRenderWindow* parent, QOpenGLContext* shared_context)
207 : GWidgetInternal(parent), QOpenGLWindow(shared_context) {}
208 ~GGLWidgetInternal() override = default;
209
210 void paintEvent(QPaintEvent* ev) override {
211 if (IsPaintingEnabled()) {
212 QPainter painter(this);
213 }
214 }
215}; 247};
216 248
217#ifdef HAS_VULKAN 249#ifdef HAS_VULKAN
218class GVKWidgetInternal final : public GWidgetInternal { 250class VulkanWindow final : public ChildRenderWindow {
219public: 251public:
220 GVKWidgetInternal(GRenderWindow* parent, QVulkanInstance* instance) : GWidgetInternal(parent) { 252 VulkanWindow(QWindow* parent, QWidget* event_handler, QVulkanInstance* instance)
253 : ChildRenderWindow{parent, event_handler} {
221 setSurfaceType(QSurface::SurfaceType::VulkanSurface); 254 setSurfaceType(QSurface::SurfaceType::VulkanSurface);
222 setVulkanInstance(instance); 255 setVulkanInstance(instance);
223 } 256 }
224 ~GVKWidgetInternal() override = default; 257
258 ~VulkanWindow() override = default;
259
260 void Present() override {
261 // TODO(bunnei): ImplementMe
262 }
263
264private:
265 QWidget* event_handler{};
225}; 266};
226#endif 267#endif
227 268
228GRenderWindow::GRenderWindow(GMainWindow* parent, EmuThread* emu_thread) 269GRenderWindow::GRenderWindow(QWidget* parent_, EmuThread* emu_thread)
229 : QWidget(parent), emu_thread(emu_thread) { 270 : QWidget(parent_), emu_thread(emu_thread) {
230 setWindowTitle(QStringLiteral("yuzu %1 | %2-%3") 271 setWindowTitle(QStringLiteral("yuzu %1 | %2-%3")
231 .arg(QString::fromUtf8(Common::g_build_name), 272 .arg(QString::fromUtf8(Common::g_build_name),
232 QString::fromUtf8(Common::g_scm_branch), 273 QString::fromUtf8(Common::g_scm_branch),
233 QString::fromUtf8(Common::g_scm_desc))); 274 QString::fromUtf8(Common::g_scm_desc)));
234 setAttribute(Qt::WA_AcceptTouchEvents); 275 setAttribute(Qt::WA_AcceptTouchEvents);
235 276 auto layout = new QHBoxLayout(this);
277 layout->setMargin(0);
278 setLayout(layout);
236 InputCommon::Init(); 279 InputCommon::Init();
280
281 GMainWindow* parent = GetMainWindow();
237 connect(this, &GRenderWindow::FirstFrameDisplayed, parent, &GMainWindow::OnLoadComplete); 282 connect(this, &GRenderWindow::FirstFrameDisplayed, parent, &GMainWindow::OnLoadComplete);
238} 283}
239 284
240GRenderWindow::~GRenderWindow() { 285GRenderWindow::~GRenderWindow() {
241 InputCommon::Shutdown(); 286 InputCommon::Shutdown();
242
243 // Avoid an unordered destruction that generates a segfault
244 delete child;
245} 287}
246 288
247void GRenderWindow::moveContext() { 289void GRenderWindow::MakeCurrent() {
248 if (!context) { 290 if (core_context) {
249 return; 291 core_context->MakeCurrent();
250 } 292 }
251 DoneCurrent();
252
253 // If the thread started running, move the GL Context to the new thread. Otherwise, move it
254 // back.
255 auto thread = (QThread::currentThread() == qApp->thread() && emu_thread != nullptr)
256 ? emu_thread
257 : qApp->thread();
258 context->moveToThread(thread);
259} 293}
260 294
261void GRenderWindow::SwapBuffers() { 295void GRenderWindow::DoneCurrent() {
262 if (context) { 296 if (core_context) {
263 context->swapBuffers(child); 297 core_context->DoneCurrent();
264 } 298 }
299}
300
301void GRenderWindow::PollEvents() {
265 if (!first_frame) { 302 if (!first_frame) {
266 first_frame = true; 303 first_frame = true;
267 emit FirstFrameDisplayed(); 304 emit FirstFrameDisplayed();
268 } 305 }
269} 306}
270 307
271void GRenderWindow::MakeCurrent() {
272 if (context) {
273 context->makeCurrent(child);
274 }
275}
276
277void GRenderWindow::DoneCurrent() {
278 if (context) {
279 context->doneCurrent();
280 }
281}
282
283void GRenderWindow::PollEvents() {}
284
285bool GRenderWindow::IsShown() const { 308bool GRenderWindow::IsShown() const {
286 return !isMinimized(); 309 return !isMinimized();
287} 310}
@@ -291,7 +314,7 @@ void GRenderWindow::RetrieveVulkanHandlers(void* get_instance_proc_addr, void* i
291#ifdef HAS_VULKAN 314#ifdef HAS_VULKAN
292 const auto instance_proc_addr = vk_instance->getInstanceProcAddr("vkGetInstanceProcAddr"); 315 const auto instance_proc_addr = vk_instance->getInstanceProcAddr("vkGetInstanceProcAddr");
293 const VkInstance instance_copy = vk_instance->vkInstance(); 316 const VkInstance instance_copy = vk_instance->vkInstance();
294 const VkSurfaceKHR surface_copy = vk_instance->surfaceForWindow(child); 317 const VkSurfaceKHR surface_copy = vk_instance->surfaceForWindow(child_window);
295 318
296 std::memcpy(get_instance_proc_addr, &instance_proc_addr, sizeof(instance_proc_addr)); 319 std::memcpy(get_instance_proc_addr, &instance_proc_addr, sizeof(instance_proc_addr));
297 std::memcpy(instance, &instance_copy, sizeof(instance_copy)); 320 std::memcpy(instance, &instance_copy, sizeof(instance_copy));
@@ -309,21 +332,10 @@ void GRenderWindow::RetrieveVulkanHandlers(void* get_instance_proc_addr, void* i
309void GRenderWindow::OnFramebufferSizeChanged() { 332void GRenderWindow::OnFramebufferSizeChanged() {
310 // Screen changes potentially incur a change in screen DPI, hence we should update the 333 // Screen changes potentially incur a change in screen DPI, hence we should update the
311 // framebuffer size 334 // framebuffer size
312 const qreal pixelRatio{GetWindowPixelRatio()}; 335 const qreal pixel_ratio = windowPixelRatio();
313 const auto size{child->GetSize()}; 336 const u32 width = this->width() * pixel_ratio;
314 UpdateCurrentFramebufferLayout(size.first * pixelRatio, size.second * pixelRatio); 337 const u32 height = this->height() * pixel_ratio;
315} 338 UpdateCurrentFramebufferLayout(width, height);
316
317void GRenderWindow::ForwardKeyPressEvent(QKeyEvent* event) {
318 if (child) {
319 child->keyPressEvent(event);
320 }
321}
322
323void GRenderWindow::ForwardKeyReleaseEvent(QKeyEvent* event) {
324 if (child) {
325 child->keyReleaseEvent(event);
326 }
327} 339}
328 340
329void GRenderWindow::BackupGeometry() { 341void GRenderWindow::BackupGeometry() {
@@ -351,13 +363,12 @@ QByteArray GRenderWindow::saveGeometry() {
351 return geometry; 363 return geometry;
352} 364}
353 365
354qreal GRenderWindow::GetWindowPixelRatio() const { 366qreal GRenderWindow::windowPixelRatio() const {
355 // windowHandle() might not be accessible until the window is displayed to screen. 367 return devicePixelRatio();
356 return windowHandle() ? windowHandle()->screen()->devicePixelRatio() : 1.0f;
357} 368}
358 369
359std::pair<u32, u32> GRenderWindow::ScaleTouch(const QPointF pos) const { 370std::pair<u32, u32> GRenderWindow::ScaleTouch(const QPointF pos) const {
360 const qreal pixel_ratio{GetWindowPixelRatio()}; 371 const qreal pixel_ratio = windowPixelRatio();
361 return {static_cast<u32>(std::max(std::round(pos.x() * pixel_ratio), qreal{0.0})), 372 return {static_cast<u32>(std::max(std::round(pos.x() * pixel_ratio), qreal{0.0})),
362 static_cast<u32>(std::max(std::round(pos.y() * pixel_ratio), qreal{0.0}))}; 373 static_cast<u32>(std::max(std::round(pos.y() * pixel_ratio), qreal{0.0}))};
363} 374}
@@ -367,6 +378,47 @@ void GRenderWindow::closeEvent(QCloseEvent* event) {
367 QWidget::closeEvent(event); 378 QWidget::closeEvent(event);
368} 379}
369 380
381void GRenderWindow::keyPressEvent(QKeyEvent* event) {
382 InputCommon::GetKeyboard()->PressKey(event->key());
383}
384
385void GRenderWindow::keyReleaseEvent(QKeyEvent* event) {
386 InputCommon::GetKeyboard()->ReleaseKey(event->key());
387}
388
389void GRenderWindow::mousePressEvent(QMouseEvent* event) {
390 if (event->source() == Qt::MouseEventSynthesizedBySystem)
391 return; // touch input is handled in TouchBeginEvent
392
393 auto pos = event->pos();
394 if (event->button() == Qt::LeftButton) {
395 const auto [x, y] = ScaleTouch(pos);
396 this->TouchPressed(x, y);
397 } else if (event->button() == Qt::RightButton) {
398 InputCommon::GetMotionEmu()->BeginTilt(pos.x(), pos.y());
399 }
400}
401
402void GRenderWindow::mouseMoveEvent(QMouseEvent* event) {
403 if (event->source() == Qt::MouseEventSynthesizedBySystem)
404 return; // touch input is handled in TouchUpdateEvent
405
406 auto pos = event->pos();
407 const auto [x, y] = ScaleTouch(pos);
408 this->TouchMoved(x, y);
409 InputCommon::GetMotionEmu()->Tilt(pos.x(), pos.y());
410}
411
412void GRenderWindow::mouseReleaseEvent(QMouseEvent* event) {
413 if (event->source() == Qt::MouseEventSynthesizedBySystem)
414 return; // touch input is handled in TouchEndEvent
415
416 if (event->button() == Qt::LeftButton)
417 this->TouchReleased();
418 else if (event->button() == Qt::RightButton)
419 InputCommon::GetMotionEmu()->EndTilt();
420}
421
370void GRenderWindow::TouchBeginEvent(const QTouchEvent* event) { 422void GRenderWindow::TouchBeginEvent(const QTouchEvent* event) {
371 // TouchBegin always has exactly one touch point, so take the .first() 423 // TouchBegin always has exactly one touch point, so take the .first()
372 const auto [x, y] = ScaleTouch(event->touchPoints().first().pos()); 424 const auto [x, y] = ScaleTouch(event->touchPoints().first().pos());
@@ -415,26 +467,20 @@ void GRenderWindow::focusOutEvent(QFocusEvent* event) {
415 InputCommon::GetKeyboard()->ReleaseAllKeys(); 467 InputCommon::GetKeyboard()->ReleaseAllKeys();
416} 468}
417 469
418void GRenderWindow::OnClientAreaResized(u32 width, u32 height) { 470void GRenderWindow::resizeEvent(QResizeEvent* event) {
419 NotifyClientAreaSizeChanged(std::make_pair(width, height)); 471 QWidget::resizeEvent(event);
472 OnFramebufferSizeChanged();
420} 473}
421 474
422std::unique_ptr<Core::Frontend::GraphicsContext> GRenderWindow::CreateSharedContext() const { 475std::unique_ptr<Core::Frontend::GraphicsContext> GRenderWindow::CreateSharedContext() const {
423 return std::make_unique<GGLContext>(context.get()); 476 if (Settings::values.renderer_backend == Settings::RendererBackend::OpenGL) {
477 return std::make_unique<GGLContext>(QOpenGLContext::globalShareContext());
478 }
479 return {};
424} 480}
425 481
426bool GRenderWindow::InitRenderTarget() { 482bool GRenderWindow::InitRenderTarget() {
427 shared_context.reset(); 483 ReleaseRenderTarget();
428 context.reset();
429 if (child) {
430 delete child;
431 }
432 if (container) {
433 delete container;
434 }
435 if (layout()) {
436 delete layout();
437 }
438 484
439 first_frame = false; 485 first_frame = false;
440 486
@@ -451,13 +497,6 @@ bool GRenderWindow::InitRenderTarget() {
451 break; 497 break;
452 } 498 }
453 499
454 container = QWidget::createWindowContainer(child, this);
455 QBoxLayout* layout = new QHBoxLayout(this);
456
457 layout->addWidget(container);
458 layout->setMargin(0);
459 setLayout(layout);
460
461 // Reset minimum required size to avoid resizing issues on the main window after restarting. 500 // Reset minimum required size to avoid resizing issues on the main window after restarting.
462 setMinimumSize(1, 1); 501 setMinimumSize(1, 1);
463 502
@@ -467,14 +506,9 @@ bool GRenderWindow::InitRenderTarget() {
467 hide(); 506 hide();
468 507
469 resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height); 508 resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height);
470 child->resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height);
471 container->resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height);
472 509
473 OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size); 510 OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size);
474
475 OnFramebufferSizeChanged(); 511 OnFramebufferSizeChanged();
476 NotifyClientAreaSizeChanged(child->GetSize());
477
478 BackupGeometry(); 512 BackupGeometry();
479 513
480 if (Settings::values.renderer_backend == Settings::RendererBackend::OpenGL) { 514 if (Settings::values.renderer_backend == Settings::RendererBackend::OpenGL) {
@@ -486,6 +520,14 @@ bool GRenderWindow::InitRenderTarget() {
486 return true; 520 return true;
487} 521}
488 522
523void GRenderWindow::ReleaseRenderTarget() {
524 if (child_widget) {
525 layout()->removeWidget(child_widget);
526 delete child_widget;
527 child_widget = nullptr;
528 }
529}
530
489void GRenderWindow::CaptureScreenshot(u32 res_scale, const QString& screenshot_path) { 531void GRenderWindow::CaptureScreenshot(u32 res_scale, const QString& screenshot_path) {
490 auto& renderer = Core::System::GetInstance().Renderer(); 532 auto& renderer = Core::System::GetInstance().Renderer();
491 533
@@ -521,16 +563,19 @@ bool GRenderWindow::InitializeOpenGL() {
521 fmt.setOption(QSurfaceFormat::FormatOption::DeprecatedFunctions); 563 fmt.setOption(QSurfaceFormat::FormatOption::DeprecatedFunctions);
522 // TODO: expose a setting for buffer value (ie default/single/double/triple) 564 // TODO: expose a setting for buffer value (ie default/single/double/triple)
523 fmt.setSwapBehavior(QSurfaceFormat::DefaultSwapBehavior); 565 fmt.setSwapBehavior(QSurfaceFormat::DefaultSwapBehavior);
524 shared_context = std::make_unique<QOpenGLContext>(); 566 fmt.setSwapInterval(0);
525 shared_context->setFormat(fmt); 567 QSurfaceFormat::setDefaultFormat(fmt);
526 shared_context->create(); 568
527 context = std::make_unique<QOpenGLContext>(); 569 GMainWindow* parent = GetMainWindow();
528 context->setShareContext(shared_context.get()); 570 QWindow* parent_win_handle = parent ? parent->windowHandle() : nullptr;
529 context->setFormat(fmt); 571 child_window = new OpenGLWindow(parent_win_handle, this, QOpenGLContext::globalShareContext());
530 context->create(); 572 child_window->create();
531 fmt.setSwapInterval(false); 573 child_widget = createWindowContainer(child_window, this);
532 574 child_widget->resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height);
533 child = new GGLWidgetInternal(this, shared_context.get()); 575 layout()->addWidget(child_widget);
576
577 core_context = CreateSharedContext();
578
534 return true; 579 return true;
535} 580}
536 581
@@ -559,7 +604,14 @@ bool GRenderWindow::InitializeVulkan() {
559 return false; 604 return false;
560 } 605 }
561 606
562 child = new GVKWidgetInternal(this, vk_instance.get()); 607 GMainWindow* parent = GetMainWindow();
608 QWindow* parent_win_handle = parent ? parent->windowHandle() : nullptr;
609 child_window = new VulkanWindow(parent_win_handle, this, vk_instance.get());
610 child_window->create();
611 child_widget = createWindowContainer(child_window, this);
612 child_widget->resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height);
613 layout()->addWidget(child_widget);
614
563 return true; 615 return true;
564#else 616#else
565 QMessageBox::critical(this, tr("Vulkan not available!"), 617 QMessageBox::critical(this, tr("Vulkan not available!"),
@@ -569,7 +621,7 @@ bool GRenderWindow::InitializeVulkan() {
569} 621}
570 622
571bool GRenderWindow::LoadOpenGL() { 623bool GRenderWindow::LoadOpenGL() {
572 Core::Frontend::ScopeAcquireWindowContext acquire_context{*this}; 624 Core::Frontend::ScopeAcquireContext acquire_context{*this};
573 if (!gladLoadGL()) { 625 if (!gladLoadGL()) {
574 QMessageBox::critical(this, tr("Error while initializing OpenGL 4.3!"), 626 QMessageBox::critical(this, tr("Error while initializing OpenGL 4.3!"),
575 tr("Your GPU may not support OpenGL 4.3, or you do not have the " 627 tr("Your GPU may not support OpenGL 4.3, or you do not have the "
@@ -621,12 +673,10 @@ QStringList GRenderWindow::GetUnsupportedGLExtensions() const {
621 673
622void GRenderWindow::OnEmulationStarting(EmuThread* emu_thread) { 674void GRenderWindow::OnEmulationStarting(EmuThread* emu_thread) {
623 this->emu_thread = emu_thread; 675 this->emu_thread = emu_thread;
624 child->DisablePainting();
625} 676}
626 677
627void GRenderWindow::OnEmulationStopping() { 678void GRenderWindow::OnEmulationStopping() {
628 emu_thread = nullptr; 679 emu_thread = nullptr;
629 child->EnablePainting();
630} 680}
631 681
632void GRenderWindow::showEvent(QShowEvent* event) { 682void GRenderWindow::showEvent(QShowEvent* event) {
diff --git a/src/yuzu/bootmanager.h b/src/yuzu/bootmanager.h
index 71a2fa321..79b030304 100644
--- a/src/yuzu/bootmanager.h
+++ b/src/yuzu/bootmanager.h
@@ -11,11 +11,13 @@
11#include <QImage> 11#include <QImage>
12#include <QThread> 12#include <QThread>
13#include <QWidget> 13#include <QWidget>
14#include <QWindow>
14 15
15#include "common/thread.h" 16#include "common/thread.h"
16#include "core/core.h" 17#include "core/core.h"
17#include "core/frontend/emu_window.h" 18#include "core/frontend/emu_window.h"
18 19
20class GRenderWindow;
19class QKeyEvent; 21class QKeyEvent;
20class QScreen; 22class QScreen;
21class QTouchEvent; 23class QTouchEvent;
@@ -26,14 +28,6 @@ class QOpenGLContext;
26class QVulkanInstance; 28class QVulkanInstance;
27#endif 29#endif
28 30
29class GWidgetInternal;
30class GGLWidgetInternal;
31class GVKWidgetInternal;
32class GMainWindow;
33class GRenderWindow;
34class QSurface;
35class QOpenGLContext;
36
37namespace VideoCore { 31namespace VideoCore {
38enum class LoadCallbackStage; 32enum class LoadCallbackStage;
39} 33}
@@ -42,7 +36,7 @@ class EmuThread final : public QThread {
42 Q_OBJECT 36 Q_OBJECT
43 37
44public: 38public:
45 explicit EmuThread(GRenderWindow* render_window); 39 explicit EmuThread(GRenderWindow& window);
46 ~EmuThread() override; 40 ~EmuThread() override;
47 41
48 /** 42 /**
@@ -96,7 +90,11 @@ private:
96 std::mutex running_mutex; 90 std::mutex running_mutex;
97 std::condition_variable running_cv; 91 std::condition_variable running_cv;
98 92
99 GRenderWindow* render_window; 93 /// Only used in asynchronous GPU mode
94 std::unique_ptr<Core::Frontend::GraphicsContext> shared_context;
95
96 /// This is shared_context in asynchronous GPU mode, core_context in synchronous GPU mode
97 Core::Frontend::GraphicsContext& context;
100 98
101signals: 99signals:
102 /** 100 /**
@@ -126,11 +124,10 @@ class GRenderWindow : public QWidget, public Core::Frontend::EmuWindow {
126 Q_OBJECT 124 Q_OBJECT
127 125
128public: 126public:
129 GRenderWindow(GMainWindow* parent, EmuThread* emu_thread); 127 GRenderWindow(QWidget* parent, EmuThread* emu_thread);
130 ~GRenderWindow() override; 128 ~GRenderWindow() override;
131 129
132 // EmuWindow implementation 130 // EmuWindow implementation.
133 void SwapBuffers() override;
134 void MakeCurrent() override; 131 void MakeCurrent() override;
135 void DoneCurrent() override; 132 void DoneCurrent() override;
136 void PollEvents() override; 133 void PollEvents() override;
@@ -139,30 +136,36 @@ public:
139 void* surface) const override; 136 void* surface) const override;
140 std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override; 137 std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override;
141 138
142 void ForwardKeyPressEvent(QKeyEvent* event);
143 void ForwardKeyReleaseEvent(QKeyEvent* event);
144
145 void BackupGeometry(); 139 void BackupGeometry();
146 void RestoreGeometry(); 140 void RestoreGeometry();
147 void restoreGeometry(const QByteArray& geometry); // overridden 141 void restoreGeometry(const QByteArray& geometry); // overridden
148 QByteArray saveGeometry(); // overridden 142 QByteArray saveGeometry(); // overridden
149 143
150 qreal GetWindowPixelRatio() const; 144 qreal windowPixelRatio() const;
151 std::pair<u32, u32> ScaleTouch(QPointF pos) const;
152 145
153 void closeEvent(QCloseEvent* event) override; 146 void closeEvent(QCloseEvent* event) override;
147
148 void resizeEvent(QResizeEvent* event) override;
149
150 void keyPressEvent(QKeyEvent* event) override;
151 void keyReleaseEvent(QKeyEvent* event) override;
152
153 void mousePressEvent(QMouseEvent* event) override;
154 void mouseMoveEvent(QMouseEvent* event) override;
155 void mouseReleaseEvent(QMouseEvent* event) override;
156
154 bool event(QEvent* event) override; 157 bool event(QEvent* event) override;
155 void focusOutEvent(QFocusEvent* event) override;
156 158
157 void OnClientAreaResized(u32 width, u32 height); 159 void focusOutEvent(QFocusEvent* event) override;
158 160
159 bool InitRenderTarget(); 161 bool InitRenderTarget();
160 162
163 /// Destroy the previous run's child_widget which should also destroy the child_window
164 void ReleaseRenderTarget();
165
161 void CaptureScreenshot(u32 res_scale, const QString& screenshot_path); 166 void CaptureScreenshot(u32 res_scale, const QString& screenshot_path);
162 167
163public slots: 168public slots:
164 void moveContext(); // overridden
165
166 void OnEmulationStarting(EmuThread* emu_thread); 169 void OnEmulationStarting(EmuThread* emu_thread);
167 void OnEmulationStopping(); 170 void OnEmulationStopping();
168 void OnFramebufferSizeChanged(); 171 void OnFramebufferSizeChanged();
@@ -173,6 +176,7 @@ signals:
173 void FirstFrameDisplayed(); 176 void FirstFrameDisplayed();
174 177
175private: 178private:
179 std::pair<u32, u32> ScaleTouch(QPointF pos) const;
176 void TouchBeginEvent(const QTouchEvent* event); 180 void TouchBeginEvent(const QTouchEvent* event);
177 void TouchUpdateEvent(const QTouchEvent* event); 181 void TouchUpdateEvent(const QTouchEvent* event);
178 void TouchEndEvent(); 182 void TouchEndEvent();
@@ -184,15 +188,9 @@ private:
184 bool LoadOpenGL(); 188 bool LoadOpenGL();
185 QStringList GetUnsupportedGLExtensions() const; 189 QStringList GetUnsupportedGLExtensions() const;
186 190
187 QWidget* container = nullptr;
188 GWidgetInternal* child = nullptr;
189
190 EmuThread* emu_thread; 191 EmuThread* emu_thread;
191 // Context that backs the GGLWidgetInternal (and will be used by core to render) 192
192 std::unique_ptr<QOpenGLContext> context; 193 std::unique_ptr<GraphicsContext> core_context;
193 // Context that will be shared between all newly created contexts. This should never be made
194 // current
195 std::unique_ptr<QOpenGLContext> shared_context;
196 194
197#ifdef HAS_VULKAN 195#ifdef HAS_VULKAN
198 std::unique_ptr<QVulkanInstance> vk_instance; 196 std::unique_ptr<QVulkanInstance> vk_instance;
@@ -202,6 +200,15 @@ private:
202 QImage screenshot_image; 200 QImage screenshot_image;
203 201
204 QByteArray geometry; 202 QByteArray geometry;
203
204 /// Native window handle that backs this presentation widget
205 QWindow* child_window = nullptr;
206
207 /// In order to embed the window into GRenderWindow, you need to use createWindowContainer to
208 /// put the child_window into a widget then add it to the layout. This child_widget can be
209 /// parented to GRenderWindow and use Qt's lifetime system
210 QWidget* child_widget = nullptr;
211
205 bool first_frame = false; 212 bool first_frame = false;
206 213
207protected: 214protected:
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index 6209fff75..d0f574147 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -640,6 +640,7 @@ void Config::ReadRendererValues() {
640 ReadSetting(QStringLiteral("use_accurate_gpu_emulation"), false).toBool(); 640 ReadSetting(QStringLiteral("use_accurate_gpu_emulation"), false).toBool();
641 Settings::values.use_asynchronous_gpu_emulation = 641 Settings::values.use_asynchronous_gpu_emulation =
642 ReadSetting(QStringLiteral("use_asynchronous_gpu_emulation"), false).toBool(); 642 ReadSetting(QStringLiteral("use_asynchronous_gpu_emulation"), false).toBool();
643 Settings::values.use_vsync = ReadSetting(QStringLiteral("use_vsync"), true).toBool();
643 Settings::values.force_30fps_mode = 644 Settings::values.force_30fps_mode =
644 ReadSetting(QStringLiteral("force_30fps_mode"), false).toBool(); 645 ReadSetting(QStringLiteral("force_30fps_mode"), false).toBool();
645 646
@@ -1074,6 +1075,7 @@ void Config::SaveRendererValues() {
1074 Settings::values.use_accurate_gpu_emulation, false); 1075 Settings::values.use_accurate_gpu_emulation, false);
1075 WriteSetting(QStringLiteral("use_asynchronous_gpu_emulation"), 1076 WriteSetting(QStringLiteral("use_asynchronous_gpu_emulation"),
1076 Settings::values.use_asynchronous_gpu_emulation, false); 1077 Settings::values.use_asynchronous_gpu_emulation, false);
1078 WriteSetting(QStringLiteral("use_vsync"), Settings::values.use_vsync, true);
1077 WriteSetting(QStringLiteral("force_30fps_mode"), Settings::values.force_30fps_mode, false); 1079 WriteSetting(QStringLiteral("force_30fps_mode"), Settings::values.force_30fps_mode, false);
1078 1080
1079 // Cast to double because Qt's written float values are not human-readable 1081 // Cast to double because Qt's written float values are not human-readable
diff --git a/src/yuzu/configuration/configure_graphics.cpp b/src/yuzu/configuration/configure_graphics.cpp
index ea899c080..fe64c7d81 100644
--- a/src/yuzu/configuration/configure_graphics.cpp
+++ b/src/yuzu/configuration/configure_graphics.cpp
@@ -103,6 +103,8 @@ void ConfigureGraphics::SetConfiguration() {
103 ui->use_accurate_gpu_emulation->setChecked(Settings::values.use_accurate_gpu_emulation); 103 ui->use_accurate_gpu_emulation->setChecked(Settings::values.use_accurate_gpu_emulation);
104 ui->use_asynchronous_gpu_emulation->setEnabled(runtime_lock); 104 ui->use_asynchronous_gpu_emulation->setEnabled(runtime_lock);
105 ui->use_asynchronous_gpu_emulation->setChecked(Settings::values.use_asynchronous_gpu_emulation); 105 ui->use_asynchronous_gpu_emulation->setChecked(Settings::values.use_asynchronous_gpu_emulation);
106 ui->use_vsync->setEnabled(runtime_lock);
107 ui->use_vsync->setChecked(Settings::values.use_vsync);
106 ui->force_30fps_mode->setEnabled(runtime_lock); 108 ui->force_30fps_mode->setEnabled(runtime_lock);
107 ui->force_30fps_mode->setChecked(Settings::values.force_30fps_mode); 109 ui->force_30fps_mode->setChecked(Settings::values.force_30fps_mode);
108 UpdateBackgroundColorButton(QColor::fromRgbF(Settings::values.bg_red, Settings::values.bg_green, 110 UpdateBackgroundColorButton(QColor::fromRgbF(Settings::values.bg_red, Settings::values.bg_green,
@@ -120,6 +122,7 @@ void ConfigureGraphics::ApplyConfiguration() {
120 Settings::values.use_accurate_gpu_emulation = ui->use_accurate_gpu_emulation->isChecked(); 122 Settings::values.use_accurate_gpu_emulation = ui->use_accurate_gpu_emulation->isChecked();
121 Settings::values.use_asynchronous_gpu_emulation = 123 Settings::values.use_asynchronous_gpu_emulation =
122 ui->use_asynchronous_gpu_emulation->isChecked(); 124 ui->use_asynchronous_gpu_emulation->isChecked();
125 Settings::values.use_vsync = ui->use_vsync->isChecked();
123 Settings::values.force_30fps_mode = ui->force_30fps_mode->isChecked(); 126 Settings::values.force_30fps_mode = ui->force_30fps_mode->isChecked();
124 Settings::values.bg_red = static_cast<float>(bg_color.redF()); 127 Settings::values.bg_red = static_cast<float>(bg_color.redF());
125 Settings::values.bg_green = static_cast<float>(bg_color.greenF()); 128 Settings::values.bg_green = static_cast<float>(bg_color.greenF());
diff --git a/src/yuzu/configuration/configure_graphics.ui b/src/yuzu/configuration/configure_graphics.ui
index db60426ab..9acc7dd93 100644
--- a/src/yuzu/configuration/configure_graphics.ui
+++ b/src/yuzu/configuration/configure_graphics.ui
@@ -85,6 +85,16 @@
85 </widget> 85 </widget>
86 </item> 86 </item>
87 <item> 87 <item>
88 <widget class="QCheckBox" name="use_vsync">
89 <property name="toolTip">
90 <string>VSync prevents the screen from tearing, but some graphics cards have lower performance with VSync enabled. Keep it enabled if you don't notice a performance difference.</string>
91 </property>
92 <property name="text">
93 <string>Use VSync (OpenGL only)</string>
94 </property>
95 </widget>
96 </item>
97 <item>
88 <widget class="QCheckBox" name="use_accurate_gpu_emulation"> 98 <widget class="QCheckBox" name="use_accurate_gpu_emulation">
89 <property name="text"> 99 <property name="text">
90 <string>Use accurate GPU emulation (slow)</string> 100 <string>Use accurate GPU emulation (slow)</string>
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 54ca2dc1d..47615adfe 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -20,7 +20,6 @@
20#include "core/file_sys/vfs.h" 20#include "core/file_sys/vfs.h"
21#include "core/file_sys/vfs_real.h" 21#include "core/file_sys/vfs_real.h"
22#include "core/frontend/applets/general_frontend.h" 22#include "core/frontend/applets/general_frontend.h"
23#include "core/frontend/scope_acquire_window_context.h"
24#include "core/hle/service/acc/profile_manager.h" 23#include "core/hle/service/acc/profile_manager.h"
25#include "core/hle/service/am/applet_ae.h" 24#include "core/hle/service/am/applet_ae.h"
26#include "core/hle/service/am/applet_oe.h" 25#include "core/hle/service/am/applet_oe.h"
@@ -985,11 +984,8 @@ void GMainWindow::BootGame(const QString& filename) {
985 return; 984 return;
986 985
987 // Create and start the emulation thread 986 // Create and start the emulation thread
988 emu_thread = std::make_unique<EmuThread>(render_window); 987 emu_thread = std::make_unique<EmuThread>(*render_window);
989 emit EmulationStarting(emu_thread.get()); 988 emit EmulationStarting(emu_thread.get());
990 if (Settings::values.renderer_backend == Settings::RendererBackend::OpenGL) {
991 render_window->moveContext();
992 }
993 emu_thread->start(); 989 emu_thread->start();
994 990
995 connect(render_window, &GRenderWindow::Closed, this, &GMainWindow::OnStopGame); 991 connect(render_window, &GRenderWindow::Closed, this, &GMainWindow::OnStopGame);
@@ -1087,6 +1083,9 @@ void GMainWindow::ShutdownGame() {
1087 emulation_running = false; 1083 emulation_running = false;
1088 1084
1089 game_path.clear(); 1085 game_path.clear();
1086
1087 // When closing the game, destroy the GLWindow to clear the context after the game is closed
1088 render_window->ReleaseRenderTarget();
1090} 1089}
1091 1090
1092void GMainWindow::StoreRecentFile(const QString& filename) { 1091void GMainWindow::StoreRecentFile(const QString& filename) {
@@ -2215,48 +2214,47 @@ void GMainWindow::closeEvent(QCloseEvent* event) {
2215 QWidget::closeEvent(event); 2214 QWidget::closeEvent(event);
2216} 2215}
2217 2216
2218void GMainWindow::keyPressEvent(QKeyEvent* event) { 2217static bool IsSingleFileDropEvent(const QMimeData* mime) {
2219 if (render_window) { 2218 return mime->hasUrls() && mime->urls().length() == 1;
2220 render_window->ForwardKeyPressEvent(event);
2221 }
2222} 2219}
2223 2220
2224void GMainWindow::keyReleaseEvent(QKeyEvent* event) { 2221void GMainWindow::AcceptDropEvent(QDropEvent* event) {
2225 if (render_window) { 2222 if (IsSingleFileDropEvent(event->mimeData())) {
2226 render_window->ForwardKeyReleaseEvent(event); 2223 event->setDropAction(Qt::DropAction::LinkAction);
2224 event->accept();
2227 } 2225 }
2228} 2226}
2229 2227
2230static bool IsSingleFileDropEvent(QDropEvent* event) { 2228bool GMainWindow::DropAction(QDropEvent* event) {
2231 const QMimeData* mimeData = event->mimeData(); 2229 if (!IsSingleFileDropEvent(event->mimeData())) {
2232 return mimeData->hasUrls() && mimeData->urls().length() == 1; 2230 return false;
2233}
2234
2235void GMainWindow::dropEvent(QDropEvent* event) {
2236 if (!IsSingleFileDropEvent(event)) {
2237 return;
2238 } 2231 }
2239 2232
2240 const QMimeData* mime_data = event->mimeData(); 2233 const QMimeData* mime_data = event->mimeData();
2241 const QString filename = mime_data->urls().at(0).toLocalFile(); 2234 const QString& filename = mime_data->urls().at(0).toLocalFile();
2242 2235
2243 if (emulation_running && QFileInfo(filename).suffix() == QStringLiteral("bin")) { 2236 if (emulation_running && QFileInfo(filename).suffix() == QStringLiteral("bin")) {
2237 // Amiibo
2244 LoadAmiibo(filename); 2238 LoadAmiibo(filename);
2245 } else { 2239 } else {
2240 // Game
2246 if (ConfirmChangeGame()) { 2241 if (ConfirmChangeGame()) {
2247 BootGame(filename); 2242 BootGame(filename);
2248 } 2243 }
2249 } 2244 }
2245 return true;
2246}
2247
2248void GMainWindow::dropEvent(QDropEvent* event) {
2249 DropAction(event);
2250} 2250}
2251 2251
2252void GMainWindow::dragEnterEvent(QDragEnterEvent* event) { 2252void GMainWindow::dragEnterEvent(QDragEnterEvent* event) {
2253 if (IsSingleFileDropEvent(event)) { 2253 AcceptDropEvent(event);
2254 event->acceptProposedAction();
2255 }
2256} 2254}
2257 2255
2258void GMainWindow::dragMoveEvent(QDragMoveEvent* event) { 2256void GMainWindow::dragMoveEvent(QDragMoveEvent* event) {
2259 event->acceptProposedAction(); 2257 AcceptDropEvent(event);
2260} 2258}
2261 2259
2262bool GMainWindow::ConfirmChangeGame() { 2260bool GMainWindow::ConfirmChangeGame() {
@@ -2377,6 +2375,7 @@ int main(int argc, char* argv[]) {
2377 2375
2378 // Enables the core to make the qt created contexts current on std::threads 2376 // Enables the core to make the qt created contexts current on std::threads
2379 QCoreApplication::setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity); 2377 QCoreApplication::setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity);
2378 QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts);
2380 QApplication app(argc, argv); 2379 QApplication app(argc, argv);
2381 2380
2382 // Qt changes the locale and causes issues in float conversion using std::to_string() when 2381 // Qt changes the locale and causes issues in float conversion using std::to_string() when
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index 8eba2172c..a67125567 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -78,6 +78,9 @@ public:
78 78
79 std::unique_ptr<DiscordRPC::DiscordInterface> discord_rpc; 79 std::unique_ptr<DiscordRPC::DiscordInterface> discord_rpc;
80 80
81 bool DropAction(QDropEvent* event);
82 void AcceptDropEvent(QDropEvent* event);
83
81signals: 84signals:
82 85
83 /** 86 /**
@@ -264,8 +267,4 @@ protected:
264 void dropEvent(QDropEvent* event) override; 267 void dropEvent(QDropEvent* event) override;
265 void dragEnterEvent(QDragEnterEvent* event) override; 268 void dragEnterEvent(QDragEnterEvent* event) override;
266 void dragMoveEvent(QDragMoveEvent* event) override; 269 void dragMoveEvent(QDragMoveEvent* event) override;
267
268 // Overrides used to forward signals to the render window when the focus moves out.
269 void keyPressEvent(QKeyEvent* event) override;
270 void keyReleaseEvent(QKeyEvent* event) override;
271}; 270};
diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp
index 96f1ce3af..b77c12baf 100644
--- a/src/yuzu_cmd/config.cpp
+++ b/src/yuzu_cmd/config.cpp
@@ -390,6 +390,8 @@ void Config::ReadValues() {
390 sdl2_config->GetBoolean("Renderer", "use_accurate_gpu_emulation", false); 390 sdl2_config->GetBoolean("Renderer", "use_accurate_gpu_emulation", false);
391 Settings::values.use_asynchronous_gpu_emulation = 391 Settings::values.use_asynchronous_gpu_emulation =
392 sdl2_config->GetBoolean("Renderer", "use_asynchronous_gpu_emulation", false); 392 sdl2_config->GetBoolean("Renderer", "use_asynchronous_gpu_emulation", false);
393 Settings::values.use_vsync =
394 static_cast<u16>(sdl2_config->GetInteger("Renderer", "use_vsync", 1));
393 395
394 Settings::values.bg_red = static_cast<float>(sdl2_config->GetReal("Renderer", "bg_red", 0.0)); 396 Settings::values.bg_red = static_cast<float>(sdl2_config->GetReal("Renderer", "bg_red", 0.0));
395 Settings::values.bg_green = 397 Settings::values.bg_green =
diff --git a/src/yuzu_cmd/default_ini.h b/src/yuzu_cmd/default_ini.h
index 8a2b658cd..df7473858 100644
--- a/src/yuzu_cmd/default_ini.h
+++ b/src/yuzu_cmd/default_ini.h
@@ -150,6 +150,11 @@ use_accurate_gpu_emulation =
150# 0 : Off (slow), 1 (default): On (fast) 150# 0 : Off (slow), 1 (default): On (fast)
151use_asynchronous_gpu_emulation = 151use_asynchronous_gpu_emulation =
152 152
153# Forces VSync on the display thread. Usually doesn't impact performance, but on some drivers it can
154# so only turn this off if you notice a speed difference.
155# 0: Off, 1 (default): On
156use_vsync =
157
153# The clear color for the renderer. What shows up on the sides of the bottom screen. 158# The clear color for the renderer. What shows up on the sides of the bottom screen.
154# Must be in range of 0.0-1.0. Defaults to 1.0 for all. 159# Must be in range of 0.0-1.0. Defaults to 1.0 for all.
155bg_red = 160bg_red =
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp
index e96139885..19584360c 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp
@@ -13,7 +13,7 @@
13#include "input_common/sdl/sdl.h" 13#include "input_common/sdl/sdl.h"
14#include "yuzu_cmd/emu_window/emu_window_sdl2.h" 14#include "yuzu_cmd/emu_window/emu_window_sdl2.h"
15 15
16EmuWindow_SDL2::EmuWindow_SDL2(bool fullscreen) { 16EmuWindow_SDL2::EmuWindow_SDL2(Core::System& system, bool fullscreen) : system{system} {
17 if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK) < 0) { 17 if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK) < 0) {
18 LOG_CRITICAL(Frontend, "Failed to initialize SDL2! Exiting..."); 18 LOG_CRITICAL(Frontend, "Failed to initialize SDL2! Exiting...");
19 exit(1); 19 exit(1);
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.h b/src/yuzu_cmd/emu_window/emu_window_sdl2.h
index b38f56661..fffac4252 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2.h
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.h
@@ -10,9 +10,13 @@
10 10
11struct SDL_Window; 11struct SDL_Window;
12 12
13namespace Core {
14class System;
15}
16
13class EmuWindow_SDL2 : public Core::Frontend::EmuWindow { 17class EmuWindow_SDL2 : public Core::Frontend::EmuWindow {
14public: 18public:
15 explicit EmuWindow_SDL2(bool fullscreen); 19 explicit EmuWindow_SDL2(Core::System& system, bool fullscreen);
16 ~EmuWindow_SDL2(); 20 ~EmuWindow_SDL2();
17 21
18 /// Polls window events 22 /// Polls window events
@@ -24,6 +28,9 @@ public:
24 /// Returns if window is shown (not minimized) 28 /// Returns if window is shown (not minimized)
25 bool IsShown() const override; 29 bool IsShown() const override;
26 30
31 /// Presents the next frame
32 virtual void Present() = 0;
33
27protected: 34protected:
28 /// Called by PollEvents when a key is pressed or released. 35 /// Called by PollEvents when a key is pressed or released.
29 void OnKeyEvent(int key, u8 state); 36 void OnKeyEvent(int key, u8 state);
@@ -55,6 +62,9 @@ protected:
55 /// Called when a configuration change affects the minimal size of the window 62 /// Called when a configuration change affects the minimal size of the window
56 void OnMinimalClientAreaChangeRequest(std::pair<unsigned, unsigned> minimal_size) override; 63 void OnMinimalClientAreaChangeRequest(std::pair<unsigned, unsigned> minimal_size) override;
57 64
65 /// Instance of the system, used to access renderer for the presentation thread
66 Core::System& system;
67
58 /// Is the window still open? 68 /// Is the window still open?
59 bool is_open = true; 69 bool is_open = true;
60 70
@@ -62,7 +72,7 @@ protected:
62 bool is_shown = true; 72 bool is_shown = true;
63 73
64 /// Internal SDL2 render window 74 /// Internal SDL2 render window
65 SDL_Window* render_window; 75 SDL_Window* render_window{};
66 76
67 /// Keeps track of how often to update the title bar during gameplay 77 /// Keeps track of how often to update the title bar during gameplay
68 u32 last_time = 0; 78 u32 last_time = 0;
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp
index 7ffa0ac09..c0d373477 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp
@@ -13,24 +13,25 @@
13#include "common/logging/log.h" 13#include "common/logging/log.h"
14#include "common/scm_rev.h" 14#include "common/scm_rev.h"
15#include "common/string_util.h" 15#include "common/string_util.h"
16#include "core/core.h"
16#include "core/settings.h" 17#include "core/settings.h"
17#include "input_common/keyboard.h" 18#include "input_common/keyboard.h"
18#include "input_common/main.h" 19#include "input_common/main.h"
19#include "input_common/motion_emu.h" 20#include "input_common/motion_emu.h"
21#include "video_core/renderer_base.h"
20#include "yuzu_cmd/emu_window/emu_window_sdl2_gl.h" 22#include "yuzu_cmd/emu_window/emu_window_sdl2_gl.h"
21 23
22class SDLGLContext : public Core::Frontend::GraphicsContext { 24class SDLGLContext : public Core::Frontend::GraphicsContext {
23public: 25public:
24 explicit SDLGLContext() { 26 explicit SDLGLContext() {
25 // create a hidden window to make the shared context against 27 // create a hidden window to make the shared context against
26 window = SDL_CreateWindow("", SDL_WINDOWPOS_UNDEFINED, // x position 28 window = SDL_CreateWindow(NULL, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 0, 0,
27 SDL_WINDOWPOS_UNDEFINED, // y position 29 SDL_WINDOW_HIDDEN | SDL_WINDOW_OPENGL);
28 Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height,
29 SDL_WINDOW_OPENGL | SDL_WINDOW_HIDDEN);
30 context = SDL_GL_CreateContext(window); 30 context = SDL_GL_CreateContext(window);
31 } 31 }
32 32
33 ~SDLGLContext() { 33 ~SDLGLContext() {
34 DoneCurrent();
34 SDL_GL_DeleteContext(context); 35 SDL_GL_DeleteContext(context);
35 SDL_DestroyWindow(window); 36 SDL_DestroyWindow(window);
36 } 37 }
@@ -43,8 +44,6 @@ public:
43 SDL_GL_MakeCurrent(window, nullptr); 44 SDL_GL_MakeCurrent(window, nullptr);
44 } 45 }
45 46
46 void SwapBuffers() override {}
47
48private: 47private:
49 SDL_Window* window; 48 SDL_Window* window;
50 SDL_GLContext context; 49 SDL_GLContext context;
@@ -80,7 +79,8 @@ bool EmuWindow_SDL2_GL::SupportsRequiredGLExtensions() {
80 return unsupported_ext.empty(); 79 return unsupported_ext.empty();
81} 80}
82 81
83EmuWindow_SDL2_GL::EmuWindow_SDL2_GL(bool fullscreen) : EmuWindow_SDL2(fullscreen) { 82EmuWindow_SDL2_GL::EmuWindow_SDL2_GL(Core::System& system, bool fullscreen)
83 : EmuWindow_SDL2{system, fullscreen} {
84 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4); 84 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
85 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); 85 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
86 SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_COMPATIBILITY); 86 SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_COMPATIBILITY);
@@ -90,6 +90,7 @@ EmuWindow_SDL2_GL::EmuWindow_SDL2_GL(bool fullscreen) : EmuWindow_SDL2(fullscree
90 SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8); 90 SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
91 SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 0); 91 SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 0);
92 SDL_GL_SetAttribute(SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 1); 92 SDL_GL_SetAttribute(SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 1);
93 SDL_GL_SetSwapInterval(0);
93 94
94 std::string window_title = fmt::format("yuzu {} | {}-{}", Common::g_build_fullname, 95 std::string window_title = fmt::format("yuzu {} | {}-{}", Common::g_build_fullname,
95 Common::g_scm_branch, Common::g_scm_desc); 96 Common::g_scm_branch, Common::g_scm_desc);
@@ -105,13 +106,22 @@ EmuWindow_SDL2_GL::EmuWindow_SDL2_GL(bool fullscreen) : EmuWindow_SDL2(fullscree
105 exit(1); 106 exit(1);
106 } 107 }
107 108
109 dummy_window = SDL_CreateWindow(NULL, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 0, 0,
110 SDL_WINDOW_HIDDEN | SDL_WINDOW_OPENGL);
111
108 if (fullscreen) { 112 if (fullscreen) {
109 Fullscreen(); 113 Fullscreen();
110 } 114 }
111 gl_context = SDL_GL_CreateContext(render_window);
112 115
113 if (gl_context == nullptr) { 116 window_context = SDL_GL_CreateContext(render_window);
114 LOG_CRITICAL(Frontend, "Failed to create SDL2 GL context! {}", SDL_GetError()); 117 core_context = CreateSharedContext();
118
119 if (window_context == nullptr) {
120 LOG_CRITICAL(Frontend, "Failed to create SDL2 GL context: {}", SDL_GetError());
121 exit(1);
122 }
123 if (core_context == nullptr) {
124 LOG_CRITICAL(Frontend, "Failed to create shared SDL2 GL context: {}", SDL_GetError());
115 exit(1); 125 exit(1);
116 } 126 }
117 127
@@ -128,28 +138,22 @@ EmuWindow_SDL2_GL::EmuWindow_SDL2_GL(bool fullscreen) : EmuWindow_SDL2(fullscree
128 OnResize(); 138 OnResize();
129 OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size); 139 OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size);
130 SDL_PumpEvents(); 140 SDL_PumpEvents();
131 SDL_GL_SetSwapInterval(false);
132 LOG_INFO(Frontend, "yuzu Version: {} | {}-{}", Common::g_build_fullname, Common::g_scm_branch, 141 LOG_INFO(Frontend, "yuzu Version: {} | {}-{}", Common::g_build_fullname, Common::g_scm_branch,
133 Common::g_scm_desc); 142 Common::g_scm_desc);
134 Settings::LogSettings(); 143 Settings::LogSettings();
135
136 DoneCurrent();
137} 144}
138 145
139EmuWindow_SDL2_GL::~EmuWindow_SDL2_GL() { 146EmuWindow_SDL2_GL::~EmuWindow_SDL2_GL() {
140 SDL_GL_DeleteContext(gl_context); 147 core_context.reset();
141} 148 SDL_GL_DeleteContext(window_context);
142
143void EmuWindow_SDL2_GL::SwapBuffers() {
144 SDL_GL_SwapWindow(render_window);
145} 149}
146 150
147void EmuWindow_SDL2_GL::MakeCurrent() { 151void EmuWindow_SDL2_GL::MakeCurrent() {
148 SDL_GL_MakeCurrent(render_window, gl_context); 152 core_context->MakeCurrent();
149} 153}
150 154
151void EmuWindow_SDL2_GL::DoneCurrent() { 155void EmuWindow_SDL2_GL::DoneCurrent() {
152 SDL_GL_MakeCurrent(render_window, nullptr); 156 core_context->DoneCurrent();
153} 157}
154 158
155void EmuWindow_SDL2_GL::RetrieveVulkanHandlers(void* get_instance_proc_addr, void* instance, 159void EmuWindow_SDL2_GL::RetrieveVulkanHandlers(void* get_instance_proc_addr, void* instance,
@@ -161,3 +165,13 @@ void EmuWindow_SDL2_GL::RetrieveVulkanHandlers(void* get_instance_proc_addr, voi
161std::unique_ptr<Core::Frontend::GraphicsContext> EmuWindow_SDL2_GL::CreateSharedContext() const { 165std::unique_ptr<Core::Frontend::GraphicsContext> EmuWindow_SDL2_GL::CreateSharedContext() const {
162 return std::make_unique<SDLGLContext>(); 166 return std::make_unique<SDLGLContext>();
163} 167}
168
169void EmuWindow_SDL2_GL::Present() {
170 SDL_GL_MakeCurrent(render_window, window_context);
171 SDL_GL_SetSwapInterval(Settings::values.use_vsync ? 1 : 0);
172 while (IsOpen()) {
173 system.Renderer().TryPresent(100);
174 SDL_GL_SwapWindow(render_window);
175 }
176 SDL_GL_MakeCurrent(render_window, nullptr);
177}
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h
index c753085a8..b80669ff0 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h
@@ -10,17 +10,12 @@
10 10
11class EmuWindow_SDL2_GL final : public EmuWindow_SDL2 { 11class EmuWindow_SDL2_GL final : public EmuWindow_SDL2 {
12public: 12public:
13 explicit EmuWindow_SDL2_GL(bool fullscreen); 13 explicit EmuWindow_SDL2_GL(Core::System& system, bool fullscreen);
14 ~EmuWindow_SDL2_GL(); 14 ~EmuWindow_SDL2_GL();
15 15
16 /// Swap buffers to display the next frame
17 void SwapBuffers() override;
18
19 /// Makes the graphics context current for the caller thread
20 void MakeCurrent() override; 16 void MakeCurrent() override;
21
22 /// Releases the GL context from the caller thread
23 void DoneCurrent() override; 17 void DoneCurrent() override;
18 void Present() override;
24 19
25 /// Ignored in OpenGL 20 /// Ignored in OpenGL
26 void RetrieveVulkanHandlers(void* get_instance_proc_addr, void* instance, 21 void RetrieveVulkanHandlers(void* get_instance_proc_addr, void* instance,
@@ -29,10 +24,17 @@ public:
29 std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override; 24 std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override;
30 25
31private: 26private:
27 /// Fake hidden window for the core context
28 SDL_Window* dummy_window{};
29
32 /// Whether the GPU and driver supports the OpenGL extension required 30 /// Whether the GPU and driver supports the OpenGL extension required
33 bool SupportsRequiredGLExtensions(); 31 bool SupportsRequiredGLExtensions();
34 32
35 using SDL_GLContext = void*; 33 using SDL_GLContext = void*;
34
36 /// The OpenGL context associated with the window 35 /// The OpenGL context associated with the window
37 SDL_GLContext gl_context; 36 SDL_GLContext window_context;
37
38 /// The OpenGL context associated with the core
39 std::unique_ptr<Core::Frontend::GraphicsContext> core_context;
38}; 40};
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp
index a203f0da9..abcc58165 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp
@@ -15,7 +15,8 @@
15#include "core/settings.h" 15#include "core/settings.h"
16#include "yuzu_cmd/emu_window/emu_window_sdl2_vk.h" 16#include "yuzu_cmd/emu_window/emu_window_sdl2_vk.h"
17 17
18EmuWindow_SDL2_VK::EmuWindow_SDL2_VK(bool fullscreen) : EmuWindow_SDL2(fullscreen) { 18EmuWindow_SDL2_VK::EmuWindow_SDL2_VK(Core::System& system, bool fullscreen)
19 : EmuWindow_SDL2{system, fullscreen} {
19 if (SDL_Vulkan_LoadLibrary(nullptr) != 0) { 20 if (SDL_Vulkan_LoadLibrary(nullptr) != 0) {
20 LOG_CRITICAL(Frontend, "SDL failed to load the Vulkan library: {}", SDL_GetError()); 21 LOG_CRITICAL(Frontend, "SDL failed to load the Vulkan library: {}", SDL_GetError());
21 exit(EXIT_FAILURE); 22 exit(EXIT_FAILURE);
@@ -110,8 +111,6 @@ EmuWindow_SDL2_VK::~EmuWindow_SDL2_VK() {
110 vkDestroyInstance(vk_instance, nullptr); 111 vkDestroyInstance(vk_instance, nullptr);
111} 112}
112 113
113void EmuWindow_SDL2_VK::SwapBuffers() {}
114
115void EmuWindow_SDL2_VK::MakeCurrent() { 114void EmuWindow_SDL2_VK::MakeCurrent() {
116 // Unused on Vulkan 115 // Unused on Vulkan
117} 116}
@@ -160,3 +159,7 @@ bool EmuWindow_SDL2_VK::UseStandardLayers(PFN_vkGetInstanceProcAddr vkGetInstanc
160 return layer.layerName == std::string("VK_LAYER_LUNARG_standard_validation"); 159 return layer.layerName == std::string("VK_LAYER_LUNARG_standard_validation");
161 }) != layers.end(); 160 }) != layers.end();
162} 161}
162
163void EmuWindow_SDL2_VK::Present() {
164 // TODO (bunnei): ImplementMe
165}
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.h b/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.h
index 2a7c06a24..1eb8c0868 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.h
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.h
@@ -10,19 +10,12 @@
10 10
11class EmuWindow_SDL2_VK final : public EmuWindow_SDL2 { 11class EmuWindow_SDL2_VK final : public EmuWindow_SDL2 {
12public: 12public:
13 explicit EmuWindow_SDL2_VK(bool fullscreen); 13 explicit EmuWindow_SDL2_VK(Core::System& system, bool fullscreen);
14 ~EmuWindow_SDL2_VK(); 14 ~EmuWindow_SDL2_VK();
15 15
16 /// Swap buffers to display the next frame
17 void SwapBuffers() override;
18
19 /// Makes the graphics context current for the caller thread
20 void MakeCurrent() override; 16 void MakeCurrent() override;
21
22 /// Releases the GL context from the caller thread
23 void DoneCurrent() override; 17 void DoneCurrent() override;
24 18 void Present() override;
25 /// Retrieves Vulkan specific handlers from the window
26 void RetrieveVulkanHandlers(void* get_instance_proc_addr, void* instance, 19 void RetrieveVulkanHandlers(void* get_instance_proc_addr, void* instance,
27 void* surface) const override; 20 void* surface) const override;
28 21
diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp
index 325795321..babf4c3a4 100644
--- a/src/yuzu_cmd/yuzu.cpp
+++ b/src/yuzu_cmd/yuzu.cpp
@@ -177,14 +177,16 @@ int main(int argc, char** argv) {
177 Settings::values.use_gdbstub = use_gdbstub; 177 Settings::values.use_gdbstub = use_gdbstub;
178 Settings::Apply(); 178 Settings::Apply();
179 179
180 Core::System& system{Core::System::GetInstance()};
181
180 std::unique_ptr<EmuWindow_SDL2> emu_window; 182 std::unique_ptr<EmuWindow_SDL2> emu_window;
181 switch (Settings::values.renderer_backend) { 183 switch (Settings::values.renderer_backend) {
182 case Settings::RendererBackend::OpenGL: 184 case Settings::RendererBackend::OpenGL:
183 emu_window = std::make_unique<EmuWindow_SDL2_GL>(fullscreen); 185 emu_window = std::make_unique<EmuWindow_SDL2_GL>(system, fullscreen);
184 break; 186 break;
185 case Settings::RendererBackend::Vulkan: 187 case Settings::RendererBackend::Vulkan:
186#ifdef HAS_VULKAN 188#ifdef HAS_VULKAN
187 emu_window = std::make_unique<EmuWindow_SDL2_VK>(fullscreen); 189 emu_window = std::make_unique<EmuWindow_SDL2_VK>(system, fullscreen);
188 break; 190 break;
189#else 191#else
190 LOG_CRITICAL(Frontend, "Vulkan backend has not been compiled!"); 192 LOG_CRITICAL(Frontend, "Vulkan backend has not been compiled!");
@@ -192,12 +194,6 @@ int main(int argc, char** argv) {
192#endif 194#endif
193 } 195 }
194 196
195 if (!Settings::values.use_multi_core) {
196 // Single core mode must acquire OpenGL context for entire emulation session
197 emu_window->MakeCurrent();
198 }
199
200 Core::System& system{Core::System::GetInstance()};
201 system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>()); 197 system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>());
202 system.SetFilesystem(std::make_shared<FileSys::RealVfsFilesystem>()); 198 system.SetFilesystem(std::make_shared<FileSys::RealVfsFilesystem>());
203 system.GetFileSystemController().CreateFactories(*system.GetFilesystem()); 199 system.GetFileSystemController().CreateFactories(*system.GetFilesystem());
@@ -234,12 +230,23 @@ int main(int argc, char** argv) {
234 230
235 system.TelemetrySession().AddField(Telemetry::FieldType::App, "Frontend", "SDL"); 231 system.TelemetrySession().AddField(Telemetry::FieldType::App, "Frontend", "SDL");
236 232
237 emu_window->MakeCurrent();
238 system.Renderer().Rasterizer().LoadDiskResources(); 233 system.Renderer().Rasterizer().LoadDiskResources();
239 234
235 // Acquire render context for duration of the thread if this is the rendering thread
236 if (!Settings::values.use_asynchronous_gpu_emulation) {
237 emu_window->MakeCurrent();
238 }
239 SCOPE_EXIT({
240 if (!Settings::values.use_asynchronous_gpu_emulation) {
241 emu_window->DoneCurrent();
242 }
243 });
244
245 std::thread render_thread([&emu_window] { emu_window->Present(); });
240 while (emu_window->IsOpen()) { 246 while (emu_window->IsOpen()) {
241 system.RunLoop(); 247 system.RunLoop();
242 } 248 }
249 render_thread.join();
243 250
244 system.Shutdown(); 251 system.Shutdown();
245 252
diff --git a/src/yuzu_tester/emu_window/emu_window_sdl2_hide.cpp b/src/yuzu_tester/emu_window/emu_window_sdl2_hide.cpp
index f2cc4a797..a1bdb1a12 100644
--- a/src/yuzu_tester/emu_window/emu_window_sdl2_hide.cpp
+++ b/src/yuzu_tester/emu_window/emu_window_sdl2_hide.cpp
@@ -112,10 +112,6 @@ EmuWindow_SDL2_Hide::~EmuWindow_SDL2_Hide() {
112 SDL_Quit(); 112 SDL_Quit();
113} 113}
114 114
115void EmuWindow_SDL2_Hide::SwapBuffers() {
116 SDL_GL_SwapWindow(render_window);
117}
118
119void EmuWindow_SDL2_Hide::PollEvents() {} 115void EmuWindow_SDL2_Hide::PollEvents() {}
120 116
121void EmuWindow_SDL2_Hide::MakeCurrent() { 117void EmuWindow_SDL2_Hide::MakeCurrent() {
diff --git a/src/yuzu_tester/emu_window/emu_window_sdl2_hide.h b/src/yuzu_tester/emu_window/emu_window_sdl2_hide.h
index c7fccc002..b13e15309 100644
--- a/src/yuzu_tester/emu_window/emu_window_sdl2_hide.h
+++ b/src/yuzu_tester/emu_window/emu_window_sdl2_hide.h
@@ -13,9 +13,6 @@ public:
13 explicit EmuWindow_SDL2_Hide(); 13 explicit EmuWindow_SDL2_Hide();
14 ~EmuWindow_SDL2_Hide(); 14 ~EmuWindow_SDL2_Hide();
15 15
16 /// Swap buffers to display the next frame
17 void SwapBuffers() override;
18
19 /// Polls window events 16 /// Polls window events
20 void PollEvents() override; 17 void PollEvents() override;
21 18