From 2e16c237845bf1b5ff89b7b7a3f8bc1a84729eb1 Mon Sep 17 00:00:00 2001
From: bunnei <bunneidev@gmail.com>
Date: Mon, 17 Feb 2020 15:35:14 -0500
Subject: [PATCH] frontend: sdl2: emu_window: Implement separate presentation
 thread.

---
 src/core/frontend/emu_window.h                |  3 --
 src/yuzu_cmd/emu_window/emu_window_sdl2.cpp   |  2 +-
 src/yuzu_cmd/emu_window/emu_window_sdl2.h     | 14 ++++-
 .../emu_window/emu_window_sdl2_gl.cpp         | 54 ++++++++++++-------
 src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h  | 18 ++++---
 .../emu_window/emu_window_sdl2_vk.cpp         |  7 +--
 src/yuzu_cmd/emu_window/emu_window_sdl2_vk.h  | 11 +---
 src/yuzu_cmd/yuzu.cpp                         | 25 +++++----
 .../emu_window/emu_window_sdl2_hide.cpp       |  4 --
 .../emu_window/emu_window_sdl2_hide.h         |  3 --
 10 files changed, 79 insertions(+), 62 deletions(-)

diff --git a/src/core/frontend/emu_window.h b/src/core/frontend/emu_window.h
index 5dde199d4..856cb61e9 100644
--- a/src/core/frontend/emu_window.h
+++ b/src/core/frontend/emu_window.h
@@ -65,9 +65,6 @@ public:
 
     /// Releases (dunno if this is the "right" word) the context from the caller thread
     virtual void DoneCurrent() = 0;
