From db7a627a2e4733d8f60774b7b3894db06a73085a Mon Sep 17 00:00:00 2001 From: James Rowe Date: Mon, 16 Sep 2019 20:34:57 -0600 Subject: [PATCH 01/28] Add scope acquire context to simplify MakeCurrent and DoneCurrent calls --- src/core/CMakeLists.txt | 2 ++ src/core/frontend/scope_acquire_context.cpp | 17 +++++++++++++++ src/core/frontend/scope_acquire_context.h | 23 +++++++++++++++++++++ 3 files changed, 42 insertions(+) create mode 100644 src/core/frontend/scope_acquire_context.cpp create mode 100644 src/core/frontend/scope_acquire_context.h diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 3a27d8f81..67167fe5d 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -106,6 +106,8 @@ add_library(core STATIC frontend/input.h frontend/mic.h frontend/mic.cpp + frontend/scope_acquire_context.cpp + frontend/scope_acquire_context.h gdbstub/gdbstub.cpp gdbstub/gdbstub.h hle/applets/applet.cpp diff --git a/src/core/frontend/scope_acquire_context.cpp b/src/core/frontend/scope_acquire_context.cpp new file mode 100644 index 000000000..3689483af --- /dev/null +++ b/src/core/frontend/scope_acquire_context.cpp @@ -0,0 +1,17 @@ +// 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 Frontend { + +ScopeAcquireContext::ScopeAcquireContext(Frontend::GraphicsContext& context) : context{context} { + context.MakeCurrent(); +} +ScopeAcquireContext::~ScopeAcquireContext() { + context.DoneCurrent(); +} + +} // namespace Frontend \ No newline at end of file diff --git a/src/core/frontend/scope_acquire_context.h b/src/core/frontend/scope_acquire_context.h new file mode 100644 index 000000000..12ab61ec6 --- /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 Frontend { + +class GraphicsContext; + +/// Helper class to acquire/release window context within a given scope +class ScopeAcquireContext : NonCopyable { +public: + explicit ScopeAcquireContext(Frontend::GraphicsContext& context); + ~ScopeAcquireContext(); + +private: + Frontend::GraphicsContext& context; +}; + +} // namespace Frontend \ No newline at end of file From c2e79038252dbf7bbbfbf5aaa09bb5450691186f Mon Sep 17 00:00:00 2001 From: James Rowe Date: Mon, 16 Sep 2019 20:36:16 -0600 Subject: [PATCH 02/28] Split graphics out of EmuWindow in preparation of shared contexts --- src/core/frontend/emu_window.cpp | 2 + src/core/frontend/emu_window.h | 72 ++++++++++++++++++++++++++++---- 2 files changed, 65 insertions(+), 9 deletions(-) diff --git a/src/core/frontend/emu_window.cpp b/src/core/frontend/emu_window.cpp index 4fc285fee..f393e4c76 100644 --- a/src/core/frontend/emu_window.cpp +++ b/src/core/frontend/emu_window.cpp @@ -10,6 +10,8 @@ namespace Frontend { +GraphicsContext::~GraphicsContext() = default; + class EmuWindow::TouchState : public Input::Factory, public std::enable_shared_from_this { public: diff --git a/src/core/frontend/emu_window.h b/src/core/frontend/emu_window.h index f2878b202..be868f506 100644 --- a/src/core/frontend/emu_window.h +++ b/src/core/frontend/emu_window.h @@ -12,6 +12,54 @@ namespace 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; + + /** + * Retrieve a frame that is ready to be rendered into + */ + virtual Frame& GetRenderFrame() = 0; + + /** + * Mark this frame as ready to present + */ + virtual void RenderComplete() = 0; + + /** + * Retrieve the latest frame to present in the frontend + */ + virtual Frame& GetPresentationFrame() = 0; + + /** + * Mark the presentation frame as complete and set it for reuse + */ + virtual void PresentationComplete() = 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 + * stubs + */ +class GraphicsContext { +public: + virtual ~GraphicsContext(); + + /// Makes the graphics context current for the caller thread + virtual void MakeCurrent() = 0; + + /// Releases (dunno if this is the "right" word) the context from the caller thread + virtual void DoneCurrent() = 0; +}; + /** * Abstraction class used to provide an interface between emulation code and the frontend * (e.g. SDL, QGLWidget, GLFW, etc...). @@ -30,7 +78,7 @@ namespace Frontend { * - DO NOT TREAT THIS CLASS AS A GUI TOOLKIT ABSTRACTION LAYER. That's not what it is. Please * re-read the upper points again and think about it if you don't see this. */ -class EmuWindow { +class EmuWindow : public GraphicsContext { public: /// Data structure to store emuwindow configuration struct WindowConfig { @@ -40,17 +88,21 @@ public: std::pair min_client_area_size; }; - /// Swap buffers to display the next frame - virtual void SwapBuffers() = 0; - /// Polls window events virtual void PollEvents() = 0; - /// Makes the graphics context current for the caller thread - virtual void MakeCurrent() = 0; - - /// Releases (dunno if this is the "right" word) the GLFW context from the caller thread - virtual void DoneCurrent() = 0; + /** + * Returns a GraphicsContext that the frontend provides that is shared with the emu window. This + * context can be used from other threads for background graphics computation. If the frontend + * is using a graphics backend that doesn't need anything specific to run on a different thread, + * then it can use a stubbed implemenation for GraphicsContext. + * + * If the return value is null, then the core should assume that the frontend cannot provide a + * Shared Context + */ + virtual std::unique_ptr CreateSharedContext() const { + return nullptr; + } /** * Signal that a touch pressed event has occurred (e.g. mouse click pressed) @@ -102,6 +154,8 @@ public: */ void UpdateCurrentFramebufferLayout(unsigned width, unsigned height); + std::unique_ptr mailbox = nullptr; + protected: EmuWindow(); virtual ~EmuWindow(); From 27d0fc64d03005d3a97ba537a850defdb0e418d3 Mon Sep 17 00:00:00 2001 From: James Rowe Date: Mon, 16 Sep 2019 20:37:43 -0600 Subject: [PATCH 03/28] Add texture mailbox support to opengl renderer. --- src/video_core/renderer_base.h | 12 +- .../renderer_opengl/renderer_opengl.cpp | 146 +++++++++++++++++- .../renderer_opengl/renderer_opengl.h | 21 ++- 3 files changed, 162 insertions(+), 17 deletions(-) diff --git a/src/video_core/renderer_base.h b/src/video_core/renderer_base.h index 963107f1e..871ff40cb 100644 --- a/src/video_core/renderer_base.h +++ b/src/video_core/renderer_base.h @@ -19,21 +19,21 @@ class Backend; class RendererBase : NonCopyable { public: - /// Used to reference a framebuffer - enum kFramebuffer { kFramebuffer_VirtualXFB = 0, kFramebuffer_EFB, kFramebuffer_Texture }; - explicit RendererBase(Frontend::EmuWindow& window); virtual ~RendererBase(); - /// Swap buffers (render frame) - virtual void SwapBuffers() = 0; - /// Initialize the renderer virtual Core::System::ResultStatus Init() = 0; /// Shutdown the renderer virtual void ShutDown() = 0; + /// 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; + /// Prepares for video dumping (e.g. create necessary buffers, etc) virtual void PrepareVideoDumping() = 0; diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index 8de06a7d2..34d64c757 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp @@ -28,8 +28,50 @@ #include "video_core/renderer_opengl/renderer_opengl.h" #include "video_core/video_core.h" +namespace Frontend { + +struct Frame { + GLuint index; + GLsync render_sync; + GLsync present_sync; +}; +} // namespace Frontend + namespace OpenGL { +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{}; + + 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); + } + } +}; + static const char vertex_shader[] = R"( in vec2 vert_position; in vec2 vert_tex_coord; @@ -53,7 +95,7 @@ void main() { static const char fragment_shader[] = R"( in vec2 frag_tex_coord; -out vec4 color; +layout(location = 0) out vec4 color; uniform vec4 i_resolution; uniform vec4 o_resolution; @@ -130,7 +172,10 @@ static std::array MakeOrthographicMatrix(const float width, cons return matrix; } -RendererOpenGL::RendererOpenGL(Frontend::EmuWindow& window) : RendererBase{window} {} +RendererOpenGL::RendererOpenGL(Frontend::EmuWindow& window) : RendererBase{window} { + window.mailbox = std::make_unique(); +} + RendererOpenGL::~RendererOpenGL() = default; /// Swap buffers (render frame) @@ -230,20 +275,66 @@ void RendererOpenGL::SwapBuffers() { glUnmapBuffer(GL_PIXEL_PACK_BUFFER); glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); - glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); current_pbo = (current_pbo + 1) % 2; next_pbo = (current_pbo + 1) % 2; } - DrawScreens(render_window.GetFramebufferLayout()); + const auto& layout = render_window.GetFramebufferLayout(); + auto& frame = render_window.mailbox->GetRenderFrame(); + auto& presentation = presentation_textures[frame.index]; + + // 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); + } + + // delete the draw fence if the frame wasn't presented + if (frame.render_sync) { + glDeleteSync(frame.render_sync); + frame.render_sync = 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; + } + + // 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(); + } + + GLuint render_texture = presentation.texture.handle; + state.draw.draw_framebuffer = draw_framebuffer.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); + glFlush(); + render_window.mailbox->RenderComplete(); m_current_frame++; Core::System::GetInstance().perf_stats->EndSystemFrame(); - // Swap buffers render_window.PollEvents(); - render_window.SwapBuffers(); Core::System::GetInstance().frame_limiter.DoFrameLimiting( Core::System::GetInstance().CoreTiming().GetGlobalTimeUs()); @@ -388,6 +479,11 @@ 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(); } @@ -669,6 +765,38 @@ void RendererOpenGL::DrawScreens(const Layout::FramebufferLayout& layout) { } } +void RendererOpenGL::Present() { + 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); + // 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); + + /* insert fence for the main thread to block on */ + frame.present_sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + glFlush(); + render_window.mailbox->PresentationComplete(); +} + /// Updates the framerate void RendererOpenGL::UpdateFramerate() {} @@ -766,7 +894,9 @@ static void APIENTRY DebugHandler(GLenum source, GLenum type, GLuint id, GLenum /// Initialize the renderer Core::System::ResultStatus RendererOpenGL::Init() { - render_window.MakeCurrent(); + if (!gladLoadGL()) { + return Core::System::ResultStatus::ErrorVideoCore_ErrorBelowGL33; + } if (GLAD_GL_KHR_debug) { glEnable(GL_DEBUG_OUTPUT); diff --git a/src/video_core/renderer_opengl/renderer_opengl.h b/src/video_core/renderer_opengl/renderer_opengl.h index 19f31ab52..1d3345f22 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.h +++ b/src/video_core/renderer_opengl/renderer_opengl.h @@ -36,20 +36,30 @@ struct ScreenInfo { TextureInfo texture; }; +struct PresentationTexture { + u32 width = 0; + u32 height = 0; + OGLTexture texture; +}; + class RendererOpenGL : public RendererBase { public: explicit RendererOpenGL(Frontend::EmuWindow& window); ~RendererOpenGL() override; - /// Swap buffers (render frame) - void SwapBuffers() override; - /// Initialize the renderer Core::System::ResultStatus Init() override; /// Shutdown the renderer void ShutDown() override; + /// Finalizes rendering the guest frame + void SwapBuffers() override; + + /// Draws the latest frame from texture mailbox to the currently bound draw framebuffer in this + /// context + void Present() override; + /// Prepares for video dumping (e.g. create necessary buffers, etc) void PrepareVideoDumping() override; @@ -117,6 +127,11 @@ 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 From 91255b8802ac81ddd6012738e92a105865ba8464 Mon Sep 17 00:00:00 2001 From: James Rowe Date: Mon, 16 Sep 2019 20:38:48 -0600 Subject: [PATCH 04/28] Change Qt to use QOpenGLWidget and support shared context and texture mailbox --- src/citra_qt/bootmanager.cpp | 142 +++++++++++------------------------ src/citra_qt/bootmanager.h | 38 +++++++--- src/citra_qt/main.cpp | 23 ++++-- 3 files changed, 85 insertions(+), 118 deletions(-) diff --git a/src/citra_qt/bootmanager.cpp b/src/citra_qt/bootmanager.cpp index 400a0104c..3ccfbb19f 100644 --- a/src/citra_qt/bootmanager.cpp +++ b/src/citra_qt/bootmanager.cpp @@ -1,31 +1,33 @@ #include #include #include +#include +#include +#include #include #include #include - #include "citra_qt/bootmanager.h" #include "common/microprofile.h" #include "common/scm_rev.h" #include "core/3ds.h" #include "core/core.h" +#include "core/frontend/scope_acquire_context.h" #include "core/settings.h" #include "input_common/keyboard.h" #include "input_common/main.h" #include "input_common/motion_emu.h" #include "network/network.h" +#include "video_core/renderer_base.h" #include "video_core/video_core.h" -EmuThread::EmuThread(GRenderWindow* render_window) : render_window(render_window) {} +EmuThread::EmuThread(Frontend::GraphicsContext& core_context) : core_context(core_context) {} EmuThread::~EmuThread() = default; void EmuThread::run() { - render_window->MakeCurrent(); - MicroProfileOnThreadCreate("EmuThread"); - + Frontend::ScopeAcquireContext scope(core_context); // Holds whether the cpu was running during the last iteration, // so that the DebugModeLeft signal can be emitted before the // next execution step. @@ -72,43 +74,10 @@ void EmuThread::run() { #if MICROPROFILE_ENABLED MicroProfileOnThreadExit(); #endif - - render_window->moveContext(); } -// 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 : public QGLWidget { -public: - GGLWidgetInternal(QGLFormat fmt, GRenderWindow* parent) - : QGLWidget(fmt, parent), parent(parent) {} - - void paintEvent(QPaintEvent* ev) override { - if (do_painting) { - QPainter painter(this); - } - } - - void resizeEvent(QResizeEvent* ev) override { - parent->OnClientAreaResized(ev->size().width(), ev->size().height()); - parent->OnFramebufferSizeChanged(); - } - - void DisablePainting() { - do_painting = false; - } - void EnablePainting() { - do_painting = true; - } - -private: - GRenderWindow* parent; - bool do_painting; -}; - GRenderWindow::GRenderWindow(QWidget* parent, EmuThread* emu_thread) - : QWidget(parent), child(nullptr), emu_thread(emu_thread) { + : QOpenGLWidget(parent), emu_thread(emu_thread) { setWindowTitle(QStringLiteral("Citra %1 | %2-%3") .arg(Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc)); @@ -121,35 +90,12 @@ GRenderWindow::~GRenderWindow() { InputCommon::Shutdown(); } -void GRenderWindow::moveContext() { - 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(); - child->context()->moveToThread(thread); -} - -void GRenderWindow::SwapBuffers() { - // In our multi-threaded QGLWidget use case we shouldn't need to call `makeCurrent`, - // since we never call `doneCurrent` in this thread. - // However: - // - The Qt debug runtime prints a bogus warning on the console if `makeCurrent` wasn't called - // since the last time `swapBuffers` was executed; - // - On macOS, if `makeCurrent` isn't called explicitely, resizing the buffer breaks. - child->makeCurrent(); - - child->swapBuffers(); -} - void GRenderWindow::MakeCurrent() { - child->makeCurrent(); + core_context->MakeCurrent(); } void GRenderWindow::DoneCurrent() { - child->doneCurrent(); + core_context->DoneCurrent(); } void GRenderWindow::PollEvents() {} @@ -163,8 +109,8 @@ void GRenderWindow::OnFramebufferSizeChanged() { // Screen changes potentially incur a change in screen DPI, hence we should update the // framebuffer size const qreal pixel_ratio = windowPixelRatio(); - const u32 width = child->QPaintDevice::width() * pixel_ratio; - const u32 height = child->QPaintDevice::height() * pixel_ratio; + const u32 width = this->width() * pixel_ratio; + const u32 height = this->height() * pixel_ratio; UpdateCurrentFramebufferLayout(width, height); } @@ -298,42 +244,16 @@ 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) { + QOpenGLWidget::resizeEvent(event); + NotifyClientAreaSizeChanged(std::make_pair(event->size().width(), event->size().height())); + OnFramebufferSizeChanged(); } void GRenderWindow::InitRenderTarget() { - if (child) { - delete child; - } - - if (layout()) { - delete layout(); - } - // TODO: One of these flags might be interesting: WA_OpaquePaintEvent, WA_NoBackground, // WA_DontShowOnScreen, WA_DeleteOnClose - QGLFormat fmt; - fmt.setVersion(3, 3); - fmt.setProfile(QGLFormat::CoreProfile); - fmt.setSwapInterval(Settings::values.vsync_enabled); - - // Requests a forward-compatible context, which is required to get a 3.2+ context on OS X - fmt.setOption(QGL::NoDeprecatedFunctions); - - child = new GGLWidgetInternal(fmt, this); - QBoxLayout* layout = new QHBoxLayout(this); - - resize(Core::kScreenTopWidth, Core::kScreenTopHeight + Core::kScreenBottomHeight); - layout->addWidget(child); - layout->setMargin(0); - setLayout(layout); - - OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size); - - OnFramebufferSizeChanged(); - NotifyClientAreaSizeChanged(std::pair(child->width(), child->height())); - + core_context = CreateSharedContext(); BackupGeometry(); } @@ -361,12 +281,15 @@ void GRenderWindow::OnMinimalClientAreaChangeRequest(std::pair minimal void GRenderWindow::OnEmulationStarting(EmuThread* emu_thread) { this->emu_thread = emu_thread; - child->DisablePainting(); } void GRenderWindow::OnEmulationStopping() { emu_thread = nullptr; - child->EnablePainting(); +} + +void GRenderWindow::paintGL() { + VideoCore::g_renderer->Present(); + update(); } void GRenderWindow::showEvent(QShowEvent* event) { @@ -376,3 +299,24 @@ void GRenderWindow::showEvent(QShowEvent* event) { connect(windowHandle(), &QWindow::screenChanged, this, &GRenderWindow::OnFramebufferSizeChanged, Qt::UniqueConnection); } + +std::unique_ptr GRenderWindow::CreateSharedContext() const { + return std::make_unique(QOpenGLContext::globalShareContext()); +} + +GGLContext::GGLContext(QOpenGLContext* shared_context) + : context(new QOpenGLContext(shared_context->parent())), + surface(new QOffscreenSurface(nullptr, shared_context->parent())) { + context->setShareContext(shared_context); + context->create(); + surface->setFormat(shared_context->format()); + surface->create(); +} + +void GGLContext::MakeCurrent() { + context->makeCurrent(surface); +} + +void GGLContext::DoneCurrent() { + context->doneCurrent(); +} diff --git a/src/citra_qt/bootmanager.h b/src/citra_qt/bootmanager.h index b36d45868..4edb96d72 100644 --- a/src/citra_qt/bootmanager.h +++ b/src/citra_qt/bootmanager.h @@ -7,8 +7,7 @@ #include #include #include -#include -#include +#include #include #include "common/thread.h" #include "core/core.h" @@ -17,16 +16,30 @@ class QKeyEvent; class QScreen; class QTouchEvent; +class QOffscreenSurface; +class QOpenGLContext; -class GGLWidgetInternal; class GMainWindow; class GRenderWindow; +class GGLContext : public Frontend::GraphicsContext { +public: + explicit GGLContext(QOpenGLContext* shared_context); + + void MakeCurrent() override; + + void DoneCurrent() override; + +private: + QOpenGLContext* context; + QOffscreenSurface* surface; +}; + class EmuThread final : public QThread { Q_OBJECT public: - explicit EmuThread(GRenderWindow* render_window); + explicit EmuThread(Frontend::GraphicsContext& context); ~EmuThread() override; /** @@ -80,7 +93,7 @@ private: std::mutex running_mutex; std::condition_variable running_cv; - GRenderWindow* render_window; + Frontend::GraphicsContext& core_context; signals: /** @@ -104,18 +117,20 @@ signals: void ErrorThrown(Core::System::ResultStatus, std::string); }; -class GRenderWindow : public QWidget, public Frontend::EmuWindow { +class GRenderWindow : public QOpenGLWidget, public Frontend::EmuWindow { Q_OBJECT public: GRenderWindow(QWidget* parent, EmuThread* emu_thread); ~GRenderWindow() override; - // EmuWindow implementation - void SwapBuffers() override; + // EmuWindow implementation. void MakeCurrent() override; void DoneCurrent() override; void PollEvents() override; + std::unique_ptr CreateSharedContext() const override; + + void paintGL() override; void BackupGeometry(); void RestoreGeometry(); @@ -126,6 +141,8 @@ public: void closeEvent(QCloseEvent* event) override; + void resizeEvent(QResizeEvent* event) override; + void keyPressEvent(QKeyEvent* event) override; void keyReleaseEvent(QKeyEvent* event) override; @@ -137,14 +154,11 @@ public: void focusOutEvent(QFocusEvent* event) override; - void OnClientAreaResized(u32 width, u32 height); - void InitRenderTarget(); void CaptureScreenshot(u32 res_scale, const QString& screenshot_path); public slots: - void moveContext(); // overridden void OnEmulationStarting(EmuThread* emu_thread); void OnEmulationStopping(); @@ -162,7 +176,7 @@ private: void OnMinimalClientAreaChangeRequest(std::pair minimal_size) override; - GGLWidgetInternal* child; + std::unique_ptr core_context; QByteArray geometry; diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp index 11da2be20..9d752d3ce 100644 --- a/src/citra_qt/main.cpp +++ b/src/citra_qt/main.cpp @@ -5,12 +5,11 @@ #include #include #include -#include -#define QT_NO_OPENGL #include #include #include #include +#include #include #include #include @@ -72,6 +71,7 @@ #include "core/file_sys/archive_extsavedata.h" #include "core/file_sys/archive_source_sd_savedata.h" #include "core/frontend/applets/default_applets.h" +#include "core/frontend/scope_acquire_context.h" #include "core/gdbstub/gdbstub.h" #include "core/hle/service/fs/archive.h" #include "core/hle/service/nfc/nfc.h" @@ -768,13 +768,14 @@ bool GMainWindow::LoadROM(const QString& filename) { ShutdownGame(); render_window->InitRenderTarget(); - render_window->MakeCurrent(); + + Frontend::ScopeAcquireContext scope(*render_window); const QString below_gl33_title = tr("OpenGL 3.3 Unsupported"); const QString below_gl33_message = tr("Your GPU may not support OpenGL 3.3, or you do not " "have the latest graphics driver."); - if (!gladLoadGL()) { + if (!QOpenGLContext::globalShareContext()->versionFunctions()) { QMessageBox::critical(this, below_gl33_title, below_gl33_message); return false; } @@ -893,9 +894,8 @@ void GMainWindow::BootGame(const QString& filename) { return; // Create and start the emulation thread - emu_thread = std::make_unique(render_window); + emu_thread = std::make_unique(*render_window); emit EmulationStarting(emu_thread.get()); - render_window->moveContext(); emu_thread->start(); connect(render_window, &GRenderWindow::Closed, this, &GMainWindow::OnStopGame); @@ -2050,11 +2050,20 @@ int main(int argc, char* argv[]) { QCoreApplication::setOrganizationName("Citra team"); QCoreApplication::setApplicationName("Citra"); + QSurfaceFormat format; + format.setVersion(3, 3); + format.setProfile(QSurfaceFormat::CoreProfile); + format.setSwapInterval(1); + // TODO: expose a setting for buffer value (ie default/single/double/triple) + format.setSwapBehavior(QSurfaceFormat::DefaultSwapBehavior); + QSurfaceFormat::setDefaultFormat(format); + #ifdef __APPLE__ std::string bin_path = FileUtil::GetBundleDirectory() + DIR_SEP + ".."; chdir(bin_path.c_str()); #endif - + 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 From fcbe5e1acd32b3cbc5cbcad84cb99eae5c53b367 Mon Sep 17 00:00:00 2001 From: James Rowe Date: Mon, 16 Sep 2019 20:39:19 -0600 Subject: [PATCH 05/28] Add a separate thread for rendering and add texture mailbox and shared context to SDL --- src/citra/citra.cpp | 5 +- src/citra/emu_window/emu_window_sdl2.cpp | 61 ++++++++++++++++++++---- src/citra/emu_window/emu_window_sdl2.h | 32 +++++++++++-- 3 files changed, 84 insertions(+), 14 deletions(-) diff --git a/src/citra/citra.cpp b/src/citra/citra.cpp index 294ba7b88..debc54b1c 100644 --- a/src/citra/citra.cpp +++ b/src/citra/citra.cpp @@ -35,6 +35,7 @@ #include "core/file_sys/cia_container.h" #include "core/frontend/applets/default_applets.h" #include "core/frontend/framebuffer_layout.h" +#include "core/frontend/scope_acquire_context.h" #include "core/gdbstub/gdbstub.h" #include "core/hle/service/am/am.h" #include "core/hle/service/cfg/cfg.h" @@ -347,7 +348,7 @@ int main(int argc, char** argv) { Core::System::GetInstance().RegisterImageInterface(std::make_shared()); std::unique_ptr emu_window{std::make_unique(fullscreen)}; - + Frontend::ScopeAcquireContext scope(*emu_window); Core::System& system{Core::System::GetInstance()}; const Core::System::ResultStatus load_result{system.Load(*emu_window, filepath)}; @@ -411,9 +412,11 @@ int main(int argc, char** argv) { system.VideoDumper().StartDumping(dump_video, "webm", layout); } + std::thread render_thread([&emu_window] { emu_window->Present(); }); while (emu_window->IsOpen()) { system.RunLoop(); } + render_thread.join(); Core::Movie::GetInstance().Shutdown(); if (system.VideoDumper().IsDumping()) { diff --git a/src/citra/emu_window/emu_window_sdl2.cpp b/src/citra/emu_window/emu_window_sdl2.cpp index 34d3d2fdb..851303987 100644 --- a/src/citra/emu_window/emu_window_sdl2.cpp +++ b/src/citra/emu_window/emu_window_sdl2.cpp @@ -20,6 +20,28 @@ #include "input_common/motion_emu.h" #include "input_common/sdl/sdl.h" #include "network/network.h" +#include "video_core/renderer_base.h" +#include "video_core/video_core.h" + +SharedContext_SDL2::SharedContext_SDL2() { + window = SDL_CreateWindow(NULL, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 0, 0, + SDL_WINDOW_HIDDEN | SDL_WINDOW_OPENGL); + context = SDL_GL_CreateContext(window); +} + +SharedContext_SDL2::~SharedContext_SDL2() { + DoneCurrent(); + SDL_GL_DeleteContext(context); + SDL_DestroyWindow(window); +} + +void SharedContext_SDL2::MakeCurrent() { + SDL_GL_MakeCurrent(window, context); +} + +void SharedContext_SDL2::DoneCurrent() { + SDL_GL_MakeCurrent(window, nullptr); +} void EmuWindow_SDL2::OnMouseMotion(s32 x, s32 y) { TouchMoved((unsigned)std::max(x, 0), (unsigned)std::max(y, 0)); @@ -135,6 +157,10 @@ EmuWindow_SDL2::EmuWindow_SDL2(bool fullscreen) { SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 0); + // Enable context sharing for the shared context + SDL_GL_SetAttribute(SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 1); + // Enable vsync + SDL_GL_SetSwapInterval(1); std::string window_title = fmt::format("Citra {} | {}-{}", Common::g_build_fullname, Common::g_scm_branch, Common::g_scm_desc); @@ -150,16 +176,24 @@ EmuWindow_SDL2::EmuWindow_SDL2(bool fullscreen) { 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); + window_context = SDL_GL_CreateContext(render_window); + core_context = CreateSharedContext(); - if (gl_context == nullptr) { + 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); + } auto gl_load_func = Settings::values.use_gles ? gladLoadGLES2Loader : gladLoadGLLoader; @@ -171,23 +205,30 @@ EmuWindow_SDL2::EmuWindow_SDL2(bool fullscreen) { OnResize(); OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size); SDL_PumpEvents(); - SDL_GL_SetSwapInterval(Settings::values.vsync_enabled); LOG_INFO(Frontend, "Citra Version: {} | {}-{}", Common::g_build_fullname, Common::g_scm_branch, Common::g_scm_desc); Settings::LogSettings(); - - DoneCurrent(); } EmuWindow_SDL2::~EmuWindow_SDL2() { Network::Shutdown(); InputCommon::Shutdown(); - SDL_GL_DeleteContext(gl_context); + SDL_GL_DeleteContext(window_context); SDL_Quit(); } -void EmuWindow_SDL2::SwapBuffers() { - SDL_GL_SwapWindow(render_window); +std::unique_ptr EmuWindow_SDL2::CreateSharedContext() const { + return std::make_unique(); +} + +void EmuWindow_SDL2::Present() { + SDL_GL_MakeCurrent(render_window, window_context); + SDL_GL_SetSwapInterval(1); + while (IsOpen()) { + VideoCore::g_renderer->Present(); + SDL_GL_SwapWindow(render_window); + } + SDL_GL_MakeCurrent(render_window, nullptr); } void EmuWindow_SDL2::PollEvents() { @@ -256,11 +297,11 @@ void EmuWindow_SDL2::PollEvents() { } void EmuWindow_SDL2::MakeCurrent() { - SDL_GL_MakeCurrent(render_window, gl_context); + core_context->MakeCurrent(); } void EmuWindow_SDL2::DoneCurrent() { - SDL_GL_MakeCurrent(render_window, nullptr); + core_context->DoneCurrent(); } void EmuWindow_SDL2::OnMinimalClientAreaChangeRequest(std::pair minimal_size) { diff --git a/src/citra/emu_window/emu_window_sdl2.h b/src/citra/emu_window/emu_window_sdl2.h index 3462c5bd9..b0834fd06 100644 --- a/src/citra/emu_window/emu_window_sdl2.h +++ b/src/citra/emu_window/emu_window_sdl2.h @@ -10,13 +10,29 @@ struct SDL_Window; +class SharedContext_SDL2 : public Frontend::GraphicsContext { +public: + using SDL_GLContext = void*; + + SharedContext_SDL2(); + + ~SharedContext_SDL2() override; + + void MakeCurrent() override; + + void DoneCurrent() override; + +private: + SDL_GLContext context; + SDL_Window* window; +}; + class EmuWindow_SDL2 : public Frontend::EmuWindow { public: explicit EmuWindow_SDL2(bool fullscreen); ~EmuWindow_SDL2(); - /// Swap buffers to display the next frame - void SwapBuffers() override; + void Present(); /// Polls window events void PollEvents() override; @@ -30,6 +46,9 @@ public: /// Whether the window is still open, and a close request hasn't yet been sent bool IsOpen() const; + /// Creates a new context that is shared with the current context + std::unique_ptr CreateSharedContext() const override; + private: /// Called by PollEvents when a key is pressed or released. void OnKeyEvent(int key, u8 state); @@ -67,9 +86,16 @@ private: /// Internal SDL2 render window SDL_Window* render_window; + /// Fake hidden window for the core context + SDL_Window* dummy_window; + 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; /// Keeps track of how often to update the title bar during gameplay u32 last_time = 0; From 8d17aa40fdb4c7f25e0ba96e90a682270515cc57 Mon Sep 17 00:00:00 2001 From: James Rowe Date: Mon, 16 Sep 2019 20:45:47 -0600 Subject: [PATCH 06/28] Remove vsync setting --- src/citra/config.cpp | 1 - src/citra/default_ini.h | 4 ---- src/citra_qt/configuration/config.cpp | 2 -- src/core/settings.cpp | 1 - src/core/settings.h | 1 - src/core/telemetry_session.cpp | 1 - 6 files changed, 10 deletions(-) diff --git a/src/citra/config.cpp b/src/citra/config.cpp index 1b18f4536..297465f92 100644 --- a/src/citra/config.cpp +++ b/src/citra/config.cpp @@ -120,7 +120,6 @@ void Config::ReadValues() { Settings::values.use_shader_jit = sdl2_config->GetBoolean("Renderer", "use_shader_jit", true); Settings::values.resolution_factor = static_cast(sdl2_config->GetInteger("Renderer", "resolution_factor", 1)); - Settings::values.vsync_enabled = sdl2_config->GetBoolean("Renderer", "vsync_enabled", false); Settings::values.use_frame_limit = sdl2_config->GetBoolean("Renderer", "use_frame_limit", true); Settings::values.frame_limit = static_cast(sdl2_config->GetInteger("Renderer", "frame_limit", 100)); diff --git a/src/citra/default_ini.h b/src/citra/default_ini.h index 2b6aa2147..2ca725438 100644 --- a/src/citra/default_ini.h +++ b/src/citra/default_ini.h @@ -117,10 +117,6 @@ use_shader_jit = # factor for the 3DS resolution resolution_factor = -# Whether to enable V-Sync (caps the framerate at 60FPS) or not. -# 0 (default): Off, 1: On -vsync_enabled = - # Turns on the frame limiter, which will limit frames output to the target game speed # 0: Off, 1: On (default) use_frame_limit = diff --git a/src/citra_qt/configuration/config.cpp b/src/citra_qt/configuration/config.cpp index feed246b4..26034dbe6 100644 --- a/src/citra_qt/configuration/config.cpp +++ b/src/citra_qt/configuration/config.cpp @@ -430,7 +430,6 @@ void Config::ReadRendererValues() { Settings::values.use_shader_jit = ReadSetting(QStringLiteral("use_shader_jit"), true).toBool(); Settings::values.resolution_factor = static_cast(ReadSetting(QStringLiteral("resolution_factor"), 1).toInt()); - Settings::values.vsync_enabled = ReadSetting(QStringLiteral("vsync_enabled"), false).toBool(); Settings::values.use_frame_limit = ReadSetting(QStringLiteral("use_frame_limit"), true).toBool(); Settings::values.frame_limit = ReadSetting(QStringLiteral("frame_limit"), 100).toInt(); @@ -857,7 +856,6 @@ void Config::SaveRendererValues() { false); WriteSetting(QStringLiteral("use_shader_jit"), Settings::values.use_shader_jit, true); WriteSetting(QStringLiteral("resolution_factor"), Settings::values.resolution_factor, 1); - WriteSetting(QStringLiteral("vsync_enabled"), Settings::values.vsync_enabled, false); WriteSetting(QStringLiteral("use_frame_limit"), Settings::values.use_frame_limit, true); WriteSetting(QStringLiteral("frame_limit"), Settings::values.frame_limit, 100); diff --git a/src/core/settings.cpp b/src/core/settings.cpp index 079d276d0..40cec0029 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -78,7 +78,6 @@ void LogSettings() { LogSetting("Renderer_ShadersAccurateMul", Settings::values.shaders_accurate_mul); LogSetting("Renderer_UseShaderJit", Settings::values.use_shader_jit); LogSetting("Renderer_UseResolutionFactor", Settings::values.resolution_factor); - LogSetting("Renderer_VsyncEnabled", Settings::values.vsync_enabled); LogSetting("Renderer_UseFrameLimit", Settings::values.use_frame_limit); LogSetting("Renderer_FrameLimit", Settings::values.frame_limit); LogSetting("Renderer_PostProcessingShader", Settings::values.pp_shader_name); diff --git a/src/core/settings.h b/src/core/settings.h index 213cc3ec3..0d391afba 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -144,7 +144,6 @@ struct Values { bool shaders_accurate_mul; bool use_shader_jit; u16 resolution_factor; - bool vsync_enabled; bool use_frame_limit; u16 frame_limit; diff --git a/src/core/telemetry_session.cpp b/src/core/telemetry_session.cpp index 274257eab..e9ec39b76 100644 --- a/src/core/telemetry_session.cpp +++ b/src/core/telemetry_session.cpp @@ -192,7 +192,6 @@ void TelemetrySession::AddInitialInfo(Loader::AppLoader& app_loader) { Settings::values.shaders_accurate_mul); AddField(Telemetry::FieldType::UserConfig, "Renderer_UseShaderJit", Settings::values.use_shader_jit); - AddField(Telemetry::FieldType::UserConfig, "Renderer_UseVsync", Settings::values.vsync_enabled); AddField(Telemetry::FieldType::UserConfig, "Renderer_FilterMode", Settings::values.filter_mode); AddField(Telemetry::FieldType::UserConfig, "Renderer_Render3d", static_cast(Settings::values.render_3d)); From 3b14bb44b96189637117f8283baa86057997dee2 Mon Sep 17 00:00:00 2001 From: James Rowe Date: Mon, 16 Sep 2019 23:06:28 -0600 Subject: [PATCH 07/28] QOffscreensurface --- src/citra_qt/bootmanager.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/citra_qt/bootmanager.cpp b/src/citra_qt/bootmanager.cpp index 3ccfbb19f..08cbc5812 100644 --- a/src/citra_qt/bootmanager.cpp +++ b/src/citra_qt/bootmanager.cpp @@ -1,7 +1,11 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + #include #include #include -#include +#include #include #include #include From db94c017bfca88a3f1a7dd9c39a38b53aa9de3e5 Mon Sep 17 00:00:00 2001 From: James Rowe Date: Mon, 16 Sep 2019 23:28:41 -0600 Subject: [PATCH 08/28] Change to a constructor that exists in qt 5.9 --- src/citra_qt/bootmanager.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/citra_qt/bootmanager.cpp b/src/citra_qt/bootmanager.cpp index 08cbc5812..c51c828ab 100644 --- a/src/citra_qt/bootmanager.cpp +++ b/src/citra_qt/bootmanager.cpp @@ -310,9 +310,10 @@ std::unique_ptr GRenderWindow::CreateSharedContext() GGLContext::GGLContext(QOpenGLContext* shared_context) : context(new QOpenGLContext(shared_context->parent())), - surface(new QOffscreenSurface(nullptr, shared_context->parent())) { + surface(new QOffscreenSurface(nullptr)) { context->setShareContext(shared_context); context->create(); + surface->setParent(shared_context->parent()); surface->setFormat(shared_context->format()); surface->create(); } From 045eec282aea0269074a7cf95520fa4e9c18e195 Mon Sep 17 00:00:00 2001 From: James Rowe Date: Mon, 16 Sep 2019 23:29:21 -0600 Subject: [PATCH 09/28] rename GGLContext to just GLContext --- src/citra_qt/bootmanager.cpp | 8 ++++---- src/citra_qt/bootmanager.h | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/citra_qt/bootmanager.cpp b/src/citra_qt/bootmanager.cpp index c51c828ab..bfbd465f6 100644 --- a/src/citra_qt/bootmanager.cpp +++ b/src/citra_qt/bootmanager.cpp @@ -305,10 +305,10 @@ void GRenderWindow::showEvent(QShowEvent* event) { } std::unique_ptr GRenderWindow::CreateSharedContext() const { - return std::make_unique(QOpenGLContext::globalShareContext()); + return std::make_unique(QOpenGLContext::globalShareContext()); } -GGLContext::GGLContext(QOpenGLContext* shared_context) +GLContext::GLContext(QOpenGLContext* shared_context) : context(new QOpenGLContext(shared_context->parent())), surface(new QOffscreenSurface(nullptr)) { context->setShareContext(shared_context); @@ -318,10 +318,10 @@ GGLContext::GGLContext(QOpenGLContext* shared_context) surface->create(); } -void GGLContext::MakeCurrent() { +void GLContext::MakeCurrent() { context->makeCurrent(surface); } -void GGLContext::DoneCurrent() { +void GLContext::DoneCurrent() { context->doneCurrent(); } diff --git a/src/citra_qt/bootmanager.h b/src/citra_qt/bootmanager.h index 4edb96d72..bd01d7838 100644 --- a/src/citra_qt/bootmanager.h +++ b/src/citra_qt/bootmanager.h @@ -22,9 +22,9 @@ class QOpenGLContext; class GMainWindow; class GRenderWindow; -class GGLContext : public Frontend::GraphicsContext { +class GLContext : public Frontend::GraphicsContext { public: - explicit GGLContext(QOpenGLContext* shared_context); + explicit GLContext(QOpenGLContext* shared_context); void MakeCurrent() override; From 5d97e4427953a95893110ffc0c75281cc75ebde8 Mon Sep 17 00:00:00 2001 From: James Rowe Date: Wed, 18 Sep 2019 10:14:35 -0600 Subject: [PATCH 10/28] Fix macOS pixel ratio detection --- src/citra_qt/bootmanager.cpp | 4 ++-- src/citra_qt/main.cpp | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/citra_qt/bootmanager.cpp b/src/citra_qt/bootmanager.cpp index bfbd465f6..91d84568f 100644 --- a/src/citra_qt/bootmanager.cpp +++ b/src/citra_qt/bootmanager.cpp @@ -144,8 +144,8 @@ QByteArray GRenderWindow::saveGeometry() { } qreal GRenderWindow::windowPixelRatio() const { - // windowHandle() might not be accessible until the window is displayed to screen. - return windowHandle() ? windowHandle()->screen()->devicePixelRatio() : 1.0f; + // QOpenGLWidget isn't backed by a native window, so we need to use the top level window instead + return QApplication::activeWindow()->devicePixelRatio(); } std::pair GRenderWindow::ScaleTouch(const QPointF pos) const { diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp index 9d752d3ce..52c1ba436 100644 --- a/src/citra_qt/main.cpp +++ b/src/citra_qt/main.cpp @@ -2062,6 +2062,7 @@ int main(int argc, char* argv[]) { std::string bin_path = FileUtil::GetBundleDirectory() + DIR_SEP + ".."; chdir(bin_path.c_str()); #endif + QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QCoreApplication::setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity); QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts); QApplication app(argc, argv); From 6fff8e3921c9ae88a7e48ec58a793601374db7f8 Mon Sep 17 00:00:00 2001 From: James Rowe Date: Wed, 18 Sep 2019 10:40:48 -0600 Subject: [PATCH 11/28] Remove screen change detection since it breaks make changing screen. (Still need to test windows) --- src/citra_qt/bootmanager.cpp | 8 +------- src/core/frontend/emu_window.h | 12 ------------ 2 files changed, 1 insertion(+), 19 deletions(-) diff --git a/src/citra_qt/bootmanager.cpp b/src/citra_qt/bootmanager.cpp index 91d84568f..d8b5db284 100644 --- a/src/citra_qt/bootmanager.cpp +++ b/src/citra_qt/bootmanager.cpp @@ -144,8 +144,7 @@ QByteArray GRenderWindow::saveGeometry() { } qreal GRenderWindow::windowPixelRatio() const { - // QOpenGLWidget isn't backed by a native window, so we need to use the top level window instead - return QApplication::activeWindow()->devicePixelRatio(); + return devicePixelRatio(); } std::pair GRenderWindow::ScaleTouch(const QPointF pos) const { @@ -250,7 +249,6 @@ void GRenderWindow::focusOutEvent(QFocusEvent* event) { void GRenderWindow::resizeEvent(QResizeEvent* event) { QOpenGLWidget::resizeEvent(event); - NotifyClientAreaSizeChanged(std::make_pair(event->size().width(), event->size().height())); OnFramebufferSizeChanged(); } @@ -298,10 +296,6 @@ void GRenderWindow::paintGL() { void GRenderWindow::showEvent(QShowEvent* event) { QWidget::showEvent(event); - - // windowHandle() is not initialized until the Window is shown, so we connect it here. - connect(windowHandle(), &QWindow::screenChanged, this, &GRenderWindow::OnFramebufferSizeChanged, - Qt::UniqueConnection); } std::unique_ptr GRenderWindow::CreateSharedContext() const { diff --git a/src/core/frontend/emu_window.h b/src/core/frontend/emu_window.h index be868f506..7d61da46d 100644 --- a/src/core/frontend/emu_window.h +++ b/src/core/frontend/emu_window.h @@ -185,15 +185,6 @@ protected: framebuffer_layout = layout; } - /** - * Update internal client area size with the given parameter. - * @note EmuWindow implementations will usually use this in window resize event handlers. - */ - void NotifyClientAreaSizeChanged(const std::pair& size) { - client_area_width = size.first; - client_area_height = size.second; - } - private: /** * Handler called when the minimal client area was requested to be changed via SetConfig. @@ -206,9 +197,6 @@ private: Layout::FramebufferLayout framebuffer_layout; ///< Current framebuffer layout - unsigned client_area_width; ///< Current client width, should be set by window impl. - unsigned client_area_height; ///< Current client height, should be set by window impl. - WindowConfig config; ///< Internal configuration (changes pending for being applied in /// ProcessConfigurationChanges) WindowConfig active_config; ///< Internal active configuration From ac90cd0378af7e8d803193ee59271043e7bbcca6 Mon Sep 17 00:00:00 2001 From: James Rowe Date: Wed, 18 Sep 2019 10:54:39 -0600 Subject: [PATCH 12/28] Change Present to finish only after swap --- src/citra/emu_window/emu_window_sdl2.cpp | 1 + src/citra_qt/bootmanager.cpp | 10 +++++++++- src/citra_qt/bootmanager.h | 1 + src/video_core/renderer_base.h | 3 +++ src/video_core/renderer_opengl/renderer_opengl.cpp | 3 +++ src/video_core/renderer_opengl/renderer_opengl.h | 3 +++ 6 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/citra/emu_window/emu_window_sdl2.cpp b/src/citra/emu_window/emu_window_sdl2.cpp index 851303987..5586cda29 100644 --- a/src/citra/emu_window/emu_window_sdl2.cpp +++ b/src/citra/emu_window/emu_window_sdl2.cpp @@ -227,6 +227,7 @@ void EmuWindow_SDL2::Present() { while (IsOpen()) { VideoCore::g_renderer->Present(); SDL_GL_SwapWindow(render_window); + VideoCore::g_renderer->PresentComplete(); } SDL_GL_MakeCurrent(render_window, nullptr); } diff --git a/src/citra_qt/bootmanager.cpp b/src/citra_qt/bootmanager.cpp index d8b5db284..9d732d463 100644 --- a/src/citra_qt/bootmanager.cpp +++ b/src/citra_qt/bootmanager.cpp @@ -86,7 +86,7 @@ GRenderWindow::GRenderWindow(QWidget* parent, EmuThread* emu_thread) setWindowTitle(QStringLiteral("Citra %1 | %2-%3") .arg(Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc)); setAttribute(Qt::WA_AcceptTouchEvents); - + connect(this, &QOpenGLWidget::frameSwapped, this, &GRenderWindow::OnFrameSwapped); InputCommon::Init(); } @@ -104,6 +104,10 @@ void GRenderWindow::DoneCurrent() { void GRenderWindow::PollEvents() {} +void OnFrameSwapped() { + VideoCore::g_renderer->PresentComplete(); +} + // On Qt 5.0+, this correctly gets the size of the framebuffer (pixels). // // Older versions get the window size (density independent pixels), @@ -294,6 +298,10 @@ void GRenderWindow::paintGL() { update(); } +void GRenderWindow::OnFrameSwapped() { + VideoCore::g_renderer->PresentComplete(); +} + void GRenderWindow::showEvent(QShowEvent* event) { QWidget::showEvent(event); } diff --git a/src/citra_qt/bootmanager.h b/src/citra_qt/bootmanager.h index bd01d7838..51188b209 100644 --- a/src/citra_qt/bootmanager.h +++ b/src/citra_qt/bootmanager.h @@ -163,6 +163,7 @@ public slots: void OnEmulationStarting(EmuThread* emu_thread); void OnEmulationStopping(); void OnFramebufferSizeChanged(); + void OnFrameSwapped(); signals: /// Emitted when the window is closed diff --git a/src/video_core/renderer_base.h b/src/video_core/renderer_base.h index 871ff40cb..1558ba173 100644 --- a/src/video_core/renderer_base.h +++ b/src/video_core/renderer_base.h @@ -34,6 +34,9 @@ public: /// Draws the latest frame to the window (Renderer specific implementation) virtual void Present() = 0; + /// Marks the presentation buffer as complete and swaps it back into the pool + virtual void PresentComplete() = 0; + /// Prepares for video dumping (e.g. create necessary buffers, etc) virtual void PrepareVideoDumping() = 0; diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index 34d64c757..08065ea0b 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp @@ -794,6 +794,9 @@ void RendererOpenGL::Present() { /* insert fence for the main thread to block on */ frame.present_sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); glFlush(); +} + +void RendererOpenGL::PresentComplete() { render_window.mailbox->PresentationComplete(); } diff --git a/src/video_core/renderer_opengl/renderer_opengl.h b/src/video_core/renderer_opengl/renderer_opengl.h index 1d3345f22..d8ed30d73 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.h +++ b/src/video_core/renderer_opengl/renderer_opengl.h @@ -60,6 +60,9 @@ public: /// context void Present() override; + /// Finializes the presentation and sets up the presentation frame to go back into the mailbox + void PresentComplete() override; + /// Prepares for video dumping (e.g. create necessary buffers, etc) void PrepareVideoDumping() override; From 52d7676831d40a0e205af8628efe4f0460710b74 Mon Sep 17 00:00:00 2001 From: James Rowe Date: Thu, 19 Sep 2019 01:06:28 -0600 Subject: [PATCH 13/28] recreate mailbox to use a queue instead --- src/citra/emu_window/emu_window_sdl2.cpp | 2 +- src/citra_qt/bootmanager.cpp | 7 +- src/citra_qt/bootmanager.h | 1 + src/core/frontend/emu_window.h | 27 +- src/video_core/renderer_base.h | 5 +- .../renderer_opengl/renderer_opengl.cpp | 237 +++++++++++------- .../renderer_opengl/renderer_opengl.h | 7 +- 7 files changed, 183 insertions(+), 103 deletions(-) diff --git a/src/citra/emu_window/emu_window_sdl2.cpp b/src/citra/emu_window/emu_window_sdl2.cpp index 5586cda29..3e4a45671 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 9d732d463..27fa70153 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 51188b209..856274878 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 7d61da46d..164af9197 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 1558ba173..0252e2dfe 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 08065ea0b..1452cd6de 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 d8ed30d73..16c76f0a3 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 From 9c32c0b98b649c8d9ca7acd146ccca075afa0c13 Mon Sep 17 00:00:00 2001 From: James Rowe Date: Fri, 20 Sep 2019 18:27:42 -0600 Subject: [PATCH 14/28] Change from render to texture to render to renderbuffer --- src/core/frontend/emu_window.h | 8 +-- .../renderer_opengl/gl_resource_manager.cpp | 17 ++++++ .../renderer_opengl/gl_resource_manager.h | 25 +++++++++ src/video_core/renderer_opengl/gl_state.cpp | 6 ++ src/video_core/renderer_opengl/gl_state.h | 2 + .../renderer_opengl/renderer_opengl.cpp | 56 ++++++++++--------- 6 files changed, 83 insertions(+), 31 deletions(-) diff --git a/src/core/frontend/emu_window.h b/src/core/frontend/emu_window.h index 164af9197..6d58bcdda 100644 --- a/src/core/frontend/emu_window.h +++ b/src/core/frontend/emu_window.h @@ -45,14 +45,10 @@ public: /** * Presentation thread calls this to get the latest frame available to present. If there is no - * frame available after timeout, returns nullptr + * 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; - - /** - * 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_opengl/gl_resource_manager.cpp b/src/video_core/renderer_opengl/gl_resource_manager.cpp index 1cffc8ea7..33552c6bd 100644 --- a/src/video_core/renderer_opengl/gl_resource_manager.cpp +++ b/src/video_core/renderer_opengl/gl_resource_manager.cpp @@ -15,6 +15,23 @@ 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); + handle = 0; +} + void OGLTexture::Create() { if (handle != 0) return; diff --git a/src/video_core/renderer_opengl/gl_resource_manager.h b/src/video_core/renderer_opengl/gl_resource_manager.h index ce7f7fbfb..7f94c8a39 100644 --- a/src/video_core/renderer_opengl/gl_resource_manager.h +++ b/src/video_core/renderer_opengl/gl_resource_manager.h @@ -12,6 +12,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; diff --git a/src/video_core/renderer_opengl/gl_state.cpp b/src/video_core/renderer_opengl/gl_state.cpp index 17bf2b2f7..83e6f1678 100644 --- a/src/video_core/renderer_opengl/gl_state.cpp +++ b/src/video_core/renderer_opengl/gl_state.cpp @@ -89,6 +89,8 @@ OpenGLState::OpenGLState() { viewport.height = 0; clip_distance = {}; + + renderbuffer = 0; } void OpenGLState::Apply() const { @@ -337,6 +339,10 @@ void OpenGLState::Apply() const { } } + if (renderbuffer != cur_state.renderbuffer) { + glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer); + } + cur_state = *this; } diff --git a/src/video_core/renderer_opengl/gl_state.h b/src/video_core/renderer_opengl/gl_state.h index b1842a820..e2ccc9f51 100644 --- a/src/video_core/renderer_opengl/gl_state.h +++ b/src/video_core/renderer_opengl/gl_state.h @@ -144,6 +144,8 @@ public: std::array clip_distance; // GL_CLIP_DISTANCE + GLuint renderbuffer; + OpenGLState(); /// Get the currently active OpenGL state diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index 1452cd6de..555f80130 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp @@ -38,7 +38,7 @@ 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::OGLTexture color{}; /// Texture shared between the render/present FBO + 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 @@ -48,7 +48,10 @@ struct Frame { namespace OpenGL { -constexpr std::size_t SWAP_CHAIN_SIZE = 5; +// 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 8 seems to be a good trade off for now +constexpr std::size_t SWAP_CHAIN_SIZE = 8; class OGLTextureMailbox : public Frontend::TextureMailbox { public: @@ -58,6 +61,7 @@ public: std::array swap_chain{}; std::deque free_queue{}; std::deque present_queue{}; + Frontend::Frame* previous_frame = nullptr; OGLTextureMailbox() { for (auto& frame : swap_chain) { @@ -65,7 +69,10 @@ public: } } - ~OGLTextureMailbox() override = default; + ~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 + } void ReloadPresentFrame(Frontend::Frame* frame, u32 height, u32 width) override { frame->present.Release(); @@ -73,12 +80,12 @@ public: 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); + 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_FRAMEBUFFER, previous_draw_fbo); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, previous_draw_fbo); frame->color_reloaded = false; } @@ -89,14 +96,9 @@ public: // Recreate the color texture attachment frame->color.Release(); frame->color.Create(); - state.texture_units[0].texture_2d = frame->color.handle; + state.renderbuffer = 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); + glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA, width, height); // Recreate the FBO for the render target frame->render.Release(); @@ -104,8 +106,8 @@ public: 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); + 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!"); } @@ -138,8 +140,15 @@ public: [&] { return !present_queue.empty(); }); if (present_queue.empty()) { // timed out waiting for a frame to draw so return nullptr - return nullptr; + return previous_frame; } + + // free the previous frame and add it back to the free queue + if (previous_frame) { + free_queue.push_back(previous_frame); + free_cv.notify_one(); + } + // the newest entries are pushed to the front of the queue Frontend::Frame* frame = present_queue.front(); present_queue.pop_front(); @@ -149,14 +158,9 @@ public: free_cv.notify_one(); } present_queue.clear(); + previous_frame = frame; 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"( @@ -838,12 +842,15 @@ void RendererOpenGL::TryPresent(int timeout_ms) { const auto& layout = render_window.GetFramebufferLayout(); auto frame = render_window.mailbox->TryGetPresentFrame(timeout_ms); if (!frame) { - LOG_CRITICAL(Render_OpenGL, "Try returned no frame to present"); + LOG_DEBUG(Render_OpenGL, "TryGetPresentFrame returned no frame to present"); return; } + + glClear(GL_COLOR_BUFFER_BIT); + // Recreate the presentation FBO if the color attachment was changed if (frame->color_reloaded) { - LOG_CRITICAL(Render_OpenGL, "Reloading present frame"); + LOG_DEBUG(Render_OpenGL, "Reloading present frame"); render_window.mailbox->ReloadPresentFrame(frame, layout.width, layout.height); } glWaitSync(frame->render_fence, 0, GL_TIMEOUT_IGNORED); @@ -860,7 +867,6 @@ void RendererOpenGL::TryPresent(int timeout_ms) { /* insert fence for the main thread to block on */ frame->present_fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); glFlush(); - render_window.mailbox->ReleasePresentFrame(frame); } void RendererOpenGL::PresentComplete() { From 26d828fb4c7f0b7778d090778951cf1caba90a79 Mon Sep 17 00:00:00 2001 From: James Rowe Date: Fri, 20 Sep 2019 19:09:48 -0600 Subject: [PATCH 15/28] Prevent softlock on shutdown and various cleanup --- src/citra/emu_window/emu_window_sdl2.cpp | 1 - src/citra_qt/bootmanager.cpp | 9 ------ src/citra_qt/bootmanager.h | 1 - src/video_core/renderer_base.h | 3 -- .../renderer_opengl/renderer_opengl.cpp | 28 ++++++++++++++----- .../renderer_opengl/renderer_opengl.h | 3 -- 6 files changed, 21 insertions(+), 24 deletions(-) diff --git a/src/citra/emu_window/emu_window_sdl2.cpp b/src/citra/emu_window/emu_window_sdl2.cpp index 3e4a45671..c4907d041 100644 --- a/src/citra/emu_window/emu_window_sdl2.cpp +++ b/src/citra/emu_window/emu_window_sdl2.cpp @@ -227,7 +227,6 @@ void EmuWindow_SDL2::Present() { while (IsOpen()) { VideoCore::g_renderer->TryPresent(100); SDL_GL_SwapWindow(render_window); - VideoCore::g_renderer->PresentComplete(); } SDL_GL_MakeCurrent(render_window, nullptr); } diff --git a/src/citra_qt/bootmanager.cpp b/src/citra_qt/bootmanager.cpp index 27fa70153..6c2581fd9 100644 --- a/src/citra_qt/bootmanager.cpp +++ b/src/citra_qt/bootmanager.cpp @@ -86,7 +86,6 @@ GRenderWindow::GRenderWindow(QWidget* parent, EmuThread* emu_thread) setWindowTitle(QStringLiteral("Citra %1 | %2-%3") .arg(Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc)); setAttribute(Qt::WA_AcceptTouchEvents); - connect(this, &QOpenGLWidget::frameSwapped, this, &GRenderWindow::OnFrameSwapped); InputCommon::Init(); } @@ -104,10 +103,6 @@ void GRenderWindow::DoneCurrent() { void GRenderWindow::PollEvents() {} -void OnFrameSwapped() { - VideoCore::g_renderer->PresentComplete(); -} - // On Qt 5.0+, this correctly gets the size of the framebuffer (pixels). // // Older versions get the window size (density independent pixels), @@ -303,10 +298,6 @@ void GRenderWindow::paintGL() { update(); } -void GRenderWindow::OnFrameSwapped() { - VideoCore::g_renderer->PresentComplete(); -} - void GRenderWindow::showEvent(QShowEvent* event) { QWidget::showEvent(event); } diff --git a/src/citra_qt/bootmanager.h b/src/citra_qt/bootmanager.h index 856274878..b2120a022 100644 --- a/src/citra_qt/bootmanager.h +++ b/src/citra_qt/bootmanager.h @@ -164,7 +164,6 @@ public slots: void OnEmulationStarting(EmuThread* emu_thread); void OnEmulationStopping(); void OnFramebufferSizeChanged(); - void OnFrameSwapped(); signals: /// Emitted when the window is closed diff --git a/src/video_core/renderer_base.h b/src/video_core/renderer_base.h index 0252e2dfe..b1ee6a973 100644 --- a/src/video_core/renderer_base.h +++ b/src/video_core/renderer_base.h @@ -35,9 +35,6 @@ public: /// 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; - /// Prepares for video dumping (e.g. create necessary buffers, etc) virtual void PrepareVideoDumping() = 0; diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index 555f80130..6c4545bee 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp @@ -50,8 +50,8 @@ 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 8 seems to be a good trade off for now -constexpr std::size_t SWAP_CHAIN_SIZE = 8; +// 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 Frontend::TextureMailbox { public: @@ -72,6 +72,11 @@ public: ~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); + free_queue.clear(); + present_queue.clear(); + free_cv.notify_all(); + present_cv.notify_all(); } void ReloadPresentFrame(Frontend::Frame* frame, u32 height, u32 width) override { @@ -120,7 +125,16 @@ public: 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(); }); + // we want to break at some point to prevent a softlock on close if the presentation thread + // stops consuming buffers + free_cv.wait_for(lock, std::chrono::milliseconds(100), [&] { return !free_queue.empty(); }); + + // 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; + } Frontend::Frame* frame = free_queue.front(); free_queue.pop_front(); @@ -396,7 +410,7 @@ void RendererOpenGL::SwapBuffers() { // 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"); + LOG_DEBUG(Render_OpenGL, "Reloading render frame"); render_window.mailbox->ReloadRenderFrame(frame, layout.width, layout.height); } @@ -846,6 +860,8 @@ void RendererOpenGL::TryPresent(int timeout_ms) { 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 @@ -867,10 +883,8 @@ void RendererOpenGL::TryPresent(int timeout_ms) { /* insert fence for the main thread to block on */ frame->present_fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); glFlush(); -} -void RendererOpenGL::PresentComplete() { - // render_window.mailbox->PresentationComplete(); + glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); } /// Updates the framerate diff --git a/src/video_core/renderer_opengl/renderer_opengl.h b/src/video_core/renderer_opengl/renderer_opengl.h index 16c76f0a3..b2b522516 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.h +++ b/src/video_core/renderer_opengl/renderer_opengl.h @@ -60,9 +60,6 @@ public: /// context void TryPresent(int timeout_ms) override; - /// Finializes the presentation and sets up the presentation frame to go back into the mailbox - void PresentComplete() override; - /// Prepares for video dumping (e.g. create necessary buffers, etc) void PrepareVideoDumping() override; From 29c12058c755d3fb5b3d40722c9846a0f1bfb2e5 Mon Sep 17 00:00:00 2001 From: James Rowe Date: Sat, 21 Sep 2019 23:56:50 -0600 Subject: [PATCH 16/28] Add minimum window size back to citra-qt --- src/citra_qt/bootmanager.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/citra_qt/bootmanager.cpp b/src/citra_qt/bootmanager.cpp index 6c2581fd9..6271b78d8 100644 --- a/src/citra_qt/bootmanager.cpp +++ b/src/citra_qt/bootmanager.cpp @@ -256,6 +256,7 @@ void GRenderWindow::InitRenderTarget() { // WA_DontShowOnScreen, WA_DeleteOnClose core_context = CreateSharedContext(); resize(Core::kScreenTopWidth, Core::kScreenTopHeight + Core::kScreenBottomHeight); + OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size); BackupGeometry(); } From 66f5278f52fed0023072a0a73840df4747ec3144 Mon Sep 17 00:00:00 2001 From: James Rowe Date: Sun, 22 Sep 2019 00:01:31 -0600 Subject: [PATCH 17/28] Delete core_context before shutting down SDL --- src/citra/emu_window/emu_window_sdl2.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/citra/emu_window/emu_window_sdl2.cpp b/src/citra/emu_window/emu_window_sdl2.cpp index c4907d041..f8fc2b3a1 100644 --- a/src/citra/emu_window/emu_window_sdl2.cpp +++ b/src/citra/emu_window/emu_window_sdl2.cpp @@ -211,6 +211,7 @@ EmuWindow_SDL2::EmuWindow_SDL2(bool fullscreen) { } EmuWindow_SDL2::~EmuWindow_SDL2() { + core_context.reset(); Network::Shutdown(); InputCommon::Shutdown(); SDL_GL_DeleteContext(window_context); From 586b8e8b46afc4cf4ea1ad84d6c56e17df9c804d Mon Sep 17 00:00:00 2001 From: James Rowe Date: Sun, 22 Sep 2019 01:19:10 -0600 Subject: [PATCH 18/28] Change from QOpenGLWidget to QWindow While QOpenGLWidget sounds like a good idea, it has issues which are harder to debug due to how Qt manages the context behind the scenes. We could probably work around any of these issues over time, but its probably easier to do it ourselves with a QWindow directly. Plus using QWindow + createWindowContainer is the easiest to use configuration for Qt + Vulkan so this is probably much better in the long run. --- src/citra_qt/bootmanager.cpp | 78 +++++++++++++++---- src/citra_qt/bootmanager.h | 34 ++++++-- src/core/frontend/emu_window.h | 2 +- .../renderer_opengl/gl_resource_manager.cpp | 1 + src/video_core/renderer_opengl/gl_state.cpp | 7 ++ src/video_core/renderer_opengl/gl_state.h | 3 +- .../renderer_opengl/renderer_opengl.cpp | 17 ++-- 7 files changed, 114 insertions(+), 28 deletions(-) diff --git a/src/citra_qt/bootmanager.cpp b/src/citra_qt/bootmanager.cpp index 6271b78d8..2d790556d 100644 --- a/src/citra_qt/bootmanager.cpp +++ b/src/citra_qt/bootmanager.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -79,13 +80,61 @@ void EmuThread::run() { MicroProfileOnThreadExit(); #endif } +OpenGLWindow::OpenGLWindow(QWindow* parent, QOpenGLContext* shared_context) + : QWindow(parent), context(new QOpenGLContext(shared_context->parent())) { + context->setShareContext(shared_context); + context->setScreen(this->screen()); + context->setFormat(shared_context->format()); + context->create(); + + setSurfaceType(QWindow::OpenGLSurface); + + // TODO: One of these flags might be interesting: WA_OpaquePaintEvent, WA_NoBackground, + // WA_DontShowOnScreen, WA_DeleteOnClose + + // We set the WindowTransparnentForInput flag to let qt pass the processing through this QWindow + // through the event stack up to the parent QWidget and then up to the GRenderWindow grandparent + // that handles the event + setFlags(Qt::WindowTransparentForInput); +} + +OpenGLWindow::~OpenGLWindow() { + context->doneCurrent(); +} + +void OpenGLWindow::Present() { + if (!isExposed()) + return; + context->makeCurrent(this); + VideoCore::g_renderer->TryPresent(100); + context->swapBuffers(this); + QWindow::requestUpdate(); +} + +bool OpenGLWindow::event(QEvent* event) { + switch (event->type()) { + case QEvent::UpdateRequest: + Present(); + return true; + default: + return QWindow::event(event); + } +} + +void OpenGLWindow::exposeEvent(QExposeEvent* event) { + QWindow::requestUpdate(); + QWindow::exposeEvent(event); +} GRenderWindow::GRenderWindow(QWidget* parent, EmuThread* emu_thread) - : QOpenGLWidget(parent), emu_thread(emu_thread) { + : QWidget(parent), emu_thread(emu_thread) { setWindowTitle(QStringLiteral("Citra %1 | %2-%3") .arg(Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc)); setAttribute(Qt::WA_AcceptTouchEvents); + auto layout = new QHBoxLayout(this); + layout->setMargin(0); + setLayout(layout); InputCommon::Init(); } @@ -247,23 +296,31 @@ void GRenderWindow::focusOutEvent(QFocusEvent* event) { } void GRenderWindow::resizeEvent(QResizeEvent* event) { - QOpenGLWidget::resizeEvent(event); + child_widget->resize(event->size()); + QWidget::resizeEvent(event); OnFramebufferSizeChanged(); } void GRenderWindow::InitRenderTarget() { - // TODO: One of these flags might be interesting: WA_OpaquePaintEvent, WA_NoBackground, - // WA_DontShowOnScreen, WA_DeleteOnClose + // Destroy the previous run's child_widget which should also destroy the child_window + if (child_widget) { + layout()->removeWidget(child_widget); + delete child_widget; + } + + child_window = + new OpenGLWindow(QWidget::window()->windowHandle(), QOpenGLContext::globalShareContext()); + child_window->create(); + child_widget = createWindowContainer(child_window, this); + child_widget->resize(Core::kScreenTopWidth, Core::kScreenTopHeight + Core::kScreenBottomHeight); + layout()->addWidget(child_widget); + core_context = CreateSharedContext(); resize(Core::kScreenTopWidth, Core::kScreenTopHeight + Core::kScreenBottomHeight); OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size); 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,11 +351,6 @@ void GRenderWindow::OnEmulationStopping() { emu_thread = nullptr; } -void GRenderWindow::paintGL() { - VideoCore::g_renderer->TryPresent(100); - update(); -} - void GRenderWindow::showEvent(QShowEvent* event) { QWidget::showEvent(event); } diff --git a/src/citra_qt/bootmanager.h b/src/citra_qt/bootmanager.h index b2120a022..c88037029 100644 --- a/src/citra_qt/bootmanager.h +++ b/src/citra_qt/bootmanager.h @@ -7,8 +7,9 @@ #include #include #include -#include #include +#include +#include #include "common/thread.h" #include "core/core.h" #include "core/frontend/emu_window.h" @@ -117,7 +118,25 @@ signals: void ErrorThrown(Core::System::ResultStatus, std::string); }; -class GRenderWindow : public QOpenGLWidget, public Frontend::EmuWindow { +class OpenGLWindow : public QWindow { + Q_OBJECT +public: + explicit OpenGLWindow(QWindow* parent, QOpenGLContext* shared_context); + + ~OpenGLWindow(); + + void Present(); + +protected: + bool event(QEvent* event) override; + + void exposeEvent(QExposeEvent* event) override; + +private: + QOpenGLContext* context; +}; + +class GRenderWindow : public QWidget, public Frontend::EmuWindow { Q_OBJECT public: @@ -130,9 +149,6 @@ public: void PollEvents() override; std::unique_ptr CreateSharedContext() const override; - void initializeGL() override; - void paintGL() override; - void BackupGeometry(); void RestoreGeometry(); void restoreGeometry(const QByteArray& geometry); // overridden @@ -181,6 +197,14 @@ private: 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; + EmuThread* emu_thread; /// Temporary storage of the screenshot taken diff --git a/src/core/frontend/emu_window.h b/src/core/frontend/emu_window.h index 6d58bcdda..df6203842 100644 --- a/src/core/frontend/emu_window.h +++ b/src/core/frontend/emu_window.h @@ -31,7 +31,7 @@ public: /** * Recreate the presentation objects attached to this frame with the new specified width/height */ - virtual void ReloadPresentFrame(Frontend::Frame* frame, u32 height, u32 width) = 0; + virtual void ReloadPresentFrame(Frontend::Frame* frame, u32 width, u32 height) = 0; /** * Render thread calls this to get an available frame to present diff --git a/src/video_core/renderer_opengl/gl_resource_manager.cpp b/src/video_core/renderer_opengl/gl_resource_manager.cpp index 33552c6bd..836cbb90c 100644 --- a/src/video_core/renderer_opengl/gl_resource_manager.cpp +++ b/src/video_core/renderer_opengl/gl_resource_manager.cpp @@ -29,6 +29,7 @@ void OGLRenderbuffer::Release() { MICROPROFILE_SCOPE(OpenGL_ResourceDeletion); glDeleteRenderbuffers(1, &handle); + OpenGLState::GetCurState().ResetRenderbuffer(handle).Apply(); handle = 0; } diff --git a/src/video_core/renderer_opengl/gl_state.cpp b/src/video_core/renderer_opengl/gl_state.cpp index 83e6f1678..3e15d64b9 100644 --- a/src/video_core/renderer_opengl/gl_state.cpp +++ b/src/video_core/renderer_opengl/gl_state.cpp @@ -428,4 +428,11 @@ OpenGLState& OpenGLState::ResetFramebuffer(GLuint handle) { return *this; } +OpenGLState& OpenGLState::ResetRenderbuffer(GLuint handle) { + if (renderbuffer == handle) { + renderbuffer = 0; + } + return *this; +} + } // namespace OpenGL diff --git a/src/video_core/renderer_opengl/gl_state.h b/src/video_core/renderer_opengl/gl_state.h index e2ccc9f51..cc7a864a2 100644 --- a/src/video_core/renderer_opengl/gl_state.h +++ b/src/video_core/renderer_opengl/gl_state.h @@ -144,7 +144,7 @@ public: std::array clip_distance; // GL_CLIP_DISTANCE - GLuint renderbuffer; + GLuint renderbuffer; // GL_RENDERBUFFER_BINDING OpenGLState(); @@ -164,6 +164,7 @@ public: OpenGLState& ResetBuffer(GLuint handle); OpenGLState& ResetVertexArray(GLuint handle); OpenGLState& ResetFramebuffer(GLuint handle); + OpenGLState& ResetRenderbuffer(GLuint handle); private: static OpenGLState cur_state; diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index 6c4545bee..464741de9 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include "common/assert.h" #include "common/bit_field.h" #include "common/logging/log.h" @@ -59,13 +60,13 @@ public: std::condition_variable free_cv; std::condition_variable present_cv; std::array swap_chain{}; - std::deque free_queue{}; + std::queue free_queue{}; std::deque present_queue{}; Frontend::Frame* previous_frame = nullptr; OGLTextureMailbox() { for (auto& frame : swap_chain) { - free_queue.push_back(&frame); + free_queue.push(&frame); } } @@ -73,7 +74,7 @@ public: // 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); - free_queue.clear(); + std::queue().swap(free_queue); present_queue.clear(); free_cv.notify_all(); present_cv.notify_all(); @@ -137,7 +138,7 @@ public: } Frontend::Frame* frame = free_queue.front(); - free_queue.pop_front(); + free_queue.pop(); return frame; } @@ -153,13 +154,13 @@ public: 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 + // 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_back(previous_frame); + free_queue.push(previous_frame); free_cv.notify_one(); } @@ -168,9 +169,9 @@ public: 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(); + free_queue.push(f); } + free_cv.notify_one(); present_queue.clear(); previous_frame = frame; return frame; From 68052de8a0f4bcb095fb700c314ec405f7238218 Mon Sep 17 00:00:00 2001 From: James Rowe Date: Tue, 24 Sep 2019 21:28:57 -0600 Subject: [PATCH 19/28] Workaround Qt bug that causes events to forward to the parent incorrectly --- src/citra_qt/bootmanager.cpp | 35 ++++++++++++++++++++++++++--------- src/citra_qt/bootmanager.h | 3 ++- 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/src/citra_qt/bootmanager.cpp b/src/citra_qt/bootmanager.cpp index 2d790556d..a593c2a17 100644 --- a/src/citra_qt/bootmanager.cpp +++ b/src/citra_qt/bootmanager.cpp @@ -80,8 +80,9 @@ void EmuThread::run() { MicroProfileOnThreadExit(); #endif } -OpenGLWindow::OpenGLWindow(QWindow* parent, QOpenGLContext* shared_context) - : QWindow(parent), context(new QOpenGLContext(shared_context->parent())) { +OpenGLWindow::OpenGLWindow(QWindow* parent, QWidget* event_handler, QOpenGLContext* shared_context) + : QWindow(parent), event_handler(event_handler), + context(new QOpenGLContext(shared_context->parent())) { context->setShareContext(shared_context); context->setScreen(this->screen()); context->setFormat(shared_context->format()); @@ -91,11 +92,6 @@ OpenGLWindow::OpenGLWindow(QWindow* parent, QOpenGLContext* shared_context) // TODO: One of these flags might be interesting: WA_OpaquePaintEvent, WA_NoBackground, // WA_DontShowOnScreen, WA_DeleteOnClose - - // We set the WindowTransparnentForInput flag to let qt pass the processing through this QWindow - // through the event stack up to the parent QWidget and then up to the GRenderWindow grandparent - // that handles the event - setFlags(Qt::WindowTransparentForInput); } OpenGLWindow::~OpenGLWindow() { @@ -116,6 +112,27 @@ bool OpenGLWindow::event(QEvent* event) { case QEvent::UpdateRequest: Present(); return true; + case QEvent::MouseButtonPress: + case QEvent::MouseButtonRelease: + case QEvent::MouseButtonDblClick: + case QEvent::MouseMove: + 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); default: return QWindow::event(event); } @@ -308,8 +325,8 @@ void GRenderWindow::InitRenderTarget() { delete child_widget; } - child_window = - new OpenGLWindow(QWidget::window()->windowHandle(), QOpenGLContext::globalShareContext()); + child_window = new OpenGLWindow(QWidget::window()->windowHandle(), this, + QOpenGLContext::globalShareContext()); child_window->create(); child_widget = createWindowContainer(child_window, this); child_widget->resize(Core::kScreenTopWidth, Core::kScreenTopHeight + Core::kScreenBottomHeight); diff --git a/src/citra_qt/bootmanager.h b/src/citra_qt/bootmanager.h index c88037029..44ed0f059 100644 --- a/src/citra_qt/bootmanager.h +++ b/src/citra_qt/bootmanager.h @@ -121,7 +121,7 @@ signals: class OpenGLWindow : public QWindow { Q_OBJECT public: - explicit OpenGLWindow(QWindow* parent, QOpenGLContext* shared_context); + explicit OpenGLWindow(QWindow* parent, QWidget* event_handler, QOpenGLContext* shared_context); ~OpenGLWindow(); @@ -134,6 +134,7 @@ protected: private: QOpenGLContext* context; + QWidget* event_handler; }; class GRenderWindow : public QWidget, public Frontend::EmuWindow { From 86f203e6e82ce06a577b4cf03b25f8eba04a453f Mon Sep 17 00:00:00 2001 From: James Rowe Date: Tue, 24 Sep 2019 21:47:00 -0600 Subject: [PATCH 20/28] Add missing key events and also try to glFinish after swapping --- src/citra_qt/bootmanager.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/citra_qt/bootmanager.cpp b/src/citra_qt/bootmanager.cpp index a593c2a17..b57b0e32c 100644 --- a/src/citra_qt/bootmanager.cpp +++ b/src/citra_qt/bootmanager.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -104,6 +105,8 @@ void OpenGLWindow::Present() { context->makeCurrent(this); VideoCore::g_renderer->TryPresent(100); context->swapBuffers(this); + auto f = context->versionFunctions(); + f->glFinish(); QWindow::requestUpdate(); } @@ -116,6 +119,8 @@ bool OpenGLWindow::event(QEvent* event) { case QEvent::MouseButtonRelease: case QEvent::MouseButtonDblClick: case QEvent::MouseMove: + case QEvent::KeyPress: + case QEvent::KeyRelease: case QEvent::FocusIn: case QEvent::FocusOut: case QEvent::FocusAboutToChange: From 782eae7f65669517a77d20373b5d6a6d4a1f19fe Mon Sep 17 00:00:00 2001 From: James Rowe Date: Thu, 28 Nov 2019 10:56:58 -0700 Subject: [PATCH 21/28] Fix drag and drop --- src/citra_qt/bootmanager.cpp | 30 +++++++++++-- .../configuration/configure_hotkeys.cpp | 4 +- src/citra_qt/configuration/configure_web.cpp | 9 ++-- src/citra_qt/main.cpp | 42 ++++++++++++++----- src/citra_qt/main.h | 9 +++- 5 files changed, 72 insertions(+), 22 deletions(-) diff --git a/src/citra_qt/bootmanager.cpp b/src/citra_qt/bootmanager.cpp index b57b0e32c..1fce7cfd3 100644 --- a/src/citra_qt/bootmanager.cpp +++ b/src/citra_qt/bootmanager.cpp @@ -3,6 +3,7 @@ // Refer to the license.txt file included. #include +#include #include #include #include @@ -14,6 +15,7 @@ #include #include #include "citra_qt/bootmanager.h" +#include "citra_qt/main.h" #include "common/microprofile.h" #include "common/scm_rev.h" #include "core/3ds.h" @@ -31,6 +33,15 @@ EmuThread::EmuThread(Frontend::GraphicsContext& core_context) : core_context(cor EmuThread::~EmuThread() = default; +static GMainWindow* GetMainWindow() { + for (QWidget* w : qApp->topLevelWidgets()) { + if (GMainWindow* main = qobject_cast(w)) { + return main; + } + } + return nullptr; +} + void EmuThread::run() { MicroProfileOnThreadCreate("EmuThread"); Frontend::ScopeAcquireContext scope(core_context); @@ -138,6 +149,15 @@ bool OpenGLWindow::event(QEvent* event) { case QEvent::InputMethodQuery: case QEvent::TouchCancel: return QCoreApplication::sendEvent(event_handler, event); + case QEvent::Drop: + GetMainWindow()->DropAction(static_cast(event)); + return true; + case QEvent::DragResponse: + case QEvent::DragEnter: + case QEvent::DragLeave: + case QEvent::DragMove: + GetMainWindow()->AcceptDropEvent(static_cast(event)); + return true; default: return QWindow::event(event); } @@ -298,15 +318,19 @@ void GRenderWindow::TouchEndEvent() { } bool GRenderWindow::event(QEvent* event) { - if (event->type() == QEvent::TouchBegin) { + switch (event->type()) { + case QEvent::TouchBegin: TouchBeginEvent(static_cast(event)); return true; - } else if (event->type() == QEvent::TouchUpdate) { + case QEvent::TouchUpdate: TouchUpdateEvent(static_cast(event)); return true; - } else if (event->type() == QEvent::TouchEnd || event->type() == QEvent::TouchCancel) { + case QEvent::TouchEnd: + case QEvent::TouchCancel: TouchEndEvent(); return true; + default: + break; } return QWidget::event(event); diff --git a/src/citra_qt/configuration/configure_hotkeys.cpp b/src/citra_qt/configuration/configure_hotkeys.cpp index 1479283f0..238b2e8c6 100644 --- a/src/citra_qt/configuration/configure_hotkeys.cpp +++ b/src/citra_qt/configuration/configure_hotkeys.cpp @@ -105,10 +105,10 @@ void ConfigureHotkeys::ApplyConfiguration(HotkeyRegistry& registry) { for (int key_column_id = 0; key_column_id < parent->rowCount(); key_column_id++) { QStandardItem* action = parent->child(key_column_id, 0); QStandardItem* keyseq = parent->child(key_column_id, 1); - for (auto& [group, sub_actions] : registry.hotkey_groups) { + for (auto & [group, sub_actions] : registry.hotkey_groups) { if (group != parent->text()) continue; - for (auto& [action_name, hotkey] : sub_actions) { + for (auto & [action_name, hotkey] : sub_actions) { if (action_name != action->text()) continue; hotkey.keyseq = QKeySequence(keyseq->text()); diff --git a/src/citra_qt/configuration/configure_web.cpp b/src/citra_qt/configuration/configure_web.cpp index cc7ca675f..266e04ffe 100644 --- a/src/citra_qt/configuration/configure_web.cpp +++ b/src/citra_qt/configuration/configure_web.cpp @@ -127,11 +127,10 @@ void ConfigureWeb::OnLoginChanged() { void ConfigureWeb::VerifyLogin() { ui->button_verify_login->setDisabled(true); ui->button_verify_login->setText(tr("Verifying...")); - verify_watcher.setFuture(QtConcurrent::run( - [username = UsernameFromDisplayToken(ui->edit_token->text().toStdString()), - token = TokenFromDisplayToken(ui->edit_token->text().toStdString())] { - return Core::VerifyLogin(username, token); - })); + verify_watcher.setFuture(QtConcurrent::run([ + username = UsernameFromDisplayToken(ui->edit_token->text().toStdString()), + token = TokenFromDisplayToken(ui->edit_token->text().toStdString()) + ] { return Core::VerifyLogin(username, token); })); } void ConfigureWeb::OnLoginVerified() { diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp index 52c1ba436..8830c9e3f 100644 --- a/src/citra_qt/main.cpp +++ b/src/citra_qt/main.cpp @@ -1869,14 +1869,33 @@ void GMainWindow::closeEvent(QCloseEvent* event) { QWidget::closeEvent(event); } -static bool IsSingleFileDropEvent(QDropEvent* event) { - const QMimeData* mimeData = event->mimeData(); - return mimeData->hasUrls() && mimeData->urls().length() == 1; +static bool IsSingleFileDropEvent(const QMimeData* mime) { + return mime->hasUrls() && mime->urls().length() == 1; } -void GMainWindow::dropEvent(QDropEvent* event) { - if (!IsSingleFileDropEvent(event)) { - return; +static const std::array AcceptedExtensions = {"cci", "3ds", "cxi", "bin", + "3dsx", "app", "elf", "axf"}; + +static bool IsCorrectFileExtension(const QMimeData* mime) { + const QString& filename = mime->urls().at(0).toLocalFile(); + return std::find(AcceptedExtensions.begin(), AcceptedExtensions.end(), + QFileInfo(filename).suffix().toStdString()) != AcceptedExtensions.end(); +} + +static bool IsAcceptableDropEvent(QDropEvent* event) { + return IsSingleFileDropEvent(event->mimeData()) && IsCorrectFileExtension(event->mimeData()); +} + +void GMainWindow::AcceptDropEvent(QDropEvent* event) { + if (IsAcceptableDropEvent(event)) { + event->setDropAction(Qt::DropAction::LinkAction); + event->accept(); + } +} + +bool GMainWindow::DropAction(QDropEvent* event) { + if (!IsAcceptableDropEvent(event)) { + return false; } const QMimeData* mime_data = event->mimeData(); @@ -1891,16 +1910,19 @@ void GMainWindow::dropEvent(QDropEvent* event) { 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() { diff --git a/src/citra_qt/main.h b/src/citra_qt/main.h index 9c7888056..0fa04f5b8 100644 --- a/src/citra_qt/main.h +++ b/src/citra_qt/main.h @@ -41,6 +41,7 @@ class QProgressBar; class RegistersWidget; class Updater; class WaitTreeWidget; + namespace DiscordRPC { class DiscordInterface; } @@ -69,8 +70,12 @@ public: GameList* game_list; std::unique_ptr discord_rpc; + bool DropAction(QDropEvent* event); + void AcceptDropEvent(QDropEvent* event); + public slots: void OnAppFocusStateChanged(Qt::ApplicationState state); + signals: /** @@ -78,8 +83,8 @@ signals: * about to start. At this time, the core system emulation has been initialized, and all * emulation handles and memory should be valid. * - * @param emu_thread Pointer to the newly created EmuThread (to be used by widgets that need to - * access/change emulation state). + * @param emu_thread Pointer to the newly created EmuThread (to be used by widgets that need + * to access/change emulation state). */ void EmulationStarting(EmuThread* emu_thread); From 123c0212ef2eb5be028c41f62b9c6c094f2f5ede Mon Sep 17 00:00:00 2001 From: James Rowe Date: Thu, 28 Nov 2019 11:20:42 -0700 Subject: [PATCH 22/28] Fix window resizing bug --- src/citra_qt/bootmanager.cpp | 2 +- src/citra_qt/bootmanager.h | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/citra_qt/bootmanager.cpp b/src/citra_qt/bootmanager.cpp index 1fce7cfd3..92c450f78 100644 --- a/src/citra_qt/bootmanager.cpp +++ b/src/citra_qt/bootmanager.cpp @@ -92,6 +92,7 @@ void EmuThread::run() { MicroProfileOnThreadExit(); #endif } + OpenGLWindow::OpenGLWindow(QWindow* parent, QWidget* event_handler, QOpenGLContext* shared_context) : QWindow(parent), event_handler(event_handler), context(new QOpenGLContext(shared_context->parent())) { @@ -342,7 +343,6 @@ void GRenderWindow::focusOutEvent(QFocusEvent* event) { } void GRenderWindow::resizeEvent(QResizeEvent* event) { - child_widget->resize(event->size()); QWidget::resizeEvent(event); OnFramebufferSizeChanged(); } diff --git a/src/citra_qt/bootmanager.h b/src/citra_qt/bootmanager.h index 44ed0f059..2a8b32bff 100644 --- a/src/citra_qt/bootmanager.h +++ b/src/citra_qt/bootmanager.h @@ -129,7 +129,6 @@ public: protected: bool event(QEvent* event) override; - void exposeEvent(QExposeEvent* event) override; private: From ea40eb0994a4201ebe1bf5df958de116fcd73c8c Mon Sep 17 00:00:00 2001 From: James Rowe Date: Thu, 28 Nov 2019 20:35:50 -0700 Subject: [PATCH 23/28] Use the proper parent window Fixes an issue where the touch point is incorrect in OpenGLWindow when the render target is initialized for the first time with single window mode disabled. --- src/citra_qt/bootmanager.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/citra_qt/bootmanager.cpp b/src/citra_qt/bootmanager.cpp index 92c450f78..595e6d152 100644 --- a/src/citra_qt/bootmanager.cpp +++ b/src/citra_qt/bootmanager.cpp @@ -354,8 +354,9 @@ void GRenderWindow::InitRenderTarget() { delete child_widget; } - child_window = new OpenGLWindow(QWidget::window()->windowHandle(), this, - QOpenGLContext::globalShareContext()); + 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(Core::kScreenTopWidth, Core::kScreenTopHeight + Core::kScreenBottomHeight); From de17fe31fb0164a52bef1643affe00bbe0a760e3 Mon Sep 17 00:00:00 2001 From: James Rowe Date: Thu, 28 Nov 2019 20:47:33 -0700 Subject: [PATCH 24/28] clang-format --- src/citra_qt/configuration/configure_hotkeys.cpp | 4 ++-- src/citra_qt/configuration/configure_web.cpp | 9 +++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/citra_qt/configuration/configure_hotkeys.cpp b/src/citra_qt/configuration/configure_hotkeys.cpp index 238b2e8c6..1479283f0 100644 --- a/src/citra_qt/configuration/configure_hotkeys.cpp +++ b/src/citra_qt/configuration/configure_hotkeys.cpp @@ -105,10 +105,10 @@ void ConfigureHotkeys::ApplyConfiguration(HotkeyRegistry& registry) { for (int key_column_id = 0; key_column_id < parent->rowCount(); key_column_id++) { QStandardItem* action = parent->child(key_column_id, 0); QStandardItem* keyseq = parent->child(key_column_id, 1); - for (auto & [group, sub_actions] : registry.hotkey_groups) { + for (auto& [group, sub_actions] : registry.hotkey_groups) { if (group != parent->text()) continue; - for (auto & [action_name, hotkey] : sub_actions) { + for (auto& [action_name, hotkey] : sub_actions) { if (action_name != action->text()) continue; hotkey.keyseq = QKeySequence(keyseq->text()); diff --git a/src/citra_qt/configuration/configure_web.cpp b/src/citra_qt/configuration/configure_web.cpp index 266e04ffe..cc7ca675f 100644 --- a/src/citra_qt/configuration/configure_web.cpp +++ b/src/citra_qt/configuration/configure_web.cpp @@ -127,10 +127,11 @@ void ConfigureWeb::OnLoginChanged() { void ConfigureWeb::VerifyLogin() { ui->button_verify_login->setDisabled(true); ui->button_verify_login->setText(tr("Verifying...")); - verify_watcher.setFuture(QtConcurrent::run([ - username = UsernameFromDisplayToken(ui->edit_token->text().toStdString()), - token = TokenFromDisplayToken(ui->edit_token->text().toStdString()) - ] { return Core::VerifyLogin(username, token); })); + verify_watcher.setFuture(QtConcurrent::run( + [username = UsernameFromDisplayToken(ui->edit_token->text().toStdString()), + token = TokenFromDisplayToken(ui->edit_token->text().toStdString())] { + return Core::VerifyLogin(username, token); + })); } void ConfigureWeb::OnLoginVerified() { From 9ae3eb4a3038c6664b0be7c88b5beb1cc735cf6d Mon Sep 17 00:00:00 2001 From: James Rowe Date: Mon, 2 Dec 2019 18:34:11 -0700 Subject: [PATCH 25/28] Remove high dpi scaling flag as it needs more work first --- src/citra_qt/main.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp index 8830c9e3f..5fdc12765 100644 --- a/src/citra_qt/main.cpp +++ b/src/citra_qt/main.cpp @@ -2084,7 +2084,6 @@ int main(int argc, char* argv[]) { std::string bin_path = FileUtil::GetBundleDirectory() + DIR_SEP + ".."; chdir(bin_path.c_str()); #endif - QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QCoreApplication::setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity); QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts); QApplication app(argc, argv); From 65613cce819cdcc0ccbf9ea19210abaffdaf2b1e Mon Sep 17 00:00:00 2001 From: James Rowe Date: Mon, 2 Dec 2019 18:59:12 -0700 Subject: [PATCH 26/28] Add microprofile scopes for presentation --- .../renderer_opengl/renderer_opengl.cpp | 199 ++++++++++-------- .../renderer_opengl/renderer_opengl.h | 3 + 2 files changed, 115 insertions(+), 87 deletions(-) diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index 464741de9..4526330d1 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp @@ -15,6 +15,7 @@ #include "common/assert.h" #include "common/bit_field.h" #include "common/logging/log.h" +#include "common/microprofile.h" #include "core/core.h" #include "core/core_timing.h" #include "core/dumping/backend.h" @@ -284,12 +285,121 @@ RendererOpenGL::RendererOpenGL(Frontend::EmuWindow& window) : RendererBase{windo 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)); + /// Swap buffers (render frame) void RendererOpenGL::SwapBuffers() { // Maintain the rasterizer's state as a priority OpenGLState prev_state = OpenGLState::GetCurState(); state.Apply(); + PrepareRendertarget(); + + RenderScreenshot(); + + RenderVideoDumping(); + + const auto& layout = render_window.GetFramebufferLayout(); + + 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); + // Recreate the frame if the size of the window has changed + if (layout.width != frame->width || layout.height != frame->height) { + LOG_DEBUG(Render_OpenGL, "Reloading render frame"); + render_window.mailbox->ReloadRenderFrame(frame, layout.width, layout.height); + } + + GLuint render_texture = frame->color.handle; + state.draw.draw_framebuffer = frame->render.handle; + state.Apply(); + DrawScreens(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++; + } + + Core::System::GetInstance().perf_stats->EndSystemFrame(); + + render_window.PollEvents(); + + Core::System::GetInstance().frame_limiter.DoFrameLimiting( + Core::System::GetInstance().CoreTiming().GetGlobalTimeUs()); + Core::System::GetInstance().perf_stats->BeginSystemFrame(); + + prev_state.Apply(); + RefreshRasterizerSetting(); + + if (Pica::g_debug_context && Pica::g_debug_context->recorder) { + Pica::g_debug_context->recorder->FrameFinished(); + } +} + +void RendererOpenGL::RenderScreenshot() { + if (VideoCore::g_renderer_screenshot_requested) { + // Draw this frame to the screenshot framebuffer + screenshot_framebuffer.Create(); + GLuint old_read_fb = state.draw.read_framebuffer; + GLuint old_draw_fb = state.draw.draw_framebuffer; + state.draw.read_framebuffer = state.draw.draw_framebuffer = screenshot_framebuffer.handle; + state.Apply(); + + Layout::FramebufferLayout layout{VideoCore::g_screenshot_framebuffer_layout}; + + GLuint renderbuffer; + glGenRenderbuffers(1, &renderbuffer); + glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer); + glRenderbufferStorage(GL_RENDERBUFFER, GL_RGB8, layout.width, layout.height); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, + renderbuffer); + + DrawScreens(layout); + + glReadPixels(0, 0, layout.width, layout.height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, + VideoCore::g_screenshot_bits); + + screenshot_framebuffer.Release(); + state.draw.read_framebuffer = old_read_fb; + state.draw.draw_framebuffer = old_draw_fb; + state.Apply(); + glDeleteRenderbuffers(1, &renderbuffer); + + VideoCore::g_screenshot_complete_callback(); + VideoCore::g_renderer_screenshot_requested = false; + } +} + +void RendererOpenGL::PrepareRendertarget() { for (int i : {0, 1, 2}) { int fb_id = i == 2 ? 1 : 0; const auto& framebuffer = GPU::g_regs.framebuffer_config[fb_id]; @@ -324,39 +434,9 @@ void RendererOpenGL::SwapBuffers() { screen_infos[i].texture.height = framebuffer.height; } } +} - if (VideoCore::g_renderer_screenshot_requested) { - // Draw this frame to the screenshot framebuffer - screenshot_framebuffer.Create(); - GLuint old_read_fb = state.draw.read_framebuffer; - GLuint old_draw_fb = state.draw.draw_framebuffer; - state.draw.read_framebuffer = state.draw.draw_framebuffer = screenshot_framebuffer.handle; - state.Apply(); - - Layout::FramebufferLayout layout{VideoCore::g_screenshot_framebuffer_layout}; - - GLuint renderbuffer; - glGenRenderbuffers(1, &renderbuffer); - glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer); - glRenderbufferStorage(GL_RENDERBUFFER, GL_RGB8, layout.width, layout.height); - glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, - renderbuffer); - - DrawScreens(layout); - - glReadPixels(0, 0, layout.width, layout.height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, - VideoCore::g_screenshot_bits); - - screenshot_framebuffer.Release(); - state.draw.read_framebuffer = old_read_fb; - state.draw.draw_framebuffer = old_draw_fb; - state.Apply(); - glDeleteRenderbuffers(1, &renderbuffer); - - VideoCore::g_screenshot_complete_callback(); - VideoCore::g_renderer_screenshot_requested = false; - } - +void RendererOpenGL::RenderVideoDumping() { if (cleanup_video_dumping.exchange(false)) { ReleaseVideoDumpingGLObjects(); } @@ -384,61 +464,6 @@ void RendererOpenGL::SwapBuffers() { current_pbo = (current_pbo + 1) % 2; next_pbo = (current_pbo + 1) % 2; } - - const auto& layout = render_window.GetFramebufferLayout(); - 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_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; - } - - // Recreate the frame if the size of the window has changed - if (layout.width != frame->width || layout.height != frame->height) { - LOG_DEBUG(Render_OpenGL, "Reloading render frame"); - render_window.mailbox->ReloadRenderFrame(frame, layout.width, layout.height); - } - - GLuint render_texture = frame->color.handle; - state.draw.draw_framebuffer = frame->render.handle; - state.Apply(); - DrawScreens(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++; - - Core::System::GetInstance().perf_stats->EndSystemFrame(); - - render_window.PollEvents(); - - Core::System::GetInstance().frame_limiter.DoFrameLimiting( - Core::System::GetInstance().CoreTiming().GetGlobalTimeUs()); - Core::System::GetInstance().perf_stats->BeginSystemFrame(); - - prev_state.Apply(); - RefreshRasterizerSetting(); - - if (Pica::g_debug_context && Pica::g_debug_context->recorder) { - Pica::g_debug_context->recorder->FrameFinished(); - } } /** diff --git a/src/video_core/renderer_opengl/renderer_opengl.h b/src/video_core/renderer_opengl/renderer_opengl.h index b2b522516..40c850b78 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.h +++ b/src/video_core/renderer_opengl/renderer_opengl.h @@ -70,6 +70,9 @@ private: void InitOpenGLObjects(); void ReloadSampler(); void ReloadShader(); + void PrepareRendertarget(); + void RenderScreenshot(); + void RenderVideoDumping(); void ConfigureFramebufferTexture(TextureInfo& texture, const GPU::Regs::FramebufferConfig& framebuffer); void DrawScreens(const Layout::FramebufferLayout& layout); From 36c5058d669dbc223db73cd6e4116d2bb1e665c1 Mon Sep 17 00:00:00 2001 From: James Rowe Date: Sun, 15 Dec 2019 15:42:05 -0700 Subject: [PATCH 27/28] Reintroduce a vsync option for people with really bad drivers --- src/citra/config.cpp | 2 ++ src/citra/default_ini.h | 5 +++++ src/citra_qt/bootmanager.cpp | 21 +++++++++++++++++-- src/citra_qt/configuration/config.cpp | 2 ++ .../configuration/configure_graphics.cpp | 4 ++++ .../configuration/configure_graphics.ui | 19 +++++++++++++++++ src/citra_qt/main.cpp | 2 +- src/core/settings.h | 2 ++ 8 files changed, 54 insertions(+), 3 deletions(-) diff --git a/src/citra/config.cpp b/src/citra/config.cpp index 297465f92..8fa802a6d 100644 --- a/src/citra/config.cpp +++ b/src/citra/config.cpp @@ -123,6 +123,8 @@ void Config::ReadValues() { Settings::values.use_frame_limit = sdl2_config->GetBoolean("Renderer", "use_frame_limit", true); Settings::values.frame_limit = static_cast(sdl2_config->GetInteger("Renderer", "frame_limit", 100)); + Settings::values.use_vsync_new = + static_cast(sdl2_config->GetInteger("Renderer", "use_vsync_new", 1)); Settings::values.render_3d = static_cast( sdl2_config->GetInteger("Renderer", "render_3d", 0)); diff --git a/src/citra/default_ini.h b/src/citra/default_ini.h index 2ca725438..d646823e2 100644 --- a/src/citra/default_ini.h +++ b/src/citra/default_ini.h @@ -112,6 +112,11 @@ shaders_accurate_mul = # 0: Interpreter (slow), 1 (default): JIT (fast) use_shader_jit = +# 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_new = + # Resolution scale factor # 0: Auto (scales resolution to window size), 1: Native 3DS screen resolution, Otherwise a scale # factor for the 3DS resolution diff --git a/src/citra_qt/bootmanager.cpp b/src/citra_qt/bootmanager.cpp index 595e6d152..11d49dd50 100644 --- a/src/citra_qt/bootmanager.cpp +++ b/src/citra_qt/bootmanager.cpp @@ -96,11 +96,22 @@ void EmuThread::run() { OpenGLWindow::OpenGLWindow(QWindow* parent, QWidget* event_handler, QOpenGLContext* shared_context) : QWindow(parent), event_handler(event_handler), context(new QOpenGLContext(shared_context->parent())) { + + // disable vsync for any shared contexts + auto format = shared_context->format(); + format.setSwapInterval(Settings::values.use_vsync_new ? 1 : 0); + this->setFormat(format); + context->setShareContext(shared_context); context->setScreen(this->screen()); - context->setFormat(shared_context->format()); + context->setFormat(format); context->create(); + LOG_WARNING(Frontend, "OpenGLWindow context format Interval {}", + context->format().swapInterval()); + + LOG_WARNING(Frontend, "OpenGLWindow surface format interval {}", this->format().swapInterval()); + setSurfaceType(QWindow::OpenGLSurface); // TODO: One of these flags might be interesting: WA_OpaquePaintEvent, WA_NoBackground, @@ -409,10 +420,16 @@ std::unique_ptr GRenderWindow::CreateSharedContext() GLContext::GLContext(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(shared_context->format()); + surface->setFormat(format); surface->create(); } diff --git a/src/citra_qt/configuration/config.cpp b/src/citra_qt/configuration/config.cpp index 26034dbe6..d516f6744 100644 --- a/src/citra_qt/configuration/config.cpp +++ b/src/citra_qt/configuration/config.cpp @@ -428,6 +428,7 @@ void Config::ReadRendererValues() { Settings::values.shaders_accurate_mul = ReadSetting(QStringLiteral("shaders_accurate_mul"), false).toBool(); Settings::values.use_shader_jit = ReadSetting(QStringLiteral("use_shader_jit"), true).toBool(); + Settings::values.use_vsync_new = ReadSetting(QStringLiteral("use_vsync_new"), true).toBool(); Settings::values.resolution_factor = static_cast(ReadSetting(QStringLiteral("resolution_factor"), 1).toInt()); Settings::values.use_frame_limit = @@ -855,6 +856,7 @@ void Config::SaveRendererValues() { WriteSetting(QStringLiteral("shaders_accurate_mul"), Settings::values.shaders_accurate_mul, false); WriteSetting(QStringLiteral("use_shader_jit"), Settings::values.use_shader_jit, true); + WriteSetting(QStringLiteral("use_vsync_new"), Settings::values.use_vsync_new, true); WriteSetting(QStringLiteral("resolution_factor"), Settings::values.resolution_factor, 1); WriteSetting(QStringLiteral("use_frame_limit"), Settings::values.use_frame_limit, true); WriteSetting(QStringLiteral("frame_limit"), Settings::values.frame_limit, 100); diff --git a/src/citra_qt/configuration/configure_graphics.cpp b/src/citra_qt/configuration/configure_graphics.cpp index d04c0d6fe..b9dd4b468 100644 --- a/src/citra_qt/configuration/configure_graphics.cpp +++ b/src/citra_qt/configuration/configure_graphics.cpp @@ -18,6 +18,8 @@ ConfigureGraphics::ConfigureGraphics(QWidget* parent) SetConfiguration(); ui->hw_renderer_group->setEnabled(ui->toggle_hw_renderer->isChecked()); + ui->toggle_vsync_new->setEnabled(!Core::System::GetInstance().IsPoweredOn()); + connect(ui->toggle_hw_renderer, &QCheckBox::toggled, this, [this] { auto checked = ui->toggle_hw_renderer->isChecked(); ui->hw_renderer_group->setEnabled(checked); @@ -46,6 +48,7 @@ void ConfigureGraphics::SetConfiguration() { ui->toggle_hw_shader->setChecked(Settings::values.use_hw_shader); ui->toggle_accurate_mul->setChecked(Settings::values.shaders_accurate_mul); ui->toggle_shader_jit->setChecked(Settings::values.use_shader_jit); + ui->toggle_vsync_new->setChecked(Settings::values.use_vsync_new); } void ConfigureGraphics::ApplyConfiguration() { @@ -53,6 +56,7 @@ void ConfigureGraphics::ApplyConfiguration() { Settings::values.use_hw_shader = ui->toggle_hw_shader->isChecked(); Settings::values.shaders_accurate_mul = ui->toggle_accurate_mul->isChecked(); Settings::values.use_shader_jit = ui->toggle_shader_jit->isChecked(); + Settings::values.use_vsync_new = ui->toggle_vsync_new->isChecked(); } void ConfigureGraphics::RetranslateUI() { diff --git a/src/citra_qt/configuration/configure_graphics.ui b/src/citra_qt/configuration/configure_graphics.ui index 88b9a0caa..43f538558 100644 --- a/src/citra_qt/configuration/configure_graphics.ui +++ b/src/citra_qt/configuration/configure_graphics.ui @@ -105,6 +105,25 @@ + + + + Advanced + + + + + + 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. + + + Enable VSync + + + + + + diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp index 5fdc12765..bbe4a94f2 100644 --- a/src/citra_qt/main.cpp +++ b/src/citra_qt/main.cpp @@ -2075,7 +2075,7 @@ int main(int argc, char* argv[]) { QSurfaceFormat format; format.setVersion(3, 3); format.setProfile(QSurfaceFormat::CoreProfile); - format.setSwapInterval(1); + format.setSwapInterval(0); // TODO: expose a setting for buffer value (ie default/single/double/triple) format.setSwapBehavior(QSurfaceFormat::DefaultSwapBehavior); QSurfaceFormat::setDefaultFormat(format); diff --git a/src/core/settings.h b/src/core/settings.h index 0d391afba..d7d351a4c 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -173,6 +173,8 @@ struct Values { bool custom_textures; bool preload_textures; + bool use_vsync_new; + // Audio bool enable_dsp_lle; bool enable_dsp_lle_multithread; From 408e225048b8613e619bace316a86ae03a02a5be Mon Sep 17 00:00:00 2001 From: James Rowe Date: Sun, 15 Dec 2019 16:20:59 -0700 Subject: [PATCH 28/28] Destroy GLWindow on exit to prevent issues closing the app while in fullscreen --- src/citra_qt/bootmanager.cpp | 14 +++++++++----- src/citra_qt/bootmanager.h | 3 +++ src/citra_qt/main.cpp | 3 +++ 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/citra_qt/bootmanager.cpp b/src/citra_qt/bootmanager.cpp index 11d49dd50..a9cdddc18 100644 --- a/src/citra_qt/bootmanager.cpp +++ b/src/citra_qt/bootmanager.cpp @@ -359,11 +359,7 @@ void GRenderWindow::resizeEvent(QResizeEvent* event) { } void GRenderWindow::InitRenderTarget() { - // Destroy the previous run's child_widget which should also destroy the child_window - if (child_widget) { - layout()->removeWidget(child_widget); - delete child_widget; - } + ReleaseRenderTarget(); GMainWindow* parent = GetMainWindow(); QWindow* parent_win_handle = parent ? parent->windowHandle() : nullptr; @@ -379,6 +375,14 @@ void GRenderWindow::InitRenderTarget() { BackupGeometry(); } +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) { if (res_scale == 0) res_scale = VideoCore::GetResolutionScaleFactor(); diff --git a/src/citra_qt/bootmanager.h b/src/citra_qt/bootmanager.h index 2a8b32bff..922e093f9 100644 --- a/src/citra_qt/bootmanager.h +++ b/src/citra_qt/bootmanager.h @@ -173,6 +173,9 @@ public: void 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: diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp index bbe4a94f2..dd56f4b13 100644 --- a/src/citra_qt/main.cpp +++ b/src/citra_qt/main.cpp @@ -1019,6 +1019,9 @@ void GMainWindow::ShutdownGame() { UpdateWindowTitle(); 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) {