mirror of
				https://git.zaroz.cloud/nintendo-back-up/yuzu/yuzu-mainline.git
				synced 2025-03-21 01:53:15 +00:00 
			
		
		
		
	Merge pull request #3430 from bunnei/split-presenter
Port citra-emu/citra#4940: "Split Presentation thread from Render thread"
This commit is contained in:
		
						commit
						969357af1a
					
				| @ -131,8 +131,8 @@ add_library(core STATIC | ||||
|     frontend/framebuffer_layout.cpp | ||||
|     frontend/framebuffer_layout.h | ||||
|     frontend/input.h | ||||
|     frontend/scope_acquire_window_context.cpp | ||||
|     frontend/scope_acquire_window_context.h | ||||
|     frontend/scope_acquire_context.cpp | ||||
|     frontend/scope_acquire_context.h | ||||
|     gdbstub/gdbstub.cpp | ||||
|     gdbstub/gdbstub.h | ||||
|     hardware_interrupt_manager.cpp | ||||
|  | ||||
| @ -24,6 +24,7 @@ | ||||
| #include "core/file_sys/sdmc_factory.h" | ||||
| #include "core/file_sys/vfs_concat.h" | ||||
| #include "core/file_sys/vfs_real.h" | ||||
| #include "core/frontend/scope_acquire_context.h" | ||||
| #include "core/gdbstub/gdbstub.h" | ||||
| #include "core/hardware_interrupt_manager.h" | ||||
| #include "core/hle/kernel/client_port.h" | ||||
| @ -184,6 +185,8 @@ struct System::Impl { | ||||
| 
 | ||||
|     ResultStatus Load(System& system, Frontend::EmuWindow& emu_window, | ||||
|                       const std::string& filepath) { | ||||
|         Core::Frontend::ScopeAcquireContext acquire_context{emu_window}; | ||||
| 
 | ||||
|         app_loader = Loader::GetLoader(GetGameFileFromPath(virtual_filesystem, filepath)); | ||||
|         if (!app_loader) { | ||||
|             LOG_CRITICAL(Core, "Failed to obtain loader for {}!", filepath); | ||||
|  | ||||
| @ -26,9 +26,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; | ||||
| }; | ||||
| 
 | ||||
| /**
 | ||||
|  | ||||
| @ -29,6 +29,7 @@ enum class AspectRatio { | ||||
| struct FramebufferLayout { | ||||
|     u32 width{ScreenUndocked::Width}; | ||||
|     u32 height{ScreenUndocked::Height}; | ||||
|     bool is_srgb{}; | ||||
| 
 | ||||
|     Common::Rectangle<u32> screen; | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										18
									
								
								src/core/frontend/scope_acquire_context.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/core/frontend/scope_acquire_context.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,18 @@ | ||||
| // Copyright 2019 yuzu Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include "core/frontend/emu_window.h" | ||||
| #include "core/frontend/scope_acquire_context.h" | ||||
| 
 | ||||
| namespace Core::Frontend { | ||||
| 
 | ||||
| ScopeAcquireContext::ScopeAcquireContext(Core::Frontend::GraphicsContext& context) | ||||
|     : context{context} { | ||||
|     context.MakeCurrent(); | ||||
| } | ||||
| ScopeAcquireContext::~ScopeAcquireContext() { | ||||
|     context.DoneCurrent(); | ||||
| } | ||||
| 
 | ||||
| } // namespace Core::Frontend
 | ||||
| @ -8,16 +8,16 @@ | ||||
| 
 | ||||
| namespace Core::Frontend { | ||||
| 
 | ||||
| class EmuWindow; | ||||
| class GraphicsContext; | ||||
| 
 | ||||
| /// Helper class to acquire/release window context within a given scope
 | ||||
| class ScopeAcquireWindowContext : NonCopyable { | ||||
| class ScopeAcquireContext : NonCopyable { | ||||
| public: | ||||
|     explicit ScopeAcquireWindowContext(Core::Frontend::EmuWindow& window); | ||||
|     ~ScopeAcquireWindowContext(); | ||||
|     explicit ScopeAcquireContext(Core::Frontend::GraphicsContext& context); | ||||
|     ~ScopeAcquireContext(); | ||||
| 
 | ||||
| private: | ||||
|     Core::Frontend::EmuWindow& emu_window; | ||||
|     Core::Frontend::GraphicsContext& context; | ||||
| }; | ||||
| 
 | ||||
| } // namespace Core::Frontend
 | ||||
| @ -1,18 +0,0 @@ | ||||
| // Copyright 2019 yuzu Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include "core/frontend/emu_window.h" | ||||
| #include "core/frontend/scope_acquire_window_context.h" | ||||
| 
 | ||||
| namespace Core::Frontend { | ||||
| 
 | ||||
| ScopeAcquireWindowContext::ScopeAcquireWindowContext(Core::Frontend::EmuWindow& emu_window_) | ||||
|     : emu_window{emu_window_} { | ||||
|     emu_window.MakeCurrent(); | ||||
| } | ||||
| ScopeAcquireWindowContext::~ScopeAcquireWindowContext() { | ||||
|     emu_window.DoneCurrent(); | ||||
| } | ||||
| 
 | ||||
| } // namespace Core::Frontend
 | ||||
| @ -94,6 +94,7 @@ void LogSettings() { | ||||
|     LogSetting("Renderer_UseAccurateGpuEmulation", Settings::values.use_accurate_gpu_emulation); | ||||
|     LogSetting("Renderer_UseAsynchronousGpuEmulation", | ||||
|                Settings::values.use_asynchronous_gpu_emulation); | ||||
|     LogSetting("Renderer_UseVsync", Settings::values.use_vsync); | ||||
|     LogSetting("Audio_OutputEngine", Settings::values.sink_id); | ||||
|     LogSetting("Audio_EnableAudioStretching", Settings::values.enable_audio_stretching); | ||||
|     LogSetting("Audio_OutputDevice", Settings::values.audio_device_id); | ||||
|  | ||||
| @ -435,6 +435,7 @@ struct Values { | ||||
|     bool use_disk_shader_cache; | ||||
|     bool use_accurate_gpu_emulation; | ||||
|     bool use_asynchronous_gpu_emulation; | ||||
|     bool use_vsync; | ||||
|     bool force_30fps_mode; | ||||
| 
 | ||||
|     float bg_red; | ||||
|  | ||||
| @ -188,6 +188,7 @@ void TelemetrySession::AddInitialInfo(Loader::AppLoader& app_loader) { | ||||
|              Settings::values.use_accurate_gpu_emulation); | ||||
|     AddField(field_type, "Renderer_UseAsynchronousGpuEmulation", | ||||
|              Settings::values.use_asynchronous_gpu_emulation); | ||||
|     AddField(field_type, "Renderer_UseVsync", Settings::values.use_vsync); | ||||
|     AddField(field_type, "System_UseDockedMode", Settings::values.use_docked_mode); | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -5,7 +5,7 @@ | ||||
| #include "common/assert.h" | ||||
| #include "common/microprofile.h" | ||||
| #include "core/core.h" | ||||
| #include "core/frontend/scope_acquire_window_context.h" | ||||
| #include "core/frontend/scope_acquire_context.h" | ||||
| #include "video_core/dma_pusher.h" | ||||
| #include "video_core/gpu.h" | ||||
| #include "video_core/gpu_thread.h" | ||||
| @ -27,7 +27,7 @@ static void RunThread(VideoCore::RendererBase& renderer, Tegra::DmaPusher& dma_p | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     Core::Frontend::ScopeAcquireWindowContext acquire_context{renderer.GetRenderWindow()}; | ||||
|     Core::Frontend::ScopeAcquireContext acquire_context{renderer.GetRenderWindow()}; | ||||
| 
 | ||||
|     CommandDataContainer next; | ||||
|     while (state.is_running) { | ||||
|  | ||||
| @ -35,15 +35,19 @@ public: | ||||
|     explicit RendererBase(Core::Frontend::EmuWindow& window); | ||||
|     virtual ~RendererBase(); | ||||
| 
 | ||||
|     /// Swap buffers (render frame)
 | ||||
|     virtual void SwapBuffers(const Tegra::FramebufferConfig* framebuffer) = 0; | ||||
| 
 | ||||
|     /// Initialize the renderer
 | ||||
|     virtual bool Init() = 0; | ||||
| 
 | ||||
|     /// Shutdown the renderer
 | ||||
|     virtual void ShutDown() = 0; | ||||
| 
 | ||||
|     /// Finalize rendering the guest frame and draw into the presentation texture
 | ||||
|     virtual void SwapBuffers(const Tegra::FramebufferConfig* framebuffer) = 0; | ||||
| 
 | ||||
|     /// Draws the latest frame to the window waiting timeout_ms for a frame to arrive (Renderer
 | ||||
|     /// specific implementation)
 | ||||
|     virtual void TryPresent(int timeout_ms) = 0; | ||||
| 
 | ||||
|     // Getter/setter functions:
 | ||||
|     // ------------------------
 | ||||
| 
 | ||||
|  | ||||
| @ -15,6 +15,24 @@ MICROPROFILE_DEFINE(OpenGL_ResourceDeletion, "OpenGL", "Resource Deletion", MP_R | ||||
| 
 | ||||
| namespace OpenGL { | ||||
| 
 | ||||
| void OGLRenderbuffer::Create() { | ||||
|     if (handle != 0) | ||||
|         return; | ||||
| 
 | ||||
|     MICROPROFILE_SCOPE(OpenGL_ResourceCreation); | ||||
|     glGenRenderbuffers(1, &handle); | ||||
| } | ||||
| 
 | ||||
| void OGLRenderbuffer::Release() { | ||||
|     if (handle == 0) | ||||
|         return; | ||||
| 
 | ||||
|     MICROPROFILE_SCOPE(OpenGL_ResourceDeletion); | ||||
|     glDeleteRenderbuffers(1, &handle); | ||||
|     OpenGLState::GetCurState().ResetRenderbuffer(handle).Apply(); | ||||
|     handle = 0; | ||||
| } | ||||
| 
 | ||||
| void OGLTexture::Create(GLenum target) { | ||||
|     if (handle != 0) | ||||
|         return; | ||||
|  | ||||
| @ -11,6 +11,31 @@ | ||||
| 
 | ||||
| namespace OpenGL { | ||||
| 
 | ||||
| class OGLRenderbuffer : private NonCopyable { | ||||
| public: | ||||
|     OGLRenderbuffer() = default; | ||||
| 
 | ||||
|     OGLRenderbuffer(OGLRenderbuffer&& o) noexcept : handle(std::exchange(o.handle, 0)) {} | ||||
| 
 | ||||
|     ~OGLRenderbuffer() { | ||||
|         Release(); | ||||
|     } | ||||
| 
 | ||||
|     OGLRenderbuffer& operator=(OGLRenderbuffer&& o) noexcept { | ||||
|         Release(); | ||||
|         handle = std::exchange(o.handle, 0); | ||||
|         return *this; | ||||
|     } | ||||
| 
 | ||||
|     /// Creates a new internal OpenGL resource and stores the handle
 | ||||
|     void Create(); | ||||
| 
 | ||||
|     /// Deletes the internal OpenGL resource
 | ||||
|     void Release(); | ||||
| 
 | ||||
|     GLuint handle = 0; | ||||
| }; | ||||
| 
 | ||||
| class OGLTexture : private NonCopyable { | ||||
| public: | ||||
|     OGLTexture() = default; | ||||
|  | ||||
| @ -423,6 +423,13 @@ void OpenGLState::ApplyClipControl() { | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void OpenGLState::ApplyRenderBuffer() { | ||||
|     if (cur_state.renderbuffer != renderbuffer) { | ||||
|         cur_state.renderbuffer = renderbuffer; | ||||
|         glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void OpenGLState::ApplyTextures() { | ||||
|     const std::size_t size = std::size(textures); | ||||
|     for (std::size_t i = 0; i < size; ++i) { | ||||
| @ -478,6 +485,7 @@ void OpenGLState::Apply() { | ||||
|     ApplyPolygonOffset(); | ||||
|     ApplyAlphaTest(); | ||||
|     ApplyClipControl(); | ||||
|     ApplyRenderBuffer(); | ||||
| } | ||||
| 
 | ||||
| void OpenGLState::EmulateViewportWithScissor() { | ||||
| @ -551,4 +559,11 @@ OpenGLState& OpenGLState::ResetFramebuffer(GLuint handle) { | ||||
|     return *this; | ||||
| } | ||||
| 
 | ||||
| OpenGLState& OpenGLState::ResetRenderbuffer(GLuint handle) { | ||||
|     if (renderbuffer == handle) { | ||||
|         renderbuffer = 0; | ||||
|     } | ||||
|     return *this; | ||||
| } | ||||
| 
 | ||||
| } // namespace OpenGL
 | ||||
|  | ||||
| @ -158,6 +158,8 @@ public: | ||||
|         GLenum depth_mode = GL_NEGATIVE_ONE_TO_ONE; | ||||
|     } clip_control; | ||||
| 
 | ||||
|     GLuint renderbuffer{}; // GL_RENDERBUFFER_BINDING
 | ||||
| 
 | ||||
|     OpenGLState(); | ||||
| 
 | ||||
|     /// Get the currently active OpenGL state
 | ||||
| @ -196,6 +198,7 @@ public: | ||||
|     void ApplyPolygonOffset(); | ||||
|     void ApplyAlphaTest(); | ||||
|     void ApplyClipControl(); | ||||
|     void ApplyRenderBuffer(); | ||||
| 
 | ||||
|     /// Resets any references to the given resource
 | ||||
|     OpenGLState& UnbindTexture(GLuint handle); | ||||
| @ -204,6 +207,7 @@ public: | ||||
|     OpenGLState& ResetPipeline(GLuint handle); | ||||
|     OpenGLState& ResetVertexArray(GLuint handle); | ||||
|     OpenGLState& ResetFramebuffer(GLuint handle); | ||||
|     OpenGLState& ResetRenderbuffer(GLuint handle); | ||||
| 
 | ||||
|     /// Viewport does not affects glClearBuffer so emulate viewport using scissor test
 | ||||
|     void EmulateViewportWithScissor(); | ||||
|  | ||||
| @ -9,11 +9,11 @@ | ||||
| #include <glad/glad.h> | ||||
| #include "common/assert.h" | ||||
| #include "common/logging/log.h" | ||||
| #include "common/microprofile.h" | ||||
| #include "common/telemetry.h" | ||||
| #include "core/core.h" | ||||
| #include "core/core_timing.h" | ||||
| #include "core/frontend/emu_window.h" | ||||
| #include "core/frontend/scope_acquire_window_context.h" | ||||
| #include "core/memory.h" | ||||
| #include "core/perf_stats.h" | ||||
| #include "core/settings.h" | ||||
| @ -24,6 +24,144 @@ | ||||
| 
 | ||||
| namespace OpenGL { | ||||
| 
 | ||||
| // If the size of this is too small, it ends up creating a soft cap on FPS as the renderer will have
 | ||||
| // to wait on available presentation frames.
 | ||||
| constexpr std::size_t SWAP_CHAIN_SIZE = 3; | ||||
| 
 | ||||
| struct Frame { | ||||
|     u32 width{};                      /// Width of the frame (to detect resize)
 | ||||
|     u32 height{};                     /// Height of the frame
 | ||||
|     bool color_reloaded{};            /// Texture attachment was recreated (ie: resized)
 | ||||
|     OpenGL::OGLRenderbuffer color{};  /// Buffer shared between the render/present FBO
 | ||||
|     OpenGL::OGLFramebuffer render{};  /// FBO created on the render thread
 | ||||
|     OpenGL::OGLFramebuffer present{}; /// FBO created on the present thread
 | ||||
|     GLsync render_fence{};            /// Fence created on the render thread
 | ||||
|     GLsync present_fence{};           /// Fence created on the presentation thread
 | ||||
|     bool is_srgb{};                   /// Framebuffer is sRGB or RGB
 | ||||
| }; | ||||
| 
 | ||||
| /**
 | ||||
|  * For smooth Vsync rendering, we want to always present the latest frame that the core generates, | ||||
|  * but also make sure that rendering happens at the pace that the frontend dictates. This is a | ||||
|  * helper class that the renderer uses to sync frames between the render thread and the presentation | ||||
|  * thread | ||||
|  */ | ||||
| class FrameMailbox { | ||||
| public: | ||||
|     std::mutex swap_chain_lock; | ||||
|     std::condition_variable present_cv; | ||||
|     std::array<Frame, SWAP_CHAIN_SIZE> swap_chain{}; | ||||
|     std::queue<Frame*> free_queue; | ||||
|     std::deque<Frame*> present_queue; | ||||
|     Frame* previous_frame{}; | ||||
| 
 | ||||
|     FrameMailbox() { | ||||
|         for (auto& frame : swap_chain) { | ||||
|             free_queue.push(&frame); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     ~FrameMailbox() { | ||||
|         // lock the mutex and clear out the present and free_queues and notify any people who are
 | ||||
|         // blocked to prevent deadlock on shutdown
 | ||||
|         std::scoped_lock lock{swap_chain_lock}; | ||||
|         std::queue<Frame*>().swap(free_queue); | ||||
|         present_queue.clear(); | ||||
|         present_cv.notify_all(); | ||||
|     } | ||||
| 
 | ||||
|     void ReloadPresentFrame(Frame* frame, u32 height, u32 width) { | ||||
|         frame->present.Release(); | ||||
|         frame->present.Create(); | ||||
|         GLint previous_draw_fbo{}; | ||||
|         glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &previous_draw_fbo); | ||||
|         glBindFramebuffer(GL_FRAMEBUFFER, frame->present.handle); | ||||
|         glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, | ||||
|                                   frame->color.handle); | ||||
|         if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { | ||||
|             LOG_CRITICAL(Render_OpenGL, "Failed to recreate present FBO!"); | ||||
|         } | ||||
|         glBindFramebuffer(GL_DRAW_FRAMEBUFFER, previous_draw_fbo); | ||||
|         frame->color_reloaded = false; | ||||
|     } | ||||
| 
 | ||||
|     void ReloadRenderFrame(Frame* frame, u32 width, u32 height) { | ||||
|         OpenGLState prev_state = OpenGLState::GetCurState(); | ||||
|         OpenGLState state = OpenGLState::GetCurState(); | ||||
| 
 | ||||
|         // Recreate the color texture attachment
 | ||||
|         frame->color.Release(); | ||||
|         frame->color.Create(); | ||||
|         state.renderbuffer = frame->color.handle; | ||||
|         state.Apply(); | ||||
|         glRenderbufferStorage(GL_RENDERBUFFER, frame->is_srgb ? GL_SRGB8 : GL_RGB8, width, height); | ||||
| 
 | ||||
|         // Recreate the FBO for the render target
 | ||||
|         frame->render.Release(); | ||||
|         frame->render.Create(); | ||||
|         state.draw.read_framebuffer = frame->render.handle; | ||||
|         state.draw.draw_framebuffer = frame->render.handle; | ||||
|         state.Apply(); | ||||
|         glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, | ||||
|                                   frame->color.handle); | ||||
|         if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { | ||||
|             LOG_CRITICAL(Render_OpenGL, "Failed to recreate render FBO!"); | ||||
|         } | ||||
|         prev_state.Apply(); | ||||
|         frame->width = width; | ||||
|         frame->height = height; | ||||
|         frame->color_reloaded = true; | ||||
|     } | ||||
| 
 | ||||
|     Frame* GetRenderFrame() { | ||||
|         std::unique_lock lock{swap_chain_lock}; | ||||
| 
 | ||||
|         // If theres no free frames, we will reuse the oldest render frame
 | ||||
|         if (free_queue.empty()) { | ||||
|             auto frame = present_queue.back(); | ||||
|             present_queue.pop_back(); | ||||
|             return frame; | ||||
|         } | ||||
| 
 | ||||
|         Frame* frame = free_queue.front(); | ||||
|         free_queue.pop(); | ||||
|         return frame; | ||||
|     } | ||||
| 
 | ||||
|     void ReleaseRenderFrame(Frame* frame) { | ||||
|         std::unique_lock lock{swap_chain_lock}; | ||||
|         present_queue.push_front(frame); | ||||
|         present_cv.notify_one(); | ||||
|     } | ||||
| 
 | ||||
|     Frame* TryGetPresentFrame(int timeout_ms) { | ||||
|         std::unique_lock lock{swap_chain_lock}; | ||||
|         // wait for new entries in the present_queue
 | ||||
|         present_cv.wait_for(lock, std::chrono::milliseconds(timeout_ms), | ||||
|                             [&] { return !present_queue.empty(); }); | ||||
|         if (present_queue.empty()) { | ||||
|             // timed out waiting for a frame to draw so return the previous frame
 | ||||
|             return previous_frame; | ||||
|         } | ||||
| 
 | ||||
|         // free the previous frame and add it back to the free queue
 | ||||
|         if (previous_frame) { | ||||
|             free_queue.push(previous_frame); | ||||
|         } | ||||
| 
 | ||||
|         // the newest entries are pushed to the front of the queue
 | ||||
|         Frame* frame = present_queue.front(); | ||||
|         present_queue.pop_front(); | ||||
|         // remove all old entries from the present queue and move them back to the free_queue
 | ||||
|         for (auto f : present_queue) { | ||||
|             free_queue.push(f); | ||||
|         } | ||||
|         present_queue.clear(); | ||||
|         previous_frame = frame; | ||||
|         return frame; | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| namespace { | ||||
| 
 | ||||
| constexpr char vertex_shader[] = R"( | ||||
| @ -158,21 +296,91 @@ void APIENTRY DebugHandler(GLenum source, GLenum type, GLuint id, GLenum severit | ||||
| } // Anonymous namespace
 | ||||
| 
 | ||||
| RendererOpenGL::RendererOpenGL(Core::Frontend::EmuWindow& emu_window, Core::System& system) | ||||
|     : VideoCore::RendererBase{emu_window}, emu_window{emu_window}, system{system} {} | ||||
|     : VideoCore::RendererBase{emu_window}, emu_window{emu_window}, system{system}, | ||||
|       frame_mailbox{std::make_unique<FrameMailbox>()} {} | ||||
| 
 | ||||
| RendererOpenGL::~RendererOpenGL() = default; | ||||
| 
 | ||||
| MICROPROFILE_DEFINE(OpenGL_RenderFrame, "OpenGL", "Render Frame", MP_RGB(128, 128, 64)); | ||||
| MICROPROFILE_DEFINE(OpenGL_WaitPresent, "OpenGL", "Wait For Present", MP_RGB(128, 128, 128)); | ||||
| 
 | ||||
| void RendererOpenGL::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) { | ||||
|     render_window.PollEvents(); | ||||
| 
 | ||||
|     if (!framebuffer) { | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     // Maintain the rasterizer's state as a priority
 | ||||
|     OpenGLState prev_state = OpenGLState::GetCurState(); | ||||
|     state.AllDirty(); | ||||
|     state.Apply(); | ||||
| 
 | ||||
|     PrepareRendertarget(framebuffer); | ||||
|     RenderScreenshot(); | ||||
| 
 | ||||
|     Frame* frame; | ||||
|     { | ||||
|         MICROPROFILE_SCOPE(OpenGL_WaitPresent); | ||||
| 
 | ||||
|         frame = frame_mailbox->GetRenderFrame(); | ||||
| 
 | ||||
|         // Clean up sync objects before drawing
 | ||||
| 
 | ||||
|         // INTEL driver workaround. We can't delete the previous render sync object until we are
 | ||||
|         // sure that the presentation is done
 | ||||
|         if (frame->present_fence) { | ||||
|             glClientWaitSync(frame->present_fence, 0, GL_TIMEOUT_IGNORED); | ||||
|         } | ||||
| 
 | ||||
|         // delete the draw fence if the frame wasn't presented
 | ||||
|         if (frame->render_fence) { | ||||
|             glDeleteSync(frame->render_fence); | ||||
|             frame->render_fence = 0; | ||||
|         } | ||||
| 
 | ||||
|         // wait for the presentation to be done
 | ||||
|         if (frame->present_fence) { | ||||
|             glWaitSync(frame->present_fence, 0, GL_TIMEOUT_IGNORED); | ||||
|             glDeleteSync(frame->present_fence); | ||||
|             frame->present_fence = 0; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     { | ||||
|         MICROPROFILE_SCOPE(OpenGL_RenderFrame); | ||||
|         const auto& layout = render_window.GetFramebufferLayout(); | ||||
| 
 | ||||
|         // Recreate the frame if the size of the window has changed
 | ||||
|         if (layout.width != frame->width || layout.height != frame->height || | ||||
|             is_srgb != frame->is_srgb) { | ||||
|             LOG_DEBUG(Render_OpenGL, "Reloading render frame"); | ||||
|             is_srgb = frame->is_srgb = screen_info.display_srgb; | ||||
|             frame_mailbox->ReloadRenderFrame(frame, layout.width, layout.height); | ||||
|         } | ||||
|         state.draw.draw_framebuffer = frame->render.handle; | ||||
|         state.Apply(); | ||||
|         DrawScreen(layout); | ||||
|         // Create a fence for the frontend to wait on and swap this frame to OffTex
 | ||||
|         frame->render_fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); | ||||
|         glFlush(); | ||||
|         frame_mailbox->ReleaseRenderFrame(frame); | ||||
|         m_current_frame++; | ||||
|         rasterizer->TickFrame(); | ||||
|     } | ||||
| 
 | ||||
|     // Restore the rasterizer state
 | ||||
|     prev_state.AllDirty(); | ||||
|     prev_state.Apply(); | ||||
| } | ||||
| 
 | ||||
| void RendererOpenGL::PrepareRendertarget(const Tegra::FramebufferConfig* framebuffer) { | ||||
|     if (framebuffer) { | ||||
|         // If framebuffer is provided, reload it from memory to a texture
 | ||||
|         if (screen_info.texture.width != static_cast<GLsizei>(framebuffer->width) || | ||||
|             screen_info.texture.height != static_cast<GLsizei>(framebuffer->height) || | ||||
|             screen_info.texture.pixel_format != framebuffer->pixel_format) { | ||||
|             screen_info.texture.pixel_format != framebuffer->pixel_format || | ||||
|             gl_framebuffer_data.empty()) { | ||||
|             // Reallocate texture if the framebuffer size has changed.
 | ||||
|             // This is expected to not happen very often and hence should not be a
 | ||||
|             // performance problem.
 | ||||
| @ -181,22 +389,7 @@ void RendererOpenGL::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) { | ||||
| 
 | ||||
|         // Load the framebuffer from memory, draw it to the screen, and swap buffers
 | ||||
|         LoadFBToScreenInfo(*framebuffer); | ||||
| 
 | ||||
|         if (renderer_settings.screenshot_requested) | ||||
|             CaptureScreenshot(); | ||||
| 
 | ||||
|         DrawScreen(render_window.GetFramebufferLayout()); | ||||
| 
 | ||||
|         rasterizer->TickFrame(); | ||||
| 
 | ||||
|         render_window.SwapBuffers(); | ||||
|     } | ||||
| 
 | ||||
|     render_window.PollEvents(); | ||||
| 
 | ||||
|     // Restore the rasterizer state
 | ||||
|     prev_state.AllDirty(); | ||||
|     prev_state.Apply(); | ||||
| } | ||||
| 
 | ||||
| void RendererOpenGL::LoadFBToScreenInfo(const Tegra::FramebufferConfig& framebuffer) { | ||||
| @ -418,13 +611,48 @@ void RendererOpenGL::DrawScreen(const Layout::FramebufferLayout& layout) { | ||||
|     DrawScreenTriangles(screen_info, static_cast<float>(screen.left), | ||||
|                         static_cast<float>(screen.top), static_cast<float>(screen.GetWidth()), | ||||
|                         static_cast<float>(screen.GetHeight())); | ||||
| 
 | ||||
|     m_current_frame++; | ||||
| } | ||||
| 
 | ||||
| void RendererOpenGL::UpdateFramerate() {} | ||||
| void RendererOpenGL::TryPresent(int timeout_ms) { | ||||
|     const auto& layout = render_window.GetFramebufferLayout(); | ||||
|     auto frame = frame_mailbox->TryGetPresentFrame(timeout_ms); | ||||
|     if (!frame) { | ||||
|         LOG_DEBUG(Render_OpenGL, "TryGetPresentFrame returned no frame to present"); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     // Clearing before a full overwrite of a fbo can signal to drivers that they can avoid a
 | ||||
|     // readback since we won't be doing any blending
 | ||||
|     glClear(GL_COLOR_BUFFER_BIT); | ||||
| 
 | ||||
|     // Recreate the presentation FBO if the color attachment was changed
 | ||||
|     if (frame->color_reloaded) { | ||||
|         LOG_DEBUG(Render_OpenGL, "Reloading present frame"); | ||||
|         frame_mailbox->ReloadPresentFrame(frame, layout.width, layout.height); | ||||
|     } | ||||
|     glWaitSync(frame->render_fence, 0, GL_TIMEOUT_IGNORED); | ||||
|     // INTEL workaround.
 | ||||
|     // Normally we could just delete the draw fence here, but due to driver bugs, we can just delete
 | ||||
|     // it on the emulation thread without too much penalty
 | ||||
|     // glDeleteSync(frame.render_sync);
 | ||||
|     // frame.render_sync = 0;
 | ||||
| 
 | ||||
|     glBindFramebuffer(GL_READ_FRAMEBUFFER, frame->present.handle); | ||||
|     glBlitFramebuffer(0, 0, frame->width, frame->height, 0, 0, layout.width, layout.height, | ||||
|                       GL_COLOR_BUFFER_BIT, GL_LINEAR); | ||||
| 
 | ||||
|     // Insert fence for the main thread to block on
 | ||||
|     frame->present_fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); | ||||
|     glFlush(); | ||||
| 
 | ||||
|     glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); | ||||
| } | ||||
| 
 | ||||
| void RendererOpenGL::RenderScreenshot() { | ||||
|     if (!renderer_settings.screenshot_requested) { | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
| void RendererOpenGL::CaptureScreenshot() { | ||||
|     // Draw the current frame to the screenshot framebuffer
 | ||||
|     screenshot_framebuffer.Create(); | ||||
|     GLuint old_read_fb = state.draw.read_framebuffer; | ||||
| @ -459,8 +687,6 @@ void RendererOpenGL::CaptureScreenshot() { | ||||
| } | ||||
| 
 | ||||
| bool RendererOpenGL::Init() { | ||||
|     Core::Frontend::ScopeAcquireWindowContext acquire_context{render_window}; | ||||
| 
 | ||||
|     if (GLAD_GL_KHR_debug) { | ||||
|         glEnable(GL_DEBUG_OUTPUT); | ||||
|         glDebugMessageCallback(DebugHandler, nullptr); | ||||
|  | ||||
| @ -44,19 +44,23 @@ struct ScreenInfo { | ||||
|     TextureInfo texture; | ||||
| }; | ||||
| 
 | ||||
| struct PresentationTexture { | ||||
|     u32 width = 0; | ||||
|     u32 height = 0; | ||||
|     OGLTexture texture; | ||||
| }; | ||||
| 
 | ||||
| class FrameMailbox; | ||||
| 
 | ||||
| class RendererOpenGL final : public VideoCore::RendererBase { | ||||
| public: | ||||
|     explicit RendererOpenGL(Core::Frontend::EmuWindow& emu_window, Core::System& system); | ||||
|     ~RendererOpenGL() override; | ||||
| 
 | ||||
|     /// Swap buffers (render frame)
 | ||||
|     void SwapBuffers(const Tegra::FramebufferConfig* framebuffer) override; | ||||
| 
 | ||||
|     /// Initialize the renderer
 | ||||
|     bool Init() override; | ||||
| 
 | ||||
|     /// Shutdown the renderer
 | ||||
|     void ShutDown() override; | ||||
|     void SwapBuffers(const Tegra::FramebufferConfig* framebuffer) override; | ||||
|     void TryPresent(int timeout_ms) override; | ||||
| 
 | ||||
| private: | ||||
|     /// Initializes the OpenGL state and creates persistent objects.
 | ||||
| @ -74,10 +78,7 @@ private: | ||||
| 
 | ||||
|     void DrawScreenTriangles(const ScreenInfo& screen_info, float x, float y, float w, float h); | ||||
| 
 | ||||
|     /// Updates the framerate.
 | ||||
|     void UpdateFramerate(); | ||||
| 
 | ||||
|     void CaptureScreenshot(); | ||||
|     void RenderScreenshot(); | ||||
| 
 | ||||
|     /// Loads framebuffer from emulated memory into the active OpenGL texture.
 | ||||
|     void LoadFBToScreenInfo(const Tegra::FramebufferConfig& framebuffer); | ||||
| @ -87,6 +88,8 @@ private: | ||||
|     void LoadColorToActiveGLTexture(u8 color_r, u8 color_g, u8 color_b, u8 color_a, | ||||
|                                     const TextureInfo& texture); | ||||
| 
 | ||||
|     void PrepareRendertarget(const Tegra::FramebufferConfig* framebuffer); | ||||
| 
 | ||||
|     Core::Frontend::EmuWindow& emu_window; | ||||
|     Core::System& system; | ||||
| 
 | ||||
| @ -107,6 +110,12 @@ private: | ||||
|     /// Used for transforming the framebuffer orientation
 | ||||
|     Tegra::FramebufferConfig::TransformFlags framebuffer_transform_flags; | ||||
|     Common::Rectangle<int> framebuffer_crop_rect; | ||||
| 
 | ||||
|     /// Represents if the final render frame is sRGB
 | ||||
|     bool is_srgb{}; | ||||
| 
 | ||||
|     /// Frame presentation mailbox
 | ||||
|     std::unique_ptr<FrameMailbox> frame_mailbox; | ||||
| }; | ||||
| 
 | ||||
| } // namespace OpenGL
 | ||||
|  | ||||
| @ -106,8 +106,14 @@ RendererVulkan::~RendererVulkan() { | ||||
| } | ||||
| 
 | ||||
| void RendererVulkan::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) { | ||||
|     render_window.PollEvents(); | ||||
| 
 | ||||
|     if (!framebuffer) { | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     const auto& layout = render_window.GetFramebufferLayout(); | ||||
|     if (framebuffer && layout.width > 0 && layout.height > 0 && render_window.IsShown()) { | ||||
|     if (layout.width > 0 && layout.height > 0 && render_window.IsShown()) { | ||||
|         const VAddr framebuffer_addr = framebuffer->address + framebuffer->offset; | ||||
|         const bool use_accelerated = | ||||
|             rasterizer->AccelerateDisplay(*framebuffer, framebuffer_addr, framebuffer->stride); | ||||
| @ -128,13 +134,16 @@ void RendererVulkan::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) { | ||||
|             blit_screen->Recreate(); | ||||
|         } | ||||
| 
 | ||||
|         render_window.SwapBuffers(); | ||||
|         rasterizer->TickFrame(); | ||||
|     } | ||||
| 
 | ||||
|     render_window.PollEvents(); | ||||
| } | ||||
| 
 | ||||
| void RendererVulkan::TryPresent(int /*timeout_ms*/) { | ||||
|     // TODO (bunnei): ImplementMe
 | ||||
| } | ||||
| 
 | ||||
| bool RendererVulkan::Init() { | ||||
|     PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr{}; | ||||
|     render_window.RetrieveVulkanHandlers(&vkGetInstanceProcAddr, &instance, &surface); | ||||
| @ -262,4 +271,4 @@ void RendererVulkan::Report() const { | ||||
|     telemetry_session.AddField(field, "GPU_Vulkan_Extensions", extensions); | ||||
| } | ||||
| 
 | ||||
| } // namespace Vulkan
 | ||||
| } // namespace Vulkan
 | ||||
|  | ||||
| @ -36,14 +36,10 @@ public: | ||||
|     explicit RendererVulkan(Core::Frontend::EmuWindow& window, Core::System& system); | ||||
|     ~RendererVulkan() override; | ||||
| 
 | ||||
|     /// Swap buffers (render frame)
 | ||||
|     void SwapBuffers(const Tegra::FramebufferConfig* framebuffer) override; | ||||
| 
 | ||||
|     /// Initialize the renderer
 | ||||
|     bool Init() override; | ||||
| 
 | ||||
|     /// Shutdown the renderer
 | ||||
|     void ShutDown() override; | ||||
|     void SwapBuffers(const Tegra::FramebufferConfig* framebuffer) override; | ||||
|     void TryPresent(int timeout_ms) override; | ||||
| 
 | ||||
| private: | ||||
|     std::optional<vk::DebugUtilsMessengerEXT> CreateDebugCallback( | ||||
|  | ||||
| @ -9,6 +9,9 @@ | ||||
| #include <QKeyEvent> | ||||
| #include <QMessageBox> | ||||
| #include <QOffscreenSurface> | ||||
| #include <QOpenGLContext> | ||||
| #include <QOpenGLFunctions> | ||||
| #include <QOpenGLFunctions_4_3_Core> | ||||
| #include <QOpenGLWindow> | ||||
| #include <QPainter> | ||||
| #include <QScreen> | ||||
| @ -23,9 +26,10 @@ | ||||
| #include "common/assert.h" | ||||
| #include "common/microprofile.h" | ||||
| #include "common/scm_rev.h" | ||||
| #include "common/scope_exit.h" | ||||
| #include "core/core.h" | ||||
| #include "core/frontend/framebuffer_layout.h" | ||||
| #include "core/frontend/scope_acquire_window_context.h" | ||||
| #include "core/frontend/scope_acquire_context.h" | ||||
| #include "core/settings.h" | ||||
| #include "input_common/keyboard.h" | ||||
| #include "input_common/main.h" | ||||
| @ -35,15 +39,27 @@ | ||||
| #include "yuzu/bootmanager.h" | ||||
| #include "yuzu/main.h" | ||||
| 
 | ||||
| EmuThread::EmuThread(GRenderWindow* render_window) : render_window(render_window) {} | ||||
| EmuThread::EmuThread(GRenderWindow& window) | ||||
|     : shared_context{window.CreateSharedContext()}, | ||||
|       context{(Settings::values.use_asynchronous_gpu_emulation && shared_context) ? *shared_context | ||||
|                                                                                   : window} {} | ||||
| 
 | ||||
| EmuThread::~EmuThread() = default; | ||||
| 
 | ||||
| void EmuThread::run() { | ||||
|     render_window->MakeCurrent(); | ||||
| static GMainWindow* GetMainWindow() { | ||||
|     for (QWidget* w : qApp->topLevelWidgets()) { | ||||
|         if (GMainWindow* main = qobject_cast<GMainWindow*>(w)) { | ||||
|             return main; | ||||
|         } | ||||
|     } | ||||
|     return nullptr; | ||||
| } | ||||
| 
 | ||||
| void EmuThread::run() { | ||||
|     MicroProfileOnThreadCreate("EmuThread"); | ||||
| 
 | ||||
|     Core::Frontend::ScopeAcquireContext acquire_context{context}; | ||||
| 
 | ||||
|     emit LoadProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0); | ||||
| 
 | ||||
|     Core::System::GetInstance().Renderer().Rasterizer().LoadDiskResources( | ||||
| @ -53,11 +69,6 @@ void EmuThread::run() { | ||||
| 
 | ||||
|     emit LoadProgress(VideoCore::LoadCallbackStage::Complete, 0, 0); | ||||
| 
 | ||||
|     if (Settings::values.use_asynchronous_gpu_emulation) { | ||||
|         // Release OpenGL context for the GPU thread
 | ||||
|         render_window->DoneCurrent(); | ||||
|     } | ||||
| 
 | ||||
|     // Holds whether the cpu was running during the last iteration,
 | ||||
|     // so that the DebugModeLeft signal can be emitted before the
 | ||||
|     // next execution step
 | ||||
| @ -98,190 +109,202 @@ void EmuThread::run() { | ||||
| #if MICROPROFILE_ENABLED | ||||
|     MicroProfileOnThreadExit(); | ||||
| #endif | ||||
| 
 | ||||
|     render_window->moveContext(); | ||||
| } | ||||
| 
 | ||||
| class GGLContext : public Core::Frontend::GraphicsContext { | ||||
| public: | ||||
|     explicit GGLContext(QOpenGLContext* shared_context) : shared_context{shared_context} { | ||||
|         context.setFormat(shared_context->format()); | ||||
|         context.setShareContext(shared_context); | ||||
|         context.create(); | ||||
|     explicit GGLContext(QOpenGLContext* shared_context) | ||||
|         : context(new QOpenGLContext(shared_context->parent())), | ||||
|           surface(new QOffscreenSurface(nullptr)) { | ||||
| 
 | ||||
|         // disable vsync for any shared contexts
 | ||||
|         auto format = shared_context->format(); | ||||
|         format.setSwapInterval(0); | ||||
| 
 | ||||
|         context->setShareContext(shared_context); | ||||
|         context->setFormat(format); | ||||
|         context->create(); | ||||
|         surface->setParent(shared_context->parent()); | ||||
|         surface->setFormat(format); | ||||
|         surface->create(); | ||||
|     } | ||||
| 
 | ||||
|     void MakeCurrent() override { | ||||
|         context.makeCurrent(shared_context->surface()); | ||||
|         context->makeCurrent(surface); | ||||
|     } | ||||
| 
 | ||||
|     void DoneCurrent() override { | ||||
|         context.doneCurrent(); | ||||
|         context->doneCurrent(); | ||||
|     } | ||||
| 
 | ||||
|     void SwapBuffers() override {} | ||||
| 
 | ||||
| private: | ||||
|     QOpenGLContext* shared_context; | ||||
|     QOpenGLContext context; | ||||
|     QOpenGLContext* context; | ||||
|     QOffscreenSurface* surface; | ||||
| }; | ||||
| 
 | ||||
| class GWidgetInternal : public QWindow { | ||||
| class ChildRenderWindow : public QWindow { | ||||
| public: | ||||
|     GWidgetInternal(GRenderWindow* parent) : parent(parent) {} | ||||
|     virtual ~GWidgetInternal() = default; | ||||
|     ChildRenderWindow(QWindow* parent, QWidget* event_handler) | ||||
|         : QWindow{parent}, event_handler{event_handler} {} | ||||
| 
 | ||||
|     void resizeEvent(QResizeEvent* ev) override { | ||||
|         parent->OnClientAreaResized(ev->size().width(), ev->size().height()); | ||||
|         parent->OnFramebufferSizeChanged(); | ||||
|     } | ||||
|     virtual ~ChildRenderWindow() = default; | ||||
| 
 | ||||
|     void keyPressEvent(QKeyEvent* event) override { | ||||
|         InputCommon::GetKeyboard()->PressKey(event->key()); | ||||
|     } | ||||
| 
 | ||||
|     void keyReleaseEvent(QKeyEvent* event) override { | ||||
|         InputCommon::GetKeyboard()->ReleaseKey(event->key()); | ||||
|     } | ||||
| 
 | ||||
|     void mousePressEvent(QMouseEvent* event) override { | ||||
|         if (event->source() == Qt::MouseEventSynthesizedBySystem) | ||||
|             return; // touch input is handled in TouchBeginEvent
 | ||||
| 
 | ||||
|         const auto pos{event->pos()}; | ||||
|         if (event->button() == Qt::LeftButton) { | ||||
|             const auto [x, y] = parent->ScaleTouch(pos); | ||||
|             parent->TouchPressed(x, y); | ||||
|         } else if (event->button() == Qt::RightButton) { | ||||
|             InputCommon::GetMotionEmu()->BeginTilt(pos.x(), pos.y()); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     void mouseMoveEvent(QMouseEvent* event) override { | ||||
|         if (event->source() == Qt::MouseEventSynthesizedBySystem) | ||||
|             return; // touch input is handled in TouchUpdateEvent
 | ||||
| 
 | ||||
|         const auto pos{event->pos()}; | ||||
|         const auto [x, y] = parent->ScaleTouch(pos); | ||||
|         parent->TouchMoved(x, y); | ||||
|         InputCommon::GetMotionEmu()->Tilt(pos.x(), pos.y()); | ||||
|     } | ||||
| 
 | ||||
|     void mouseReleaseEvent(QMouseEvent* event) override { | ||||
|         if (event->source() == Qt::MouseEventSynthesizedBySystem) | ||||
|             return; // touch input is handled in TouchEndEvent
 | ||||
| 
 | ||||
|         if (event->button() == Qt::LeftButton) | ||||
|             parent->TouchReleased(); | ||||
|         else if (event->button() == Qt::RightButton) | ||||
|             InputCommon::GetMotionEmu()->EndTilt(); | ||||
|     } | ||||
| 
 | ||||
|     void DisablePainting() { | ||||
|         do_painting = false; | ||||
|     } | ||||
| 
 | ||||
|     void EnablePainting() { | ||||
|         do_painting = true; | ||||
|     } | ||||
| 
 | ||||
|     std::pair<unsigned, unsigned> GetSize() const { | ||||
|         return std::make_pair(width(), height()); | ||||
|     } | ||||
|     virtual void Present() = 0; | ||||
| 
 | ||||
| protected: | ||||
|     bool IsPaintingEnabled() const { | ||||
|         return do_painting; | ||||
|     bool event(QEvent* event) override { | ||||
|         switch (event->type()) { | ||||
|         case QEvent::UpdateRequest: | ||||
|             Present(); | ||||
|             return true; | ||||
|         case QEvent::MouseButtonPress: | ||||
|         case QEvent::MouseButtonRelease: | ||||
|         case QEvent::MouseButtonDblClick: | ||||
|         case QEvent::MouseMove: | ||||
|         case QEvent::KeyPress: | ||||
|         case QEvent::KeyRelease: | ||||
|         case QEvent::FocusIn: | ||||
|         case QEvent::FocusOut: | ||||
|         case QEvent::FocusAboutToChange: | ||||
|         case QEvent::Enter: | ||||
|         case QEvent::Leave: | ||||
|         case QEvent::Wheel: | ||||
|         case QEvent::TabletMove: | ||||
|         case QEvent::TabletPress: | ||||
|         case QEvent::TabletRelease: | ||||
|         case QEvent::TabletEnterProximity: | ||||
|         case QEvent::TabletLeaveProximity: | ||||
|         case QEvent::TouchBegin: | ||||
|         case QEvent::TouchUpdate: | ||||
|         case QEvent::TouchEnd: | ||||
|         case QEvent::InputMethodQuery: | ||||
|         case QEvent::TouchCancel: | ||||
|             return QCoreApplication::sendEvent(event_handler, event); | ||||
|         case QEvent::Drop: | ||||
|             GetMainWindow()->DropAction(static_cast<QDropEvent*>(event)); | ||||
|             return true; | ||||
|         case QEvent::DragResponse: | ||||
|         case QEvent::DragEnter: | ||||
|         case QEvent::DragLeave: | ||||
|         case QEvent::DragMove: | ||||
|             GetMainWindow()->AcceptDropEvent(static_cast<QDropEvent*>(event)); | ||||
|             return true; | ||||
|         default: | ||||
|             return QWindow::event(event); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     void exposeEvent(QExposeEvent* event) override { | ||||
|         QWindow::requestUpdate(); | ||||
|         QWindow::exposeEvent(event); | ||||
|     } | ||||
| 
 | ||||
| private: | ||||
|     GRenderWindow* parent; | ||||
|     bool do_painting = false; | ||||
|     QWidget* event_handler{}; | ||||
| }; | ||||
| 
 | ||||
| // This class overrides paintEvent and resizeEvent to prevent the GUI thread from stealing GL
 | ||||
| // context.
 | ||||
| // The corresponding functionality is handled in EmuThread instead
 | ||||
| class GGLWidgetInternal final : public GWidgetInternal, public QOpenGLWindow { | ||||
| class OpenGLWindow final : public ChildRenderWindow { | ||||
| public: | ||||
|     GGLWidgetInternal(GRenderWindow* parent, QOpenGLContext* shared_context) | ||||
|         : GWidgetInternal(parent), QOpenGLWindow(shared_context) {} | ||||
|     ~GGLWidgetInternal() override = default; | ||||
|     OpenGLWindow(QWindow* parent, QWidget* event_handler, QOpenGLContext* shared_context) | ||||
|         : ChildRenderWindow{parent, event_handler}, | ||||
|           context(new QOpenGLContext(shared_context->parent())) { | ||||
| 
 | ||||
|     void paintEvent(QPaintEvent* ev) override { | ||||
|         if (IsPaintingEnabled()) { | ||||
|             QPainter painter(this); | ||||
|         } | ||||
|         // disable vsync for any shared contexts
 | ||||
|         auto format = shared_context->format(); | ||||
|         format.setSwapInterval(Settings::values.use_vsync ? 1 : 0); | ||||
|         this->setFormat(format); | ||||
| 
 | ||||
|         context->setShareContext(shared_context); | ||||
|         context->setScreen(this->screen()); | ||||
|         context->setFormat(format); | ||||
|         context->create(); | ||||
| 
 | ||||
|         setSurfaceType(QWindow::OpenGLSurface); | ||||
| 
 | ||||
|         // TODO: One of these flags might be interesting: WA_OpaquePaintEvent, WA_NoBackground,
 | ||||
|         // WA_DontShowOnScreen, WA_DeleteOnClose
 | ||||
|     } | ||||
| 
 | ||||
|     ~OpenGLWindow() override { | ||||
|         context->doneCurrent(); | ||||
|     } | ||||
| 
 | ||||
|     void Present() override { | ||||
|         if (!isExposed()) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         context->makeCurrent(this); | ||||
|         Core::System::GetInstance().Renderer().TryPresent(100); | ||||
|         context->swapBuffers(this); | ||||
|         auto f = context->versionFunctions<QOpenGLFunctions_4_3_Core>(); | ||||
|         f->glFinish(); | ||||
|         QWindow::requestUpdate(); | ||||
|     } | ||||
| 
 | ||||
| private: | ||||
|     QOpenGLContext* context{}; | ||||
| }; | ||||
| 
 | ||||
| #ifdef HAS_VULKAN | ||||
| class GVKWidgetInternal final : public GWidgetInternal { | ||||
| class VulkanWindow final : public ChildRenderWindow { | ||||
| public: | ||||
|     GVKWidgetInternal(GRenderWindow* parent, QVulkanInstance* instance) : GWidgetInternal(parent) { | ||||
|     VulkanWindow(QWindow* parent, QWidget* event_handler, QVulkanInstance* instance) | ||||
|         : ChildRenderWindow{parent, event_handler} { | ||||
|         setSurfaceType(QSurface::SurfaceType::VulkanSurface); | ||||
|         setVulkanInstance(instance); | ||||
|     } | ||||
|     ~GVKWidgetInternal() override = default; | ||||
| 
 | ||||
|     ~VulkanWindow() override = default; | ||||
| 
 | ||||
|     void Present() override { | ||||
|         // TODO(bunnei): ImplementMe
 | ||||
|     } | ||||
| 
 | ||||
| private: | ||||
|     QWidget* event_handler{}; | ||||
| }; | ||||
| #endif | ||||
| 
 | ||||
| GRenderWindow::GRenderWindow(GMainWindow* parent, EmuThread* emu_thread) | ||||
|     : QWidget(parent), emu_thread(emu_thread) { | ||||
| GRenderWindow::GRenderWindow(QWidget* parent_, EmuThread* emu_thread) | ||||
|     : QWidget(parent_), emu_thread(emu_thread) { | ||||
|     setWindowTitle(QStringLiteral("yuzu %1 | %2-%3") | ||||
|                        .arg(QString::fromUtf8(Common::g_build_name), | ||||
|                             QString::fromUtf8(Common::g_scm_branch), | ||||
|                             QString::fromUtf8(Common::g_scm_desc))); | ||||
|     setAttribute(Qt::WA_AcceptTouchEvents); | ||||
| 
 | ||||
|     auto layout = new QHBoxLayout(this); | ||||
|     layout->setMargin(0); | ||||
|     setLayout(layout); | ||||
|     InputCommon::Init(); | ||||
| 
 | ||||
|     GMainWindow* parent = GetMainWindow(); | ||||
|     connect(this, &GRenderWindow::FirstFrameDisplayed, parent, &GMainWindow::OnLoadComplete); | ||||
| } | ||||
| 
 | ||||
| GRenderWindow::~GRenderWindow() { | ||||
|     InputCommon::Shutdown(); | ||||
| 
 | ||||
|     // Avoid an unordered destruction that generates a segfault
 | ||||
|     delete child; | ||||
| } | ||||
| 
 | ||||
| void GRenderWindow::moveContext() { | ||||
|     if (!context) { | ||||
|         return; | ||||
| void GRenderWindow::MakeCurrent() { | ||||
|     if (core_context) { | ||||
|         core_context->MakeCurrent(); | ||||
|     } | ||||
|     DoneCurrent(); | ||||
| 
 | ||||
|     // If the thread started running, move the GL Context to the new thread. Otherwise, move it
 | ||||
|     // back.
 | ||||
|     auto thread = (QThread::currentThread() == qApp->thread() && emu_thread != nullptr) | ||||
|                       ? emu_thread | ||||
|                       : qApp->thread(); | ||||
|     context->moveToThread(thread); | ||||
| } | ||||
| 
 | ||||
| void GRenderWindow::SwapBuffers() { | ||||
|     if (context) { | ||||
|         context->swapBuffers(child); | ||||
| void GRenderWindow::DoneCurrent() { | ||||
|     if (core_context) { | ||||
|         core_context->DoneCurrent(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void GRenderWindow::PollEvents() { | ||||
|     if (!first_frame) { | ||||
|         first_frame = true; | ||||
|         emit FirstFrameDisplayed(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void GRenderWindow::MakeCurrent() { | ||||
|     if (context) { | ||||
|         context->makeCurrent(child); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void GRenderWindow::DoneCurrent() { | ||||
|     if (context) { | ||||
|         context->doneCurrent(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void GRenderWindow::PollEvents() {} | ||||
| 
 | ||||
| bool GRenderWindow::IsShown() const { | ||||
|     return !isMinimized(); | ||||
| } | ||||
| @ -291,7 +314,7 @@ void GRenderWindow::RetrieveVulkanHandlers(void* get_instance_proc_addr, void* i | ||||
| #ifdef HAS_VULKAN | ||||
|     const auto instance_proc_addr = vk_instance->getInstanceProcAddr("vkGetInstanceProcAddr"); | ||||
|     const VkInstance instance_copy = vk_instance->vkInstance(); | ||||
|     const VkSurfaceKHR surface_copy = vk_instance->surfaceForWindow(child); | ||||
|     const VkSurfaceKHR surface_copy = vk_instance->surfaceForWindow(child_window); | ||||
| 
 | ||||
|     std::memcpy(get_instance_proc_addr, &instance_proc_addr, sizeof(instance_proc_addr)); | ||||
|     std::memcpy(instance, &instance_copy, sizeof(instance_copy)); | ||||
| @ -309,21 +332,10 @@ void GRenderWindow::RetrieveVulkanHandlers(void* get_instance_proc_addr, void* i | ||||
| void GRenderWindow::OnFramebufferSizeChanged() { | ||||
|     // Screen changes potentially incur a change in screen DPI, hence we should update the
 | ||||
|     // framebuffer size
 | ||||
|     const qreal pixelRatio{GetWindowPixelRatio()}; | ||||
|     const auto size{child->GetSize()}; | ||||
|     UpdateCurrentFramebufferLayout(size.first * pixelRatio, size.second * pixelRatio); | ||||
| } | ||||
| 
 | ||||
| void GRenderWindow::ForwardKeyPressEvent(QKeyEvent* event) { | ||||
|     if (child) { | ||||
|         child->keyPressEvent(event); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void GRenderWindow::ForwardKeyReleaseEvent(QKeyEvent* event) { | ||||
|     if (child) { | ||||
|         child->keyReleaseEvent(event); | ||||
|     } | ||||
|     const qreal pixel_ratio = windowPixelRatio(); | ||||
|     const u32 width = this->width() * pixel_ratio; | ||||
|     const u32 height = this->height() * pixel_ratio; | ||||
|     UpdateCurrentFramebufferLayout(width, height); | ||||
| } | ||||
| 
 | ||||
| void GRenderWindow::BackupGeometry() { | ||||
| @ -351,13 +363,12 @@ QByteArray GRenderWindow::saveGeometry() { | ||||
|     return geometry; | ||||
| } | ||||
| 
 | ||||
| qreal GRenderWindow::GetWindowPixelRatio() const { | ||||
|     // windowHandle() might not be accessible until the window is displayed to screen.
 | ||||
|     return windowHandle() ? windowHandle()->screen()->devicePixelRatio() : 1.0f; | ||||
| qreal GRenderWindow::windowPixelRatio() const { | ||||
|     return devicePixelRatio(); | ||||
| } | ||||
| 
 | ||||
| std::pair<u32, u32> GRenderWindow::ScaleTouch(const QPointF pos) const { | ||||
|     const qreal pixel_ratio{GetWindowPixelRatio()}; | ||||
|     const qreal pixel_ratio = windowPixelRatio(); | ||||
|     return {static_cast<u32>(std::max(std::round(pos.x() * pixel_ratio), qreal{0.0})), | ||||
|             static_cast<u32>(std::max(std::round(pos.y() * pixel_ratio), qreal{0.0}))}; | ||||
| } | ||||
| @ -367,6 +378,47 @@ void GRenderWindow::closeEvent(QCloseEvent* event) { | ||||
|     QWidget::closeEvent(event); | ||||
| } | ||||
| 
 | ||||
| void GRenderWindow::keyPressEvent(QKeyEvent* event) { | ||||
|     InputCommon::GetKeyboard()->PressKey(event->key()); | ||||
| } | ||||
| 
 | ||||
| void GRenderWindow::keyReleaseEvent(QKeyEvent* event) { | ||||
|     InputCommon::GetKeyboard()->ReleaseKey(event->key()); | ||||
| } | ||||
| 
 | ||||
| void GRenderWindow::mousePressEvent(QMouseEvent* event) { | ||||
|     if (event->source() == Qt::MouseEventSynthesizedBySystem) | ||||
|         return; // touch input is handled in TouchBeginEvent
 | ||||
| 
 | ||||
|     auto pos = event->pos(); | ||||
|     if (event->button() == Qt::LeftButton) { | ||||
|         const auto [x, y] = ScaleTouch(pos); | ||||
|         this->TouchPressed(x, y); | ||||
|     } else if (event->button() == Qt::RightButton) { | ||||
|         InputCommon::GetMotionEmu()->BeginTilt(pos.x(), pos.y()); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void GRenderWindow::mouseMoveEvent(QMouseEvent* event) { | ||||
|     if (event->source() == Qt::MouseEventSynthesizedBySystem) | ||||
|         return; // touch input is handled in TouchUpdateEvent
 | ||||
| 
 | ||||
|     auto pos = event->pos(); | ||||
|     const auto [x, y] = ScaleTouch(pos); | ||||
|     this->TouchMoved(x, y); | ||||
|     InputCommon::GetMotionEmu()->Tilt(pos.x(), pos.y()); | ||||
| } | ||||
| 
 | ||||
| void GRenderWindow::mouseReleaseEvent(QMouseEvent* event) { | ||||
|     if (event->source() == Qt::MouseEventSynthesizedBySystem) | ||||
|         return; // touch input is handled in TouchEndEvent
 | ||||
| 
 | ||||
|     if (event->button() == Qt::LeftButton) | ||||
|         this->TouchReleased(); | ||||
|     else if (event->button() == Qt::RightButton) | ||||
|         InputCommon::GetMotionEmu()->EndTilt(); | ||||
| } | ||||
| 
 | ||||
| void GRenderWindow::TouchBeginEvent(const QTouchEvent* event) { | ||||
|     // TouchBegin always has exactly one touch point, so take the .first()
 | ||||
|     const auto [x, y] = ScaleTouch(event->touchPoints().first().pos()); | ||||
| @ -415,26 +467,20 @@ void GRenderWindow::focusOutEvent(QFocusEvent* event) { | ||||
|     InputCommon::GetKeyboard()->ReleaseAllKeys(); | ||||
| } | ||||
| 
 | ||||
| void GRenderWindow::OnClientAreaResized(u32 width, u32 height) { | ||||
|     NotifyClientAreaSizeChanged(std::make_pair(width, height)); | ||||
| void GRenderWindow::resizeEvent(QResizeEvent* event) { | ||||
|     QWidget::resizeEvent(event); | ||||
|     OnFramebufferSizeChanged(); | ||||
| } | ||||
| 
 | ||||
| std::unique_ptr<Core::Frontend::GraphicsContext> GRenderWindow::CreateSharedContext() const { | ||||
|     return std::make_unique<GGLContext>(context.get()); | ||||
|     if (Settings::values.renderer_backend == Settings::RendererBackend::OpenGL) { | ||||
|         return std::make_unique<GGLContext>(QOpenGLContext::globalShareContext()); | ||||
|     } | ||||
|     return {}; | ||||
| } | ||||
| 
 | ||||
| bool GRenderWindow::InitRenderTarget() { | ||||
|     shared_context.reset(); | ||||
|     context.reset(); | ||||
|     if (child) { | ||||
|         delete child; | ||||
|     } | ||||
|     if (container) { | ||||
|         delete container; | ||||
|     } | ||||
|     if (layout()) { | ||||
|         delete layout(); | ||||
|     } | ||||
|     ReleaseRenderTarget(); | ||||
| 
 | ||||
|     first_frame = false; | ||||
| 
 | ||||
| @ -451,13 +497,6 @@ bool GRenderWindow::InitRenderTarget() { | ||||
|         break; | ||||
|     } | ||||
| 
 | ||||
|     container = QWidget::createWindowContainer(child, this); | ||||
|     QBoxLayout* layout = new QHBoxLayout(this); | ||||
| 
 | ||||
|     layout->addWidget(container); | ||||
|     layout->setMargin(0); | ||||
|     setLayout(layout); | ||||
| 
 | ||||
|     // Reset minimum required size to avoid resizing issues on the main window after restarting.
 | ||||
|     setMinimumSize(1, 1); | ||||
| 
 | ||||
| @ -467,14 +506,9 @@ bool GRenderWindow::InitRenderTarget() { | ||||
|     hide(); | ||||
| 
 | ||||
|     resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height); | ||||
|     child->resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height); | ||||
|     container->resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height); | ||||
| 
 | ||||
|     OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size); | ||||
| 
 | ||||
|     OnFramebufferSizeChanged(); | ||||
|     NotifyClientAreaSizeChanged(child->GetSize()); | ||||
| 
 | ||||
|     BackupGeometry(); | ||||
| 
 | ||||
|     if (Settings::values.renderer_backend == Settings::RendererBackend::OpenGL) { | ||||
| @ -486,6 +520,14 @@ bool GRenderWindow::InitRenderTarget() { | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| void GRenderWindow::ReleaseRenderTarget() { | ||||
|     if (child_widget) { | ||||
|         layout()->removeWidget(child_widget); | ||||
|         delete child_widget; | ||||
|         child_widget = nullptr; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void GRenderWindow::CaptureScreenshot(u32 res_scale, const QString& screenshot_path) { | ||||
|     auto& renderer = Core::System::GetInstance().Renderer(); | ||||
| 
 | ||||
| @ -521,16 +563,19 @@ bool GRenderWindow::InitializeOpenGL() { | ||||
|     fmt.setOption(QSurfaceFormat::FormatOption::DeprecatedFunctions); | ||||
|     // TODO: expose a setting for buffer value (ie default/single/double/triple)
 | ||||
|     fmt.setSwapBehavior(QSurfaceFormat::DefaultSwapBehavior); | ||||
|     shared_context = std::make_unique<QOpenGLContext>(); | ||||
|     shared_context->setFormat(fmt); | ||||
|     shared_context->create(); | ||||
|     context = std::make_unique<QOpenGLContext>(); | ||||
|     context->setShareContext(shared_context.get()); | ||||
|     context->setFormat(fmt); | ||||
|     context->create(); | ||||
|     fmt.setSwapInterval(false); | ||||
|     fmt.setSwapInterval(0); | ||||
|     QSurfaceFormat::setDefaultFormat(fmt); | ||||
| 
 | ||||
|     GMainWindow* parent = GetMainWindow(); | ||||
|     QWindow* parent_win_handle = parent ? parent->windowHandle() : nullptr; | ||||
|     child_window = new OpenGLWindow(parent_win_handle, this, QOpenGLContext::globalShareContext()); | ||||
|     child_window->create(); | ||||
|     child_widget = createWindowContainer(child_window, this); | ||||
|     child_widget->resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height); | ||||
|     layout()->addWidget(child_widget); | ||||
| 
 | ||||
|     core_context = CreateSharedContext(); | ||||
| 
 | ||||
|     child = new GGLWidgetInternal(this, shared_context.get()); | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| @ -559,7 +604,14 @@ bool GRenderWindow::InitializeVulkan() { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     child = new GVKWidgetInternal(this, vk_instance.get()); | ||||
|     GMainWindow* parent = GetMainWindow(); | ||||
|     QWindow* parent_win_handle = parent ? parent->windowHandle() : nullptr; | ||||
|     child_window = new VulkanWindow(parent_win_handle, this, vk_instance.get()); | ||||
|     child_window->create(); | ||||
|     child_widget = createWindowContainer(child_window, this); | ||||
|     child_widget->resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height); | ||||
|     layout()->addWidget(child_widget); | ||||
| 
 | ||||
|     return true; | ||||
| #else | ||||
|     QMessageBox::critical(this, tr("Vulkan not available!"), | ||||
| @ -569,7 +621,7 @@ bool GRenderWindow::InitializeVulkan() { | ||||
| } | ||||
| 
 | ||||
| bool GRenderWindow::LoadOpenGL() { | ||||
|     Core::Frontend::ScopeAcquireWindowContext acquire_context{*this}; | ||||
|     Core::Frontend::ScopeAcquireContext acquire_context{*this}; | ||||
|     if (!gladLoadGL()) { | ||||
|         QMessageBox::critical(this, tr("Error while initializing OpenGL 4.3!"), | ||||
|                               tr("Your GPU may not support OpenGL 4.3, or you do not have the " | ||||
| @ -621,12 +673,10 @@ QStringList GRenderWindow::GetUnsupportedGLExtensions() const { | ||||
| 
 | ||||
| void GRenderWindow::OnEmulationStarting(EmuThread* emu_thread) { | ||||
|     this->emu_thread = emu_thread; | ||||
|     child->DisablePainting(); | ||||
| } | ||||
| 
 | ||||
| void GRenderWindow::OnEmulationStopping() { | ||||
|     emu_thread = nullptr; | ||||
|     child->EnablePainting(); | ||||
| } | ||||
| 
 | ||||
| void GRenderWindow::showEvent(QShowEvent* event) { | ||||
|  | ||||
| @ -11,11 +11,13 @@ | ||||
| #include <QImage> | ||||
| #include <QThread> | ||||
| #include <QWidget> | ||||
| #include <QWindow> | ||||
| 
 | ||||
| #include "common/thread.h" | ||||
| #include "core/core.h" | ||||
| #include "core/frontend/emu_window.h" | ||||
| 
 | ||||
| class GRenderWindow; | ||||
| class QKeyEvent; | ||||
| class QScreen; | ||||
| class QTouchEvent; | ||||
| @ -26,14 +28,6 @@ class QOpenGLContext; | ||||
| class QVulkanInstance; | ||||
| #endif | ||||
| 
 | ||||
| class GWidgetInternal; | ||||
| class GGLWidgetInternal; | ||||
| class GVKWidgetInternal; | ||||
| class GMainWindow; | ||||
| class GRenderWindow; | ||||
| class QSurface; | ||||
| class QOpenGLContext; | ||||
| 
 | ||||
| namespace VideoCore { | ||||
| enum class LoadCallbackStage; | ||||
| } | ||||
| @ -42,7 +36,7 @@ class EmuThread final : public QThread { | ||||
|     Q_OBJECT | ||||
| 
 | ||||
| public: | ||||
|     explicit EmuThread(GRenderWindow* render_window); | ||||
|     explicit EmuThread(GRenderWindow& window); | ||||
|     ~EmuThread() override; | ||||
| 
 | ||||
|     /**
 | ||||
| @ -96,7 +90,11 @@ private: | ||||
|     std::mutex running_mutex; | ||||
|     std::condition_variable running_cv; | ||||
| 
 | ||||
|     GRenderWindow* render_window; | ||||
|     /// Only used in asynchronous GPU mode
 | ||||
|     std::unique_ptr<Core::Frontend::GraphicsContext> shared_context; | ||||
| 
 | ||||
|     /// This is shared_context in asynchronous GPU mode, core_context in synchronous GPU mode
 | ||||
|     Core::Frontend::GraphicsContext& context; | ||||
| 
 | ||||
| signals: | ||||
|     /**
 | ||||
| @ -126,11 +124,10 @@ class GRenderWindow : public QWidget, public Core::Frontend::EmuWindow { | ||||
|     Q_OBJECT | ||||
| 
 | ||||
| public: | ||||
|     GRenderWindow(GMainWindow* parent, EmuThread* emu_thread); | ||||
|     GRenderWindow(QWidget* parent, EmuThread* emu_thread); | ||||
|     ~GRenderWindow() override; | ||||
| 
 | ||||
|     // EmuWindow implementation
 | ||||
|     void SwapBuffers() override; | ||||
|     // EmuWindow implementation.
 | ||||
|     void MakeCurrent() override; | ||||
|     void DoneCurrent() override; | ||||
|     void PollEvents() override; | ||||
| @ -139,30 +136,36 @@ public: | ||||
|                                 void* surface) const override; | ||||
|     std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override; | ||||
| 
 | ||||
|     void ForwardKeyPressEvent(QKeyEvent* event); | ||||
|     void ForwardKeyReleaseEvent(QKeyEvent* event); | ||||
| 
 | ||||
|     void BackupGeometry(); | ||||
|     void RestoreGeometry(); | ||||
|     void restoreGeometry(const QByteArray& geometry); // overridden
 | ||||
|     QByteArray saveGeometry();                        // overridden
 | ||||
| 
 | ||||
|     qreal GetWindowPixelRatio() const; | ||||
|     std::pair<u32, u32> ScaleTouch(QPointF pos) const; | ||||
|     qreal windowPixelRatio() const; | ||||
| 
 | ||||
|     void closeEvent(QCloseEvent* event) override; | ||||
| 
 | ||||
|     void resizeEvent(QResizeEvent* event) override; | ||||
| 
 | ||||
|     void keyPressEvent(QKeyEvent* event) override; | ||||
|     void keyReleaseEvent(QKeyEvent* event) override; | ||||
| 
 | ||||
|     void mousePressEvent(QMouseEvent* event) override; | ||||
|     void mouseMoveEvent(QMouseEvent* event) override; | ||||
|     void mouseReleaseEvent(QMouseEvent* event) override; | ||||
| 
 | ||||
|     bool event(QEvent* event) override; | ||||
| 
 | ||||
|     void focusOutEvent(QFocusEvent* event) override; | ||||
| 
 | ||||
|     void OnClientAreaResized(u32 width, u32 height); | ||||
| 
 | ||||
|     bool InitRenderTarget(); | ||||
| 
 | ||||
|     /// Destroy the previous run's child_widget which should also destroy the child_window
 | ||||
|     void ReleaseRenderTarget(); | ||||
| 
 | ||||
|     void CaptureScreenshot(u32 res_scale, const QString& screenshot_path); | ||||
| 
 | ||||
| public slots: | ||||
|     void moveContext(); // overridden
 | ||||
| 
 | ||||
|     void OnEmulationStarting(EmuThread* emu_thread); | ||||
|     void OnEmulationStopping(); | ||||
|     void OnFramebufferSizeChanged(); | ||||
| @ -173,6 +176,7 @@ signals: | ||||
|     void FirstFrameDisplayed(); | ||||
| 
 | ||||
| private: | ||||
|     std::pair<u32, u32> ScaleTouch(QPointF pos) const; | ||||
|     void TouchBeginEvent(const QTouchEvent* event); | ||||
|     void TouchUpdateEvent(const QTouchEvent* event); | ||||
|     void TouchEndEvent(); | ||||
| @ -184,15 +188,9 @@ private: | ||||
|     bool LoadOpenGL(); | ||||
|     QStringList GetUnsupportedGLExtensions() const; | ||||
| 
 | ||||
|     QWidget* container = nullptr; | ||||
|     GWidgetInternal* child = nullptr; | ||||
| 
 | ||||
|     EmuThread* emu_thread; | ||||
|     // Context that backs the GGLWidgetInternal (and will be used by core to render)
 | ||||
|     std::unique_ptr<QOpenGLContext> context; | ||||
|     // Context that will be shared between all newly created contexts. This should never be made
 | ||||
|     // current
 | ||||
|     std::unique_ptr<QOpenGLContext> shared_context; | ||||
| 
 | ||||
|     std::unique_ptr<GraphicsContext> core_context; | ||||
| 
 | ||||
| #ifdef HAS_VULKAN | ||||
|     std::unique_ptr<QVulkanInstance> vk_instance; | ||||
| @ -202,6 +200,15 @@ private: | ||||
|     QImage screenshot_image; | ||||
| 
 | ||||
|     QByteArray geometry; | ||||
| 
 | ||||
|     /// Native window handle that backs this presentation widget
 | ||||
|     QWindow* child_window = nullptr; | ||||
| 
 | ||||
|     /// In order to embed the window into GRenderWindow, you need to use createWindowContainer to
 | ||||
|     /// put the child_window into a widget then add it to the layout. This child_widget can be
 | ||||
|     /// parented to GRenderWindow and use Qt's lifetime system
 | ||||
|     QWidget* child_widget = nullptr; | ||||
| 
 | ||||
|     bool first_frame = false; | ||||
| 
 | ||||
| protected: | ||||
|  | ||||
| @ -640,6 +640,7 @@ void Config::ReadRendererValues() { | ||||
|         ReadSetting(QStringLiteral("use_accurate_gpu_emulation"), false).toBool(); | ||||
|     Settings::values.use_asynchronous_gpu_emulation = | ||||
|         ReadSetting(QStringLiteral("use_asynchronous_gpu_emulation"), false).toBool(); | ||||
|     Settings::values.use_vsync = ReadSetting(QStringLiteral("use_vsync"), true).toBool(); | ||||
|     Settings::values.force_30fps_mode = | ||||
|         ReadSetting(QStringLiteral("force_30fps_mode"), false).toBool(); | ||||
| 
 | ||||
| @ -1074,6 +1075,7 @@ void Config::SaveRendererValues() { | ||||
|                  Settings::values.use_accurate_gpu_emulation, false); | ||||
|     WriteSetting(QStringLiteral("use_asynchronous_gpu_emulation"), | ||||
|                  Settings::values.use_asynchronous_gpu_emulation, false); | ||||
|     WriteSetting(QStringLiteral("use_vsync"), Settings::values.use_vsync, true); | ||||
|     WriteSetting(QStringLiteral("force_30fps_mode"), Settings::values.force_30fps_mode, false); | ||||
| 
 | ||||
|     // Cast to double because Qt's written float values are not human-readable
 | ||||
|  | ||||
| @ -103,6 +103,8 @@ void ConfigureGraphics::SetConfiguration() { | ||||
|     ui->use_accurate_gpu_emulation->setChecked(Settings::values.use_accurate_gpu_emulation); | ||||
|     ui->use_asynchronous_gpu_emulation->setEnabled(runtime_lock); | ||||
|     ui->use_asynchronous_gpu_emulation->setChecked(Settings::values.use_asynchronous_gpu_emulation); | ||||
|     ui->use_vsync->setEnabled(runtime_lock); | ||||
|     ui->use_vsync->setChecked(Settings::values.use_vsync); | ||||
|     ui->force_30fps_mode->setEnabled(runtime_lock); | ||||
|     ui->force_30fps_mode->setChecked(Settings::values.force_30fps_mode); | ||||
|     UpdateBackgroundColorButton(QColor::fromRgbF(Settings::values.bg_red, Settings::values.bg_green, | ||||
| @ -120,6 +122,7 @@ void ConfigureGraphics::ApplyConfiguration() { | ||||
|     Settings::values.use_accurate_gpu_emulation = ui->use_accurate_gpu_emulation->isChecked(); | ||||
|     Settings::values.use_asynchronous_gpu_emulation = | ||||
|         ui->use_asynchronous_gpu_emulation->isChecked(); | ||||
|     Settings::values.use_vsync = ui->use_vsync->isChecked(); | ||||
|     Settings::values.force_30fps_mode = ui->force_30fps_mode->isChecked(); | ||||
|     Settings::values.bg_red = static_cast<float>(bg_color.redF()); | ||||
|     Settings::values.bg_green = static_cast<float>(bg_color.greenF()); | ||||
|  | ||||
| @ -84,6 +84,16 @@ | ||||
|           </property> | ||||
|          </widget> | ||||
|         </item> | ||||
|         <item> | ||||
|          <widget class="QCheckBox" name="use_vsync"> | ||||
|           <property name="toolTip"> | ||||
|            <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> | ||||
|           </property> | ||||
|           <property name="text"> | ||||
|            <string>Use VSync (OpenGL only)</string> | ||||
|           </property> | ||||
|          </widget> | ||||
|         </item> | ||||
|         <item> | ||||
|          <widget class="QCheckBox" name="use_accurate_gpu_emulation"> | ||||
|           <property name="text"> | ||||
|  | ||||
| @ -20,7 +20,6 @@ | ||||
| #include "core/file_sys/vfs.h" | ||||
| #include "core/file_sys/vfs_real.h" | ||||
| #include "core/frontend/applets/general_frontend.h" | ||||
| #include "core/frontend/scope_acquire_window_context.h" | ||||
| #include "core/hle/service/acc/profile_manager.h" | ||||
| #include "core/hle/service/am/applet_ae.h" | ||||
| #include "core/hle/service/am/applet_oe.h" | ||||
| @ -985,11 +984,8 @@ void GMainWindow::BootGame(const QString& filename) { | ||||
|         return; | ||||
| 
 | ||||
|     // Create and start the emulation thread
 | ||||
|     emu_thread = std::make_unique<EmuThread>(render_window); | ||||
|     emu_thread = std::make_unique<EmuThread>(*render_window); | ||||
|     emit EmulationStarting(emu_thread.get()); | ||||
|     if (Settings::values.renderer_backend == Settings::RendererBackend::OpenGL) { | ||||
|         render_window->moveContext(); | ||||
|     } | ||||
|     emu_thread->start(); | ||||
| 
 | ||||
|     connect(render_window, &GRenderWindow::Closed, this, &GMainWindow::OnStopGame); | ||||
| @ -1087,6 +1083,9 @@ void GMainWindow::ShutdownGame() { | ||||
|     emulation_running = false; | ||||
| 
 | ||||
|     game_path.clear(); | ||||
| 
 | ||||
|     // When closing the game, destroy the GLWindow to clear the context after the game is closed
 | ||||
|     render_window->ReleaseRenderTarget(); | ||||
| } | ||||
| 
 | ||||
| void GMainWindow::StoreRecentFile(const QString& filename) { | ||||
| @ -2215,48 +2214,47 @@ void GMainWindow::closeEvent(QCloseEvent* event) { | ||||
|     QWidget::closeEvent(event); | ||||
| } | ||||
| 
 | ||||
| void GMainWindow::keyPressEvent(QKeyEvent* event) { | ||||
|     if (render_window) { | ||||
|         render_window->ForwardKeyPressEvent(event); | ||||
| static bool IsSingleFileDropEvent(const QMimeData* mime) { | ||||
|     return mime->hasUrls() && mime->urls().length() == 1; | ||||
| } | ||||
| 
 | ||||
| void GMainWindow::AcceptDropEvent(QDropEvent* event) { | ||||
|     if (IsSingleFileDropEvent(event->mimeData())) { | ||||
|         event->setDropAction(Qt::DropAction::LinkAction); | ||||
|         event->accept(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void GMainWindow::keyReleaseEvent(QKeyEvent* event) { | ||||
|     if (render_window) { | ||||
|         render_window->ForwardKeyReleaseEvent(event); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| static bool IsSingleFileDropEvent(QDropEvent* event) { | ||||
|     const QMimeData* mimeData = event->mimeData(); | ||||
|     return mimeData->hasUrls() && mimeData->urls().length() == 1; | ||||
| } | ||||
| 
 | ||||
| void GMainWindow::dropEvent(QDropEvent* event) { | ||||
|     if (!IsSingleFileDropEvent(event)) { | ||||
|         return; | ||||
| bool GMainWindow::DropAction(QDropEvent* event) { | ||||
|     if (!IsSingleFileDropEvent(event->mimeData())) { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     const QMimeData* mime_data = event->mimeData(); | ||||
|     const QString filename = mime_data->urls().at(0).toLocalFile(); | ||||
|     const QString& filename = mime_data->urls().at(0).toLocalFile(); | ||||
| 
 | ||||
|     if (emulation_running && QFileInfo(filename).suffix() == QStringLiteral("bin")) { | ||||
|         // Amiibo
 | ||||
|         LoadAmiibo(filename); | ||||
|     } else { | ||||
|         // Game
 | ||||
|         if (ConfirmChangeGame()) { | ||||
|             BootGame(filename); | ||||
|         } | ||||
|     } | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| void GMainWindow::dropEvent(QDropEvent* event) { | ||||
|     DropAction(event); | ||||
| } | ||||
| 
 | ||||
| void GMainWindow::dragEnterEvent(QDragEnterEvent* event) { | ||||
|     if (IsSingleFileDropEvent(event)) { | ||||
|         event->acceptProposedAction(); | ||||
|     } | ||||
|     AcceptDropEvent(event); | ||||
| } | ||||
| 
 | ||||
| void GMainWindow::dragMoveEvent(QDragMoveEvent* event) { | ||||
|     event->acceptProposedAction(); | ||||
|     AcceptDropEvent(event); | ||||
| } | ||||
| 
 | ||||
| bool GMainWindow::ConfirmChangeGame() { | ||||
| @ -2377,6 +2375,7 @@ int main(int argc, char* argv[]) { | ||||
| 
 | ||||
|     // Enables the core to make the qt created contexts current on std::threads
 | ||||
|     QCoreApplication::setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity); | ||||
|     QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts); | ||||
|     QApplication app(argc, argv); | ||||
| 
 | ||||
|     // Qt changes the locale and causes issues in float conversion using std::to_string() when
 | ||||
|  | ||||
| @ -78,6 +78,9 @@ public: | ||||
| 
 | ||||
|     std::unique_ptr<DiscordRPC::DiscordInterface> discord_rpc; | ||||
| 
 | ||||
|     bool DropAction(QDropEvent* event); | ||||
|     void AcceptDropEvent(QDropEvent* event); | ||||
| 
 | ||||
| signals: | ||||
| 
 | ||||
|     /**
 | ||||
| @ -264,8 +267,4 @@ protected: | ||||
|     void dropEvent(QDropEvent* event) override; | ||||
|     void dragEnterEvent(QDragEnterEvent* event) override; | ||||
|     void dragMoveEvent(QDragMoveEvent* event) override; | ||||
| 
 | ||||
|     // Overrides used to forward signals to the render window when the focus moves out.
 | ||||
|     void keyPressEvent(QKeyEvent* event) override; | ||||
|     void keyReleaseEvent(QKeyEvent* event) override; | ||||
| }; | ||||
|  | ||||
| @ -390,6 +390,8 @@ void Config::ReadValues() { | ||||
|         sdl2_config->GetBoolean("Renderer", "use_accurate_gpu_emulation", false); | ||||
|     Settings::values.use_asynchronous_gpu_emulation = | ||||
|         sdl2_config->GetBoolean("Renderer", "use_asynchronous_gpu_emulation", false); | ||||
|     Settings::values.use_vsync = | ||||
|         static_cast<u16>(sdl2_config->GetInteger("Renderer", "use_vsync", 1)); | ||||
| 
 | ||||
|     Settings::values.bg_red = static_cast<float>(sdl2_config->GetReal("Renderer", "bg_red", 0.0)); | ||||
|     Settings::values.bg_green = | ||||
|  | ||||
| @ -150,6 +150,11 @@ use_accurate_gpu_emulation = | ||||
| # 0 : Off (slow), 1 (default): On (fast) | ||||
| use_asynchronous_gpu_emulation = | ||||
| 
 | ||||
| # Forces VSync on the display thread. Usually doesn't impact performance, but on some drivers it can | ||||
| # so only turn this off if you notice a speed difference. | ||||
| # 0: Off, 1 (default): On | ||||
| use_vsync = | ||||
| 
 | ||||
| # The clear color for the renderer. What shows up on the sides of the bottom screen. | ||||
| # Must be in range of 0.0-1.0. Defaults to 1.0 for all. | ||||
| bg_red = | ||||
|  | ||||
| @ -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); | ||||
|  | ||||
| @ -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; | ||||
|  | ||||
| @ -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); | ||||
| } | ||||
|  | ||||
| @ -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; | ||||
| }; | ||||
|  | ||||
| @ -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,7 @@ 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() { | ||||
|     // TODO (bunnei): ImplementMe
 | ||||
| } | ||||
|  | ||||
| @ -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; | ||||
| 
 | ||||
|  | ||||
| @ -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(); | ||||
| 
 | ||||
|  | ||||
| @ -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() { | ||||
|  | ||||
| @ -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; | ||||
| 
 | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 bunnei
						bunnei