From 571451bdfe18e9e53af3fa458f18a3192094eebe Mon Sep 17 00:00:00 2001 From: bunnei Date: Thu, 13 Feb 2020 22:06:11 -0500 Subject: core: settings: Add setting to enable vsync, which is on by default. --- src/core/settings.cpp | 1 + src/core/settings.h | 1 + src/core/telemetry_session.cpp | 1 + 3 files changed, 3 insertions(+) (limited to 'src/core') diff --git a/src/core/settings.cpp b/src/core/settings.cpp index d1fc94060..7c0303684 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -94,6 +94,7 @@ void LogSettings() { 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); diff --git a/src/core/settings.h b/src/core/settings.h index f837d3fbc..15b691342 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -435,6 +435,7 @@ struct Values { 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; diff --git a/src/core/telemetry_session.cpp b/src/core/telemetry_session.cpp index 0e72d31cd..0f3685d1c 100644 --- a/src/core/telemetry_session.cpp +++ b/src/core/telemetry_session.cpp @@ -188,6 +188,7 @@ void TelemetrySession::AddInitialInfo(Loader::AppLoader& app_loader) { 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); } -- cgit v1.2.3 From 0c82b00dfde1071b3619e288b223f771953775eb Mon Sep 17 00:00:00 2001 From: bunnei Date: Thu, 13 Feb 2020 22:10:20 -0500 Subject: core: frontend: emu_window: Add TextureMailbox class. --- src/core/frontend/emu_window.h | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) (limited to 'src/core') diff --git a/src/core/frontend/emu_window.h b/src/core/frontend/emu_window.h index 3376eedc5..5dde199d4 100644 --- a/src/core/frontend/emu_window.h +++ b/src/core/frontend/emu_window.h @@ -12,6 +12,45 @@ namespace Core::Frontend { +struct Frame; +/** + * 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 can define to sync frames between the render thread and the + * presentation thread + */ +class TextureMailbox { +public: + virtual ~TextureMailbox() = default; + + /** + * Recreate the render objects attached to this frame with the new specified width/height + */ + virtual void ReloadRenderFrame(Frontend::Frame* frame, u32 width, u32 height) = 0; + + /** + * Recreate the presentation objects attached to this frame with the new specified width/height + */ + virtual void ReloadPresentFrame(Frontend::Frame* frame, u32 width, u32 height) = 0; + + /** + * Render thread calls this to get an available frame to present + */ + virtual Frontend::Frame* GetRenderFrame() = 0; + + /** + * Render thread calls this after draw commands are done to add to the presentation mailbox + */ + virtual void ReleaseRenderFrame(Frame* frame) = 0; + + /** + * Presentation thread calls this to get the latest frame available to present. If there is no + * frame available after timeout, returns the previous frame. If there is no previous frame it + * returns nullptr + */ + virtual Frontend::Frame* TryGetPresentFrame(int timeout_ms) = 0; +}; + /** * Represents a graphics context that can be used for background computation or drawing. If the * graphics backend doesn't require the context, then the implementation of these methods can be @@ -132,6 +171,8 @@ public: */ void UpdateCurrentFramebufferLayout(unsigned width, unsigned height); + std::unique_ptr mailbox; + protected: EmuWindow(); virtual ~EmuWindow(); -- cgit v1.2.3 From dc672ca4b39c1ab9c2ee81257b6fb605a23cbcd8 Mon Sep 17 00:00:00 2001 From: bunnei Date: Mon, 17 Feb 2020 14:31:14 -0500 Subject: renderer_opengl: Add texture mailbox support for presenter thread. --- src/core/frontend/framebuffer_layout.h | 1 + src/video_core/renderer_base.h | 10 +- src/video_core/renderer_opengl/renderer_opengl.cpp | 269 +++++++++++++++++++-- src/video_core/renderer_opengl/renderer_opengl.h | 24 +- 4 files changed, 269 insertions(+), 35 deletions(-) (limited to 'src/core') diff --git a/src/core/frontend/framebuffer_layout.h b/src/core/frontend/framebuffer_layout.h index 1d39c1faf..e9d0a40d3 100644 --- a/src/core/frontend/framebuffer_layout.h +++ b/src/core/frontend/framebuffer_layout.h @@ -29,6 +29,7 @@ enum class AspectRatio { struct FramebufferLayout { u32 width{ScreenUndocked::Width}; u32 height{ScreenUndocked::Height}; + bool is_srgb{}; Common::Rectangle screen; diff --git a/src/video_core/renderer_base.h b/src/video_core/renderer_base.h index af1bebc4f..5ec99a126 100644 --- a/src/video_core/renderer_base.h +++ b/src/video_core/renderer_base.h @@ -35,15 +35,19 @@ public: 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: // ------------------------ diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index bba16afaf..ee69caa3a 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp @@ -9,11 +9,11 @@ #include #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" @@ -22,8 +22,145 @@ #include "video_core/renderer_opengl/gl_rasterizer.h" #include "video_core/renderer_opengl/renderer_opengl.h" +namespace Core::Frontend { + +struct Frame { + u32 width{}; /// Width of the frame (to detect resize) + u32 height{}; /// Height of the frame + bool color_reloaded = false; /// 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 +}; + +} // namespace Core::Frontend + 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. There doesn't seem to be much of a downside to a larger +// number but 9 swap textures at 60FPS presentation allows for 800% speed so thats probably fine +constexpr std::size_t SWAP_CHAIN_SIZE = 9; + +class OGLTextureMailbox : public Core::Frontend::TextureMailbox { +public: + std::mutex swap_chain_lock; + std::condition_variable present_cv; + std::array swap_chain{}; + std::queue free_queue; + std::deque present_queue; + Core::Frontend::Frame* previous_frame{}; + + OGLTextureMailbox() { + for (auto& frame : swap_chain) { + free_queue.push(&frame); + } + } + + ~OGLTextureMailbox() override { + // 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().swap(free_queue); + present_queue.clear(); + present_cv.notify_all(); + } + + void ReloadPresentFrame(Core::Frontend::Frame* frame, u32 height, u32 width) override { + 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(Core::Frontend::Frame* frame, u32 width, u32 height) override { + 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; + } + + Core::Frontend::Frame* GetRenderFrame() override { + 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; + } + + Core::Frontend::Frame* frame = free_queue.front(); + free_queue.pop(); + return frame; + } + + void ReleaseRenderFrame(Core::Frontend::Frame* frame) override { + std::unique_lock lock(swap_chain_lock); + present_queue.push_front(frame); + present_cv.notify_one(); + } + + Core::Frontend::Frame* TryGetPresentFrame(int timeout_ms) override { + 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 + Core::Frontend::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,16 +295,86 @@ 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} { + emu_window.mailbox = std::make_unique(); +} 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(); + + Core::Frontend::Frame* frame; + { + MICROPROFILE_SCOPE(OpenGL_WaitPresent); + + frame = render_window.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; + render_window.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(); + render_window.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(framebuffer->width) || @@ -181,22 +388,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 +610,48 @@ void RendererOpenGL::DrawScreen(const Layout::FramebufferLayout& layout) { DrawScreenTriangles(screen_info, static_cast(screen.left), static_cast(screen.top), static_cast(screen.GetWidth()), static_cast(screen.GetHeight())); +} - m_current_frame++; +void RendererOpenGL::TryPresent(int timeout_ms) { + const auto& layout = render_window.GetFramebufferLayout(); + auto frame = render_window.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"); + render_window.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::UpdateFramerate() {} +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 +686,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); diff --git a/src/video_core/renderer_opengl/renderer_opengl.h b/src/video_core/renderer_opengl/renderer_opengl.h index b56328a7f..797965925 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.h +++ b/src/video_core/renderer_opengl/renderer_opengl.h @@ -44,19 +44,21 @@ struct ScreenInfo { TextureInfo texture; }; +struct PresentationTexture { + u32 width = 0; + u32 height = 0; + OGLTexture texture; +}; + 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 +76,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 +86,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 +108,9 @@ private: /// Used for transforming the framebuffer orientation Tegra::FramebufferConfig::TransformFlags framebuffer_transform_flags; Common::Rectangle framebuffer_crop_rect; + + /// Represents if the final render frame is sRGB + bool is_srgb{}; }; } // namespace OpenGL -- cgit v1.2.3 From 2e16c237845bf1b5ff89b7b7a3f8bc1a84729eb1 Mon Sep 17 00:00:00 2001 From: bunnei Date: Mon, 17 Feb 2020 15:35:14 -0500 Subject: 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 +++++- src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp | 54 ++++++++++++++-------- src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h | 18 ++++---- src/yuzu_cmd/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 -- src/yuzu_tester/emu_window/emu_window_sdl2_hide.h | 3 -- 10 files changed, 79 insertions(+), 62 deletions(-) (limited to 'src/core') 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 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 EmuWindow_SDL2_GL::CreateSharedContext() const { return std::make_unique(); } + +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 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_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 emu_window; switch (Settings::values.renderer_backend) { case Settings::RendererBackend::OpenGL: - emu_window = std::make_unique(fullscreen); + emu_window = std::make_unique(system, fullscreen); break; case Settings::RendererBackend::Vulkan: #ifdef HAS_VULKAN - emu_window = std::make_unique(fullscreen); + emu_window = std::make_unique(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()); system.SetFilesystem(std::make_shared()); 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; -- cgit v1.2.3 From 667f026c9570b772719d2ada94cc40d420113c23 Mon Sep 17 00:00:00 2001 From: bunnei Date: Mon, 17 Feb 2020 15:38:56 -0500 Subject: core: frontend: Refactor scope_acquire_window_context to scope_acquire_context. --- src/core/CMakeLists.txt | 4 ++-- src/core/core.cpp | 3 +++ src/core/frontend/scope_acquire_context.cpp | 18 +++++++++++++++++ src/core/frontend/scope_acquire_context.h | 23 ++++++++++++++++++++++ src/core/frontend/scope_acquire_window_context.cpp | 18 ----------------- src/core/frontend/scope_acquire_window_context.h | 23 ---------------------- src/video_core/gpu_thread.cpp | 4 ++-- src/yuzu/bootmanager.cpp | 4 ++-- 8 files changed, 50 insertions(+), 47 deletions(-) create mode 100644 src/core/frontend/scope_acquire_context.cpp create mode 100644 src/core/frontend/scope_acquire_context.h delete mode 100644 src/core/frontend/scope_acquire_window_context.cpp delete mode 100644 src/core/frontend/scope_acquire_window_context.h (limited to 'src/core') diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 88c06b2ce..54be7dc0c 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -131,8 +131,8 @@ add_library(core STATIC 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 diff --git a/src/core/core.cpp b/src/core/core.cpp index 86e314c94..a82faf127 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -24,6 +24,7 @@ #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); diff --git a/src/core/frontend/scope_acquire_context.cpp b/src/core/frontend/scope_acquire_context.cpp new file mode 100644 index 000000000..878c3157c --- /dev/null +++ b/src/core/frontend/scope_acquire_context.cpp @@ -0,0 +1,18 @@ +// 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 diff --git a/src/core/frontend/scope_acquire_context.h b/src/core/frontend/scope_acquire_context.h new file mode 100644 index 000000000..7a65c0623 --- /dev/null +++ b/src/core/frontend/scope_acquire_context.h @@ -0,0 +1,23 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/common_types.h" + +namespace Core::Frontend { + +class GraphicsContext; + +/// Helper class to acquire/release window context within a given scope +class ScopeAcquireContext : NonCopyable { +public: + explicit ScopeAcquireContext(Core::Frontend::GraphicsContext& context); + ~ScopeAcquireContext(); + +private: + Core::Frontend::GraphicsContext& context; +}; + +} // namespace Core::Frontend diff --git a/src/core/frontend/scope_acquire_window_context.cpp b/src/core/frontend/scope_acquire_window_context.cpp deleted file mode 100644 index 3663dad17..000000000 --- a/src/core/frontend/scope_acquire_window_context.cpp +++ /dev/null @@ -1,18 +0,0 @@ -// 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 diff --git a/src/core/frontend/scope_acquire_window_context.h b/src/core/frontend/scope_acquire_window_context.h deleted file mode 100644 index 2d9f6e825..000000000 --- a/src/core/frontend/scope_acquire_window_context.h +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2019 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include "common/common_types.h" - -namespace Core::Frontend { - -class EmuWindow; - -/// Helper class to acquire/release window context within a given scope -class ScopeAcquireWindowContext : NonCopyable { -public: - explicit ScopeAcquireWindowContext(Core::Frontend::EmuWindow& window); - ~ScopeAcquireWindowContext(); - -private: - Core::Frontend::EmuWindow& emu_window; -}; - -} // namespace Core::Frontend diff --git a/src/video_core/gpu_thread.cpp b/src/video_core/gpu_thread.cpp index 2cdf1aa7f..b1088af3d 100644 --- a/src/video_core/gpu_thread.cpp +++ b/src/video_core/gpu_thread.cpp @@ -5,7 +5,7 @@ #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) { diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp index 55a37fffa..4982884f5 100644 --- a/src/yuzu/bootmanager.cpp +++ b/src/yuzu/bootmanager.cpp @@ -25,7 +25,7 @@ #include "common/scm_rev.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" @@ -569,7 +569,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 " -- cgit v1.2.3 From aef159354cd6c5cbbf6366bcfd767a9b4e0b7dd9 Mon Sep 17 00:00:00 2001 From: bunnei Date: Wed, 26 Feb 2020 18:28:50 -0500 Subject: renderer_opengl: Move Frame/FrameMailbox to OpenGL namespace. --- src/core/frontend/emu_window.h | 41 ------------ src/video_core/renderer_opengl/renderer_opengl.cpp | 73 +++++++++++----------- src/video_core/renderer_opengl/renderer_opengl.h | 5 ++ 3 files changed, 42 insertions(+), 77 deletions(-) (limited to 'src/core') diff --git a/src/core/frontend/emu_window.h b/src/core/frontend/emu_window.h index 856cb61e9..5eb87fb63 100644 --- a/src/core/frontend/emu_window.h +++ b/src/core/frontend/emu_window.h @@ -12,45 +12,6 @@ namespace Core::Frontend { -struct Frame; -/** - * 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 can define to sync frames between the render thread and the - * presentation thread - */ -class TextureMailbox { -public: - virtual ~TextureMailbox() = default; - - /** - * Recreate the render objects attached to this frame with the new specified width/height - */ - virtual void ReloadRenderFrame(Frontend::Frame* frame, u32 width, u32 height) = 0; - - /** - * Recreate the presentation objects attached to this frame with the new specified width/height - */ - virtual void ReloadPresentFrame(Frontend::Frame* frame, u32 width, u32 height) = 0; - - /** - * Render thread calls this to get an available frame to present - */ - virtual Frontend::Frame* GetRenderFrame() = 0; - - /** - * Render thread calls this after draw commands are done to add to the presentation mailbox - */ - virtual void ReleaseRenderFrame(Frame* frame) = 0; - - /** - * Presentation thread calls this to get the latest frame available to present. If there is no - * frame available after timeout, returns the previous frame. If there is no previous frame it - * returns nullptr - */ - virtual Frontend::Frame* TryGetPresentFrame(int timeout_ms) = 0; -}; - /** * Represents a graphics context that can be used for background computation or drawing. If the * graphics backend doesn't require the context, then the implementation of these methods can be @@ -168,8 +129,6 @@ public: */ void UpdateCurrentFramebufferLayout(unsigned width, unsigned height); - std::unique_ptr mailbox; - protected: EmuWindow(); virtual ~EmuWindow(); diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index fa226c8ca..e516ede9d 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp @@ -22,12 +22,17 @@ #include "video_core/renderer_opengl/gl_rasterizer.h" #include "video_core/renderer_opengl/renderer_opengl.h" -namespace Core::Frontend { +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. There doesn't seem to be much of a downside to a larger +// number but 9 swap textures at 60FPS presentation allows for 800% speed so thats probably fine +constexpr std::size_t SWAP_CHAIN_SIZE = 9; struct Frame { u32 width{}; /// Width of the frame (to detect resize) u32 height{}; /// Height of the frame - bool color_reloaded = false; /// Texture attachment was recreated (ie: resized) + 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 @@ -36,40 +41,37 @@ struct Frame { bool is_srgb{}; /// Framebuffer is sRGB or RGB }; -} // namespace Core::Frontend - -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. There doesn't seem to be much of a downside to a larger -// number but 9 swap textures at 60FPS presentation allows for 800% speed so thats probably fine -constexpr std::size_t SWAP_CHAIN_SIZE = 9; - -class OGLTextureMailbox : public Core::Frontend::TextureMailbox { +/** + * 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 swap_chain{}; - std::queue free_queue; - std::deque present_queue; - Core::Frontend::Frame* previous_frame{}; + std::array swap_chain{}; + std::queue free_queue; + std::deque present_queue; + Frame* previous_frame{}; - OGLTextureMailbox() { + FrameMailbox() { for (auto& frame : swap_chain) { free_queue.push(&frame); } } - ~OGLTextureMailbox() override { + ~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().swap(free_queue); + std::queue().swap(free_queue); present_queue.clear(); present_cv.notify_all(); } - void ReloadPresentFrame(Core::Frontend::Frame* frame, u32 height, u32 width) override { + void ReloadPresentFrame(Frame* frame, u32 height, u32 width) { frame->present.Release(); frame->present.Create(); GLint previous_draw_fbo{}; @@ -84,7 +86,7 @@ public: frame->color_reloaded = false; } - void ReloadRenderFrame(Core::Frontend::Frame* frame, u32 width, u32 height) override { + void ReloadRenderFrame(Frame* frame, u32 width, u32 height) { OpenGLState prev_state = OpenGLState::GetCurState(); OpenGLState state = OpenGLState::GetCurState(); @@ -103,7 +105,7 @@ public: state.Apply(); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, frame->color.handle); - if (!glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE) { + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { LOG_CRITICAL(Render_OpenGL, "Failed to recreate render FBO!"); } prev_state.Apply(); @@ -112,7 +114,7 @@ public: frame->color_reloaded = true; } - Core::Frontend::Frame* GetRenderFrame() override { + Frame* GetRenderFrame() { std::unique_lock lock(swap_chain_lock); // If theres no free frames, we will reuse the oldest render frame @@ -122,18 +124,18 @@ public: return frame; } - Core::Frontend::Frame* frame = free_queue.front(); + Frame* frame = free_queue.front(); free_queue.pop(); return frame; } - void ReleaseRenderFrame(Core::Frontend::Frame* frame) override { + void ReleaseRenderFrame(Frame* frame) { std::unique_lock lock(swap_chain_lock); present_queue.push_front(frame); present_cv.notify_one(); } - Core::Frontend::Frame* TryGetPresentFrame(int timeout_ms) override { + 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), @@ -149,7 +151,7 @@ public: } // the newest entries are pushed to the front of the queue - Core::Frontend::Frame* frame = present_queue.front(); + 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) { @@ -295,9 +297,8 @@ 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} { - emu_window.mailbox = std::make_unique(); -} + : VideoCore::RendererBase{emu_window}, emu_window{emu_window}, system{system}, + frame_mailbox{std::make_unique()} {} RendererOpenGL::~RendererOpenGL() = default; @@ -319,11 +320,11 @@ void RendererOpenGL::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) { PrepareRendertarget(framebuffer); RenderScreenshot(); - Core::Frontend::Frame* frame; + Frame* frame; { MICROPROFILE_SCOPE(OpenGL_WaitPresent); - frame = render_window.mailbox->GetRenderFrame(); + frame = frame_mailbox->GetRenderFrame(); // Clean up sync objects before drawing @@ -356,7 +357,7 @@ void RendererOpenGL::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) { is_srgb != frame->is_srgb) { LOG_DEBUG(Render_OpenGL, "Reloading render frame"); is_srgb = frame->is_srgb = screen_info.display_srgb; - render_window.mailbox->ReloadRenderFrame(frame, layout.width, layout.height); + frame_mailbox->ReloadRenderFrame(frame, layout.width, layout.height); } state.draw.draw_framebuffer = frame->render.handle; state.Apply(); @@ -364,7 +365,7 @@ void RendererOpenGL::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) { // 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(); - render_window.mailbox->ReleaseRenderFrame(frame); + frame_mailbox->ReleaseRenderFrame(frame); m_current_frame++; rasterizer->TickFrame(); } @@ -615,7 +616,7 @@ void RendererOpenGL::DrawScreen(const Layout::FramebufferLayout& layout) { void RendererOpenGL::TryPresent(int timeout_ms) { const auto& layout = render_window.GetFramebufferLayout(); - auto frame = render_window.mailbox->TryGetPresentFrame(timeout_ms); + auto frame = frame_mailbox->TryGetPresentFrame(timeout_ms); if (!frame) { LOG_DEBUG(Render_OpenGL, "TryGetPresentFrame returned no frame to present"); return; @@ -628,7 +629,7 @@ void RendererOpenGL::TryPresent(int timeout_ms) { // Recreate the presentation FBO if the color attachment was changed if (frame->color_reloaded) { LOG_DEBUG(Render_OpenGL, "Reloading present frame"); - render_window.mailbox->ReloadPresentFrame(frame, layout.width, layout.height); + frame_mailbox->ReloadPresentFrame(frame, layout.width, layout.height); } glWaitSync(frame->render_fence, 0, GL_TIMEOUT_IGNORED); // INTEL workaround. diff --git a/src/video_core/renderer_opengl/renderer_opengl.h b/src/video_core/renderer_opengl/renderer_opengl.h index 797965925..4107e10a9 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.h +++ b/src/video_core/renderer_opengl/renderer_opengl.h @@ -50,6 +50,8 @@ struct PresentationTexture { OGLTexture texture; }; +class FrameMailbox; + class RendererOpenGL final : public VideoCore::RendererBase { public: explicit RendererOpenGL(Core::Frontend::EmuWindow& emu_window, Core::System& system); @@ -111,6 +113,9 @@ private: /// Represents if the final render frame is sRGB bool is_srgb{}; + + /// Frame presentation mailbox + std::unique_ptr frame_mailbox; }; } // namespace OpenGL -- cgit v1.2.3