From 586b8e8b46afc4cf4ea1ad84d6c56e17df9c804d Mon Sep 17 00:00:00 2001 From: James Rowe Date: Sun, 22 Sep 2019 01:19:10 -0600 Subject: [PATCH] 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;