diff --git a/src/citra_qt/bootmanager.cpp b/src/citra_qt/bootmanager.cpp index 6271b78d87..2d790556d6 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 b2120a022c..c88037029f 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 6d58bcddac..df62038426 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 33552c6bd8..836cbb90c4 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 83e6f16784..3e15d64b91 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 e2ccc9f512..cc7a864a22 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 6c4545bee5..464741de97 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;