-
-    /// Swap buffers to display the next frame
-    virtual void SwapBuffers() = 0;
 };
 
 /**
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 @@
 #include "input_common/sdl/sdl.h"
 #include "yuzu_cmd/emu_window/emu_window_sdl2.h"
 
-EmuWindow_SDL2::EmuWindow_SDL2(bool fullscreen) {
+EmuWindow_SDL2::EmuWindow_SDL2(Core::System& system, bool fullscreen) : system{system} {
     if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK) < 0) {
         LOG_CRITICAL(Frontend, "Failed to initialize SDL2! Exiting...");
         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 @@
 
 struct SDL_Window;
 
+namespace Core {
+class System;
+}
+
 class EmuWindow_SDL2 : public Core::Frontend::EmuWindow {
 public:
-    explicit EmuWindow_SDL2(bool fullscreen);
+    explicit EmuWindow_SDL2(Core::System& system, bool fullscreen);
     ~EmuWindow_SDL2();
 
     /// Polls window events
@@ -24,6 +28,9 @@ public:
     /// Returns if window is shown (not minimized)
     bool IsShown() const override;
 
+    /// Presents the next frame
+    virtual void Present() = 0;
+
 protected:
     /// Called by PollEvents when a key is pressed or released.
     void OnKeyEvent(int key, u8 state);
@@ -55,6 +62,9 @@ protected:
     /// Called when a configuration change affects the minimal size of the window
     void OnMinimalClientAreaChangeRequest(std::pair<unsigned, unsigned> minimal_size) override;
 
+    /// Instance of the system, used to access renderer for the presentation thread
+    Core::System& system;
+
     /// Is the window still open?
     bool is_open = true;
 
@@ -62,7 +72,7 @@ protected:
     bool is_shown = true;
 
     /// Internal SDL2 render window
-    SDL_Window* render_window;
+    SDL_Window* render_window{};
 
     /// Keeps track of how often to update the title bar during gameplay
     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 @@
 #include "common/logging/log.h"
 #include "common/scm_rev.h"
 #include "common/string_util.h"
+#include "core/core.h"
 #include "core/settings.h"
 #include "input_common/keyboard.h"
 #include "input_common/main.h"
 #include "input_common/motion_emu.h"
+#include "video_core/renderer_base.h"
 #include "yuzu_cmd/emu_window/emu_window_sdl2_gl.h"
 
 class SDLGLContext : public Core::Frontend::GraphicsContext {
 public:
     explicit SDLGLContext() {
         // create a hidden window to make the shared context against
-        window = SDL_CreateWindow("", SDL_WINDOWPOS_UNDEFINED, // x position
-                                  SDL_WINDOWPOS_UNDEFINED,     // y position
-                                  Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height,
-                                  SDL_WINDOW_OPENGL | SDL_WINDOW_HIDDEN);
+        window = SDL_CreateWindow(NULL, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 0, 0,
+                                  SDL_WINDOW_HIDDEN | SDL_WINDOW_OPENGL);
         context = SDL_GL_CreateContext(window);
     }
 
     ~SDLGLContext() {
+        DoneCurrent();
         SDL_GL_DeleteContext(context);
         SDL_DestroyWindow(window);
     }
@@ -43,8 +44,6 @@ public:
         SDL_GL_MakeCurrent(window, nullptr);
     }
 
-    void SwapBuffers() override {}
-
 private:
     SDL_Window* window;
     SDL_GLContext context;
@@ -80,7 +79,8 @@ bool EmuWindow_SDL2_GL::SupportsRequiredGLExtensions() {
     return unsupported_ext.empty();
 }
 
-EmuWindow_SDL2_GL::EmuWindow_SDL2_GL(bool fullscreen) : EmuWindow_SDL2(fullscreen) {
+EmuWindow_SDL2_GL::EmuWindow_SDL2_GL(Core::System& system, bool fullscreen)
+    : EmuWindow_SDL2{system, fullscreen} {
     SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
     SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
     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
     SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
     SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 0);
     SDL_GL_SetAttribute(SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 1);
+    SDL_GL_SetSwapInterval(0);
 
     std::string window_title = fmt::format("yuzu {} | {}-{}", Common::g_build_fullname,
                                            Common::g_scm_branch, Common::g_scm_desc);
@@ -105,13 +106,22 @@ EmuWindow_SDL2_GL::EmuWindow_SDL2_GL(bool fullscreen) : EmuWindow_SDL2(fullscree
         exit(1);
     }
 
+    dummy_window = SDL_CreateWindow(NULL, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 0, 0,
+                                    SDL_WINDOW_HIDDEN | SDL_WINDOW_OPENGL);
+
     if (fullscreen) {
         Fullscreen();
     }
-    gl_context = SDL_GL_CreateContext(render_window);
 
-    if (gl_context == nullptr) {
-        LOG_CRITICAL(Frontend, "Failed to create SDL2 GL context! {}", SDL_GetError());
+    window_context = SDL_GL_CreateContext(render_window);
+    core_context = CreateSharedContext();
+
+    if (window_context == nullptr) {
+        LOG_CRITICAL(Frontend, "Failed to create SDL2 GL context: {}", SDL_GetError());
+        exit(1);
+    }
+    if (core_context == nullptr) {
+        LOG_CRITICAL(Frontend, "Failed to create shared SDL2 GL context: {}", SDL_GetError());
         exit(1);
     }
 
@@ -128,28 +138,22 @@ EmuWindow_SDL2_GL::EmuWindow_SDL2_GL(bool fullscreen) : EmuWindow_SDL2(fullscree
     OnResize();
     OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size);
     SDL_PumpEvents();
-    SDL_GL_SetSwapInterval(false);
     LOG_INFO(Frontend, "yuzu Version: {} | {}-{}", Common::g_build_fullname, Common::g_scm_branch,
              Common::g_scm_desc);
     Settings::LogSettings();
-
-    DoneCurrent();
 }
 
 EmuWindow_SDL2_GL::~EmuWindow_SDL2_GL() {
-    SDL_GL_DeleteContext(gl_context);
-}
-
-void EmuWindow_SDL2_GL::SwapBuffers() {
-    SDL_GL_SwapWindow(render_window);
+    core_context.reset();
+    SDL_GL_DeleteContext(window_context);
 }
 
 void EmuWindow_SDL2_GL::MakeCurrent() {
-    SDL_GL_MakeCurrent(render_window, gl_context);
+    core_context->MakeCurrent();
 }
 
 void EmuWindow_SDL2_GL::DoneCurrent() {
-    SDL_GL_MakeCurrent(render_window, nullptr);
+    core_context->DoneCurrent();
 }
 
 void 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
 std::unique_ptr<Core::Frontend::GraphicsContext> EmuWindow_SDL2_GL::CreateSharedContext() const {
     return std::make_unique<SDLGLContext>();
 }
+
+void EmuWindow_SDL2_GL::Present() {
+    SDL_GL_MakeCurrent(render_window, window_context);
+    SDL_GL_SetSwapInterval(Settings::values.use_vsync ? 1 : 0);
+    while (IsOpen()) {
+        system.Renderer().TryPresent(100);
+        SDL_GL_SwapWindow(render_window);
+    }
+    SDL_GL_MakeCurrent(render_window, nullptr);
+}
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 @@
 
 class EmuWindow_SDL2_GL final : public EmuWindow_SDL2 {
 public:
-    explicit EmuWindow_SDL2_GL(bool fullscreen);
+    explicit EmuWindow_SDL2_GL(Core::System& system, bool fullscreen);
     ~EmuWindow_SDL2_GL();
 
-    /// Swap buffers to display the next frame
-    void SwapBuffers() override;
-
-    /// Makes the graphics context current for the caller thread
     void MakeCurrent() override;
-
-    /// Releases the GL context from the caller thread
     void DoneCurrent() override;
+    void Present() override;
 
     /// Ignored in OpenGL
     void RetrieveVulkanHandlers(void* get_instance_proc_addr, void* instance,
@@ -29,10 +24,17 @@ public:
     std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override;
 
 private:
+    /// Fake hidden window for the core context
+    SDL_Window* dummy_window{};
+
     /// Whether the GPU and driver supports the OpenGL extension required
     bool SupportsRequiredGLExtensions();
 
     using SDL_GLContext = void*;
+
     /// The OpenGL context associated with the window
-    SDL_GLContext gl_context;
+    SDL_GLContext window_context;
+
+    /// The OpenGL context associated with the core
+    std::unique_ptr<Core::Frontend::GraphicsContext> core_context;
 };
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..f5f6675b5 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 @@
 #include "core/settings.h"
 #include "yuzu_cmd/emu_window/emu_window_sdl2_vk.h"
 
-EmuWindow_SDL2_VK::EmuWindow_SDL2_VK(bool fullscreen) : EmuWindow_SDL2(fullscreen) {
+EmuWindow_SDL2_VK::EmuWindow_SDL2_VK(Core::System& system, bool fullscreen)
+    : EmuWindow_SDL2{system, fullscreen} {
     if (SDL_Vulkan_LoadLibrary(nullptr) != 0) {
         LOG_CRITICAL(Frontend, "SDL failed to load the Vulkan library: {}", SDL_GetError());
         exit(EXIT_FAILURE);
@@ -110,8 +111,6 @@ EmuWindow_SDL2_VK::~EmuWindow_SDL2_VK() {
     vkDestroyInstance(vk_instance, nullptr);
 }
 
-void EmuWindow_SDL2_VK::SwapBuffers() {}
-
 void EmuWindow_SDL2_VK::MakeCurrent() {
     // Unused on Vulkan
 }
@@ -160,3 +159,5 @@ bool EmuWindow_SDL2_VK::UseStandardLayers(PFN_vkGetInstanceProcAddr vkGetInstanc
                return layer.layerName == std::string("VK_LAYER_LUNARG_standard_validation");
            }) != layers.end();
 }
+
+void EmuWindow_SDL2_VK::Present() {}
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 @@
 
 class EmuWindow_SDL2_VK final : public EmuWindow_SDL2 {
 public:
-    explicit EmuWindow_SDL2_VK(bool fullscreen);
+    explicit EmuWindow_SDL2_VK(Core::System& system, bool fullscreen);
     ~EmuWindow_SDL2_VK();
 
-    /// Swap buffers to display the next frame
-    void SwapBuffers() override;
-
-    /// Makes the graphics context current for the caller thread
     void MakeCurrent() override;
-
-    /// Releases the GL context from the caller thread
     void DoneCurrent() override;
-
-    /// Retrieves Vulkan specific handlers from the window
+    void Present() override;
     void RetrieveVulkanHandlers(void* get_instance_proc_addr, void* instance,
                                 void* surface) const override;
 
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) {
     Settings::values.use_gdbstub = use_gdbstub;
     Settings::Apply();
 
+    Core::System& system{Core::System::GetInstance()};
+
     std::unique_ptr<EmuWindow_SDL2> emu_window;
     switch (Settings::values.renderer_backend) {
     case Settings::RendererBackend::OpenGL:
-        emu_window = std::make_unique<EmuWindow_SDL2_GL>(fullscreen);
+        emu_window = std::make_unique<EmuWindow_SDL2_GL>(system, fullscreen);
         break;
     case Settings::RendererBackend::Vulkan:
 #ifdef HAS_VULKAN
-        emu_window = std::make_unique<EmuWindow_SDL2_VK>(fullscreen);
+        emu_window = std::make_unique<EmuWindow_SDL2_VK>(system, fullscreen);
         break;
 #else
         LOG_CRITICAL(Frontend, "Vulkan backend has not been compiled!");
@@ -192,12 +194,6 @@ int main(int argc, char** argv) {
 #endif
     }
 
-    if (!Settings::values.use_multi_core) {
-        // Single core mode must acquire OpenGL context for entire emulation session
-        emu_window->MakeCurrent();
-    }
-
-    Core::System& system{Core::System::GetInstance()};
     system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>());
     system.SetFilesystem(std::make_shared<FileSys::RealVfsFilesystem>());
     system.GetFileSystemController().CreateFactories(*system.GetFilesystem());
@@ -234,12 +230,23 @@ int main(int argc, char** argv) {
 
     system.TelemetrySession().AddField(Telemetry::FieldType::App, "Frontend", "SDL");
 
-    emu_window->MakeCurrent();
     system.Renderer().Rasterizer().LoadDiskResources();
 
+    // Acquire render context for duration of the thread if this is the rendering thread
+    if (!Settings::values.use_asynchronous_gpu_emulation) {
+        emu_window->MakeCurrent();
+    }
+    SCOPE_EXIT({
+        if (!Settings::values.use_asynchronous_gpu_emulation) {
+            emu_window->DoneCurrent();
+        }
+    });
+
+    std::thread render_thread([&emu_window] { emu_window->Present(); });
     while (emu_window->IsOpen()) {
         system.RunLoop();
     }
+    render_thread.join();
 
     system.Shutdown();
 
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() {
     SDL_Quit();
 }
 
-void EmuWindow_SDL2_Hide::SwapBuffers() {
-    SDL_GL_SwapWindow(render_window);
-}
-
 void EmuWindow_SDL2_Hide::PollEvents() {}
 
 void 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:
     explicit EmuWindow_SDL2_Hide();
     ~EmuWindow_SDL2_Hide();
 
-    /// Swap buffers to display the next frame
-    void SwapBuffers() override;
-
     /// Polls window events
     void PollEvents() override;