diff --git a/src/citra/emu_window/emu_window_sdl2.cpp b/src/citra/emu_window/emu_window_sdl2.cpp index 5586cda29b..3e4a456718 100644 --- a/src/citra/emu_window/emu_window_sdl2.cpp +++ b/src/citra/emu_window/emu_window_sdl2.cpp @@ -225,7 +225,7 @@ void EmuWindow_SDL2::Present() { SDL_GL_MakeCurrent(render_window, window_context); SDL_GL_SetSwapInterval(1); while (IsOpen()) { - VideoCore::g_renderer->Present(); + VideoCore::g_renderer->TryPresent(100); SDL_GL_SwapWindow(render_window); VideoCore::g_renderer->PresentComplete(); } diff --git a/src/citra_qt/bootmanager.cpp b/src/citra_qt/bootmanager.cpp index 9d732d463e..27fa701530 100644 --- a/src/citra_qt/bootmanager.cpp +++ b/src/citra_qt/bootmanager.cpp @@ -260,9 +260,14 @@ void GRenderWindow::InitRenderTarget() { // TODO: One of these flags might be interesting: WA_OpaquePaintEvent, WA_NoBackground, // WA_DontShowOnScreen, WA_DeleteOnClose core_context = CreateSharedContext(); + resize(Core::kScreenTopWidth, Core::kScreenTopHeight + Core::kScreenBottomHeight); BackupGeometry(); } +void GRenderWindow::initializeGL() { + context()->format().setSwapInterval(1); +} + void GRenderWindow::CaptureScreenshot(u32 res_scale, const QString& screenshot_path) { if (res_scale == 0) res_scale = VideoCore::GetResolutionScaleFactor(); @@ -294,7 +299,7 @@ void GRenderWindow::OnEmulationStopping() { } void GRenderWindow::paintGL() { - VideoCore::g_renderer->Present(); + VideoCore::g_renderer->TryPresent(100); update(); } diff --git a/src/citra_qt/bootmanager.h b/src/citra_qt/bootmanager.h index 51188b209f..8562748783 100644 --- a/src/citra_qt/bootmanager.h +++ b/src/citra_qt/bootmanager.h @@ -130,6 +130,7 @@ public: void PollEvents() override; std::unique_ptr CreateSharedContext() const override; + void initializeGL() override; void paintGL() override; void BackupGeometry(); diff --git a/src/core/frontend/emu_window.h b/src/core/frontend/emu_window.h index 7d61da46dc..164af91975 100644 --- a/src/core/frontend/emu_window.h +++ b/src/core/frontend/emu_window.h @@ -24,24 +24,35 @@ public: virtual ~TextureMailbox() = default; /** - * Retrieve a frame that is ready to be rendered into + * Recreate the render objects attached to this frame with the new specified width/height */ - virtual Frame& GetRenderFrame() = 0; + virtual void ReloadRenderFrame(Frontend::Frame* frame, u32 width, u32 height) = 0; /** - * Mark this frame as ready to present + * Recreate the presentation objects attached to this frame with the new specified width/height */ - virtual void RenderComplete() = 0; + virtual void ReloadPresentFrame(Frontend::Frame* frame, u32 height, u32 width) = 0; /** - * Retrieve the latest frame to present in the frontend + * Render thread calls this to get an available frame to present */ - virtual Frame& GetPresentationFrame() = 0; + virtual Frontend::Frame* GetRenderFrame() = 0; /** - * Mark the presentation frame as complete and set it for reuse + * Render thread calls this after draw commands are done to add to the presentation mailbox */ - virtual void PresentationComplete() = 0; + 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 nullptr + */ + virtual Frontend::Frame* TryGetPresentFrame(int timeout_ms) = 0; + + /** + * Presentation thread calls this after swap to release the frame and add it back to the queue + */ + virtual void ReleasePresentFrame(Frame* frame) = 0; }; /** diff --git a/src/video_core/renderer_base.h b/src/video_core/renderer_base.h index 1558ba1734..0252e2dfe0 100644 --- a/src/video_core/renderer_base.h +++ b/src/video_core/renderer_base.h @@ -31,8 +31,9 @@ public: /// Finalize rendering the guest frame and draw into the presentation texture virtual void SwapBuffers() = 0; - /// Draws the latest frame to the window (Renderer specific implementation) - virtual void Present() = 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; /// Marks the presentation buffer as complete and swaps it back into the pool virtual void PresentComplete() = 0; diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index 08065ea0bb..1452cd6deb 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp @@ -3,9 +3,13 @@ // Refer to the license.txt file included. #include +#include +#include #include #include +#include #include +#include #include #include "common/assert.h" #include "common/bit_field.h" @@ -31,45 +35,128 @@ namespace Frontend { struct Frame { - GLuint index; - GLsync render_sync; - GLsync present_sync; + 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::OGLTexture color{}; /// Texture 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 }; } // namespace Frontend namespace OpenGL { +constexpr std::size_t SWAP_CHAIN_SIZE = 5; + class OGLTextureMailbox : public Frontend::TextureMailbox { public: - Frontend::Frame render_tex = {0, 0, 0}, present_tex = {1, 0, 0}, off_tex = {2, 0, 0}; - bool swapped = false; - std::mutex swap_mutex{}; + std::mutex swap_chain_lock; + std::condition_variable free_cv; + std::condition_variable present_cv; + std::array swap_chain{}; + std::deque free_queue{}; + std::deque present_queue{}; - OGLTextureMailbox() = default; - - ~OGLTextureMailbox() = default; - - Frontend::Frame& GetRenderFrame() { - return render_tex; - } - - void RenderComplete() { - std::scoped_lock lock(swap_mutex); - swapped = true; - std::swap(render_tex, off_tex); - } - - Frontend::Frame& GetPresentationFrame() { - return present_tex; - } - - void PresentationComplete() { - std::scoped_lock lock(swap_mutex); - if (swapped) { - swapped = false; - std::swap(present_tex, off_tex); + OGLTextureMailbox() { + for (auto& frame : swap_chain) { + free_queue.push_back(&frame); } } + + ~OGLTextureMailbox() override = default; + + void ReloadPresentFrame(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); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, + frame->color.handle, 0); + if (!glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE) { + LOG_CRITICAL(Render_OpenGL, "Failed to recreate present FBO!"); + } + glBindFramebuffer(GL_FRAMEBUFFER, previous_draw_fbo); + frame->color_reloaded = false; + } + + void ReloadRenderFrame(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.texture_units[0].texture_2d = frame->color.handle; + state.Apply(); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); + + // 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(); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, + frame->color.handle, 0); + 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; + } + + Frontend::Frame* GetRenderFrame() override { + std::unique_lock lock(swap_chain_lock); + // wait for new entries in the free_queue + free_cv.wait(lock, [&] { return !free_queue.empty(); }); + + Frontend::Frame* frame = free_queue.front(); + free_queue.pop_front(); + return frame; + } + + void ReleaseRenderFrame(Frontend::Frame* frame) override { + std::unique_lock lock(swap_chain_lock); + present_queue.push_front(frame); + present_cv.notify_one(); + } + + 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 nullptr + return nullptr; + } + // the newest entries are pushed to the front of the queue + 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_back(f); + free_cv.notify_one(); + } + present_queue.clear(); + return frame; + } + + void ReleasePresentFrame(Frontend::Frame* frame) override { + std::unique_lock lock(swap_chain_lock); + free_queue.push_back(frame); + free_cv.notify_one(); + } }; static const char vertex_shader[] = R"( @@ -280,56 +367,43 @@ void RendererOpenGL::SwapBuffers() { } const auto& layout = render_window.GetFramebufferLayout(); - auto& frame = render_window.mailbox->GetRenderFrame(); - auto& presentation = presentation_textures[frame.index]; + auto 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_sync) { - glClientWaitSync(frame.present_sync, 0, GL_TIMEOUT_IGNORED); + 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_sync) { - glDeleteSync(frame.render_sync); - frame.render_sync = 0; + if (frame->render_fence) { + glDeleteSync(frame->render_fence); + frame->render_fence = 0; } // wait for the presentation to be done - if (frame.present_sync) { - glWaitSync(frame.present_sync, 0, GL_TIMEOUT_IGNORED); - glDeleteSync(frame.present_sync); - frame.present_sync = 0; + if (frame->present_fence) { + glWaitSync(frame->present_fence, 0, GL_TIMEOUT_IGNORED); + glDeleteSync(frame->present_fence); + frame->present_fence = 0; } - // Recreate the presentation texture if the size of the window has changed - if (layout.width != presentation.width || layout.height != presentation.height) { - presentation.width = layout.width; - presentation.height = layout.height; - presentation.texture.Release(); - presentation.texture.Create(); - state.texture_units[0].texture_2d = presentation.texture.handle; - state.Apply(); - glActiveTexture(GL_TEXTURE0); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, layout.width, layout.height, 0, GL_RGBA, - GL_UNSIGNED_BYTE, 0); - state.texture_units[0].texture_2d = 0; - state.Apply(); + // Recreate the frame if the size of the window has changed + if (layout.width != frame->width || layout.height != frame->height) { + LOG_CRITICAL(Render_OpenGL, "Reloading render frame"); + render_window.mailbox->ReloadRenderFrame(frame, layout.width, layout.height); } - GLuint render_texture = presentation.texture.handle; - state.draw.draw_framebuffer = draw_framebuffer.handle; + GLuint render_texture = frame->color.handle; + state.draw.draw_framebuffer = frame->render.handle; state.Apply(); - glFramebufferTexture(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, render_texture, 0); - GLenum DrawBuffers[1] = {GL_COLOR_ATTACHMENT0}; - glDrawBuffers(1, DrawBuffers); // "1" is the size of DrawBuffers DrawScreens(layout); // Create a fence for the frontend to wait on and swap this frame to OffTex - frame.render_sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + frame->render_fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); glFlush(); - render_window.mailbox->RenderComplete(); + render_window.mailbox->ReleaseRenderFrame(frame); m_current_frame++; Core::System::GetInstance().perf_stats->EndSystemFrame(); @@ -479,11 +553,6 @@ void RendererOpenGL::InitOpenGLObjects() { screen_info.display_texture = screen_info.texture.resource.handle; } - draw_framebuffer.Create(); - presentation_framebuffer.Create(); - presentation_textures[0].texture.Create(); - presentation_textures[1].texture.Create(); - presentation_textures[2].texture.Create(); state.texture_units[0].texture_2d = 0; state.Apply(); } @@ -765,39 +834,37 @@ void RendererOpenGL::DrawScreens(const Layout::FramebufferLayout& layout) { } } -void RendererOpenGL::Present() { +void RendererOpenGL::TryPresent(int timeout_ms) { const auto& layout = render_window.GetFramebufferLayout(); - auto& frame = render_window.mailbox->GetPresentationFrame(); - const auto& presentation = presentation_textures[frame.index]; - const GLuint texture_handle = presentation.texture.handle; - - glWaitSync(frame.render_sync, 0, GL_TIMEOUT_IGNORED); + auto frame = render_window.mailbox->TryGetPresentFrame(timeout_ms); + if (!frame) { + LOG_CRITICAL(Render_OpenGL, "Try returned no frame to present"); + return; + } + // Recreate the presentation FBO if the color attachment was changed + if (frame->color_reloaded) { + LOG_CRITICAL(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; - glClearColor(Settings::values.bg_red, Settings::values.bg_green, Settings::values.bg_blue, - 0.0f); - glClear(GL_COLOR_BUFFER_BIT); - - glBindFramebuffer(GL_READ_FRAMEBUFFER, presentation_framebuffer.handle); - - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, texture_handle); - glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture_handle, - 0); - glBlitFramebuffer(0, 0, presentation.width, presentation.height, 0, 0, layout.width, - layout.height, GL_COLOR_BUFFER_BIT, GL_LINEAR); + 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_sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + frame->present_fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); glFlush(); + render_window.mailbox->ReleasePresentFrame(frame); } void RendererOpenGL::PresentComplete() { - render_window.mailbox->PresentationComplete(); + // render_window.mailbox->PresentationComplete(); } /// Updates the framerate diff --git a/src/video_core/renderer_opengl/renderer_opengl.h b/src/video_core/renderer_opengl/renderer_opengl.h index d8ed30d733..16c76f0a3a 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.h +++ b/src/video_core/renderer_opengl/renderer_opengl.h @@ -58,7 +58,7 @@ public: /// Draws the latest frame from texture mailbox to the currently bound draw framebuffer in this /// context - void Present() override; + void TryPresent(int timeout_ms) override; /// Finializes the presentation and sets up the presentation frame to go back into the mailbox void PresentComplete() override; @@ -130,11 +130,6 @@ private: std::array frame_dumping_pbos; GLuint current_pbo = 1; GLuint next_pbo = 0; - - // Textures used for presentation - OGLFramebuffer draw_framebuffer; - OGLFramebuffer presentation_framebuffer; - std::array presentation_textures{}; }; } // namespace OpenGL