diff --git a/CHANGES b/CHANGES index 0d09f8dc0..f564da133 100644 --- a/CHANGES +++ b/CHANGES @@ -18,6 +18,7 @@ Misc: - macOS: Add category to plist (closes mgba.io/i/2691) - macOS: Fix modern build with libepoxy (fixes mgba.io/i/2700) - Qt: Keep track of current palette preset name (fixes mgba.io/i/2680) + - Qt: Move OpenGL proxy onto its own thread (fixes mgba.io/i/2493) 0.10.0: (2022-10-11) Features: diff --git a/src/platform/qt/DisplayGL.cpp b/src/platform/qt/DisplayGL.cpp index e4b2af9a0..b8c0f8cd9 100644 --- a/src/platform/qt/DisplayGL.cpp +++ b/src/platform/qt/DisplayGL.cpp @@ -175,6 +175,11 @@ DisplayGL::DisplayGL(const QSurfaceFormat& format, QWidget* parent) m_drawThread.setObjectName("Painter Thread"); m_painter->setThread(&m_drawThread); + m_proxyThread.setObjectName("OpenGL Proxy Thread"); + m_proxyContext = std::make_unique(); + m_proxyContext->setFormat(format); + connect(m_painter.get(), &PainterGL::created, this, &DisplayGL::setupProxyThread); + connect(&m_drawThread, &QThread::started, m_painter.get(), &PainterGL::create); connect(m_painter.get(), &PainterGL::started, this, [this] { m_hasStarted = true; @@ -189,6 +194,11 @@ DisplayGL::~DisplayGL() { QMetaObject::invokeMethod(m_painter.get(), "destroy", Qt::BlockingQueuedConnection); m_drawThread.exit(); m_drawThread.wait(); + + if (m_proxyThread.isRunning()) { + m_proxyThread.exit(); + m_proxyThread.wait(); + } } bool DisplayGL::supportsShaders() const { @@ -209,9 +219,6 @@ void DisplayGL::startDrawing(std::shared_ptr controller) { m_painter->setContext(controller); m_painter->setMessagePainter(messagePainter()); m_context = controller; - if (videoProxy()) { - videoProxy()->moveToThread(&m_drawThread); - } lockAspectRatio(isAspectRatioLocked()); lockIntegerScaling(isIntegerScalingLocked()); @@ -411,11 +418,34 @@ bool DisplayGL::shouldDisableUpdates() { void DisplayGL::setVideoProxy(std::shared_ptr proxy) { Display::setVideoProxy(proxy); if (proxy) { - proxy->moveToThread(&m_drawThread); + proxy->moveToThread(&m_proxyThread); } m_painter->setVideoProxy(proxy); } +void DisplayGL::setupProxyThread() { + m_proxyContext->moveToThread(&m_proxyThread); + connect(&m_proxyThread, &QThread::started, m_proxyContext.get(), [this]() { + m_proxyContext->setShareContext(m_painter->shareContext()); + m_proxyContext->create(); + m_proxySurface.create(); + m_proxyContext->makeCurrent(&m_proxySurface); +#if defined(_WIN32) && defined(USE_EPOXY) + epoxy_handle_external_wglMakeCurrent(); +#endif + }); + connect(m_painter.get(), &PainterGL::texSwapped, m_proxyContext.get(), [this]() { + if (!m_context->hardwareAccelerated()) { + return; + } + if (videoProxy()) { + videoProxy()->processData(); + } + m_painter->updateFramebufferHandle(); + }, Qt::BlockingQueuedConnection); + m_proxyThread.start(); +} + int DisplayGL::framebufferHandle() { return m_painter->glTex(); } @@ -481,9 +511,15 @@ void PainterGL::create() { #if defined(BUILD_GLES2) || defined(BUILD_GLES3) if (m_supportsShaders) { + QOpenGLFunctions_Baseline* fn = m_gl->versionFunctions(); gl2Backend = static_cast(malloc(sizeof(mGLES2Context))); mGLES2ContextCreate(gl2Backend); m_backend = &gl2Backend->d; + fn->glGenTextures(m_bridgeTexes.size(), m_bridgeTexes.data()); + for (auto tex : m_bridgeTexes) { + m_freeTex.enqueue(tex); + } + m_bridgeTexIn = m_freeTex.dequeue(); } #endif @@ -503,10 +539,10 @@ void PainterGL::create() { painter->makeCurrent(); #if defined(BUILD_GLES2) || defined(BUILD_GLES3) + mGLES2Context* gl2Backend = reinterpret_cast(painter->m_backend); if (painter->m_widget && painter->supportsShaders()) { QOpenGLFunctions_Baseline* fn = painter->m_gl->versionFunctions(); fn->glFinish(); - mGLES2Context* gl2Backend = reinterpret_cast(painter->m_backend); painter->m_widget->setTex(painter->m_finalTex[painter->m_finalTexIdx]); painter->m_finalTexIdx ^= 1; gl2Backend->finalShader.tex = painter->m_finalTex[painter->m_finalTexIdx]; @@ -540,6 +576,8 @@ void PainterGL::create() { m_backend->filter = false; m_backend->lockAspectRatio = false; m_backend->interframeBlending = false; + + emit created(); } void PainterGL::destroy() { @@ -548,9 +586,11 @@ void PainterGL::destroy() { } makeCurrent(); #if defined(BUILD_GLES2) || defined(BUILD_GLES3) + QOpenGLFunctions_Baseline* fn = m_gl->versionFunctions(); if (m_shader.passes) { mGLES2ShaderFree(&m_shader); } + fn->glDeleteTextures(m_bridgeTexes.size(), m_bridgeTexes.data()); #endif m_backend->deinit(m_backend); m_gl->doneCurrent(); @@ -636,6 +676,7 @@ void PainterGL::start() { } #endif resizeContext(); + m_context->addFrameAction(std::bind(&PainterGL::swapTex, this)); m_buffer = nullptr; m_active = true; @@ -644,7 +685,7 @@ void PainterGL::start() { } void PainterGL::draw() { - if (!m_started || m_queue.isEmpty()) { + if (!m_started || (m_queue.isEmpty() && m_queueTex.isEmpty())) { return; } @@ -671,7 +712,7 @@ void PainterGL::draw() { return; } dequeue(); - bool forceRedraw = !m_videoProxy; + bool forceRedraw = true; if (!m_delayTimer.isValid()) { m_delayTimer.start(); } else { @@ -725,11 +766,6 @@ void PainterGL::doStop() { m_videoProxy->processData(); } } - if (m_videoProxy) { - m_videoProxy->reset(); - m_videoProxy->moveToThread(m_window->thread()); - m_videoProxy.reset(); - } m_backend->clear(m_backend); m_backend->swap(m_backend); } @@ -759,38 +795,60 @@ void PainterGL::performDraw() { } void PainterGL::enqueue(const uint32_t* backing) { + if (!backing) { + return; + } QMutexLocker locker(&m_mutex); uint32_t* buffer = nullptr; - if (backing) { - if (m_free.isEmpty()) { - buffer = m_queue.dequeue(); - } else { - buffer = m_free.takeLast(); - } - if (buffer) { - QSize size = m_context->screenDimensions(); - memcpy(buffer, backing, size.width() * size.height() * BYTES_PER_PIXEL); - } + if (m_free.isEmpty()) { + buffer = m_queue.dequeue(); + } else { + buffer = m_free.takeLast(); + } + if (buffer) { + QSize size = m_context->screenDimensions(); + memcpy(buffer, backing, size.width() * size.height() * BYTES_PER_PIXEL); } m_queue.enqueue(buffer); } +void PainterGL::enqueue(GLuint tex) { + QMutexLocker locker(&m_mutex); + if (m_freeTex.isEmpty()) { + m_bridgeTexIn = m_queueTex.dequeue(); + } else { + m_bridgeTexIn = m_freeTex.takeLast(); + } + m_queueTex.enqueue(tex); +} + void PainterGL::dequeue() { QMutexLocker locker(&m_mutex); - if (m_queue.isEmpty()) { - return; + if (!m_queue.isEmpty()) { + uint32_t* buffer = m_queue.dequeue(); + if (m_buffer) { + m_free.append(m_buffer); + } + m_buffer = buffer; } - uint32_t* buffer = m_queue.dequeue(); - if (m_buffer) { - m_free.append(m_buffer); - m_buffer = nullptr; + + if (!m_queueTex.isEmpty()) { + if (m_bridgeTexOut != std::numeric_limits::max()) { + m_freeTex.enqueue(m_bridgeTexOut); + } + m_bridgeTexOut = m_queueTex.dequeue(); +#if defined(BUILD_GLES2) || defined(BUILD_GLES3) + if (supportsShaders()) { + mGLES2Context* gl2Backend = reinterpret_cast(m_backend); + gl2Backend->tex = m_bridgeTexOut; + } +#endif } - m_buffer = buffer; } void PainterGL::dequeueAll(bool keep) { QMutexLocker locker(&m_mutex); - uint32_t* buffer = 0; + uint32_t* buffer = nullptr; while (!m_queue.isEmpty()) { buffer = m_queue.dequeue(); if (keep) { @@ -802,6 +860,13 @@ void PainterGL::dequeueAll(bool keep) { m_free.append(buffer); } } + m_queueTex.clear(); + m_freeTex.clear(); + for (auto tex : m_bridgeTexes) { + m_freeTex.enqueue(tex); + } + m_bridgeTexIn = m_freeTex.dequeue(); + m_bridgeTexOut = std::numeric_limits::max(); if (m_buffer && !keep) { m_free.append(m_buffer); m_buffer = nullptr; @@ -861,4 +926,29 @@ int PainterGL::glTex() { #endif } +QOpenGLContext* PainterGL::shareContext() { + if (m_widget) { + return m_widget->context(); + } else { + return m_gl.get(); + } +} + +void PainterGL::updateFramebufferHandle() { + QOpenGLFunctions_Baseline* fn = m_gl->versionFunctions(); + fn->glFinish(); + enqueue(m_bridgeTexIn); + m_context->setFramebufferHandle(m_bridgeTexIn); +} + +void PainterGL::swapTex() { + if (!m_started) { + return; + } + + CoreController::Interrupter interrupter(m_context); + emit texSwapped(); + m_context->addFrameAction(std::bind(&PainterGL::swapTex, this)); +} + #endif diff --git a/src/platform/qt/DisplayGL.h b/src/platform/qt/DisplayGL.h index 77ea6d195..3ed31a9d8 100644 --- a/src/platform/qt/DisplayGL.h +++ b/src/platform/qt/DisplayGL.h @@ -112,6 +112,9 @@ protected: virtual void paintEvent(QPaintEvent*) override { forceDraw(); } virtual void resizeEvent(QResizeEvent*) override; +private slots: + void setupProxyThread(); + private: void resizePainter(); bool shouldDisableUpdates(); @@ -122,8 +125,11 @@ private: bool m_hasStarted = false; std::unique_ptr m_painter; QThread m_drawThread; + QThread m_proxyThread; std::shared_ptr m_context; mGLWidget* m_gl; + QOffscreenSurface m_proxySurface; + std::unique_ptr m_proxyContext; }; class PainterGL : public QObject { @@ -137,15 +143,21 @@ public: void setContext(std::shared_ptr); void setMessagePainter(MessagePainter*); void enqueue(const uint32_t* backing); + void enqueue(GLuint tex); void stop(); bool supportsShaders() const { return m_supportsShaders; } int glTex(); + QOpenGLContext* shareContext(); + void setVideoProxy(std::shared_ptr); void interrupt(); + // Run on main thread + void swapTex(); + public slots: void create(); void destroy(); @@ -163,13 +175,16 @@ public slots: void showFrameCounter(bool enable); void filter(bool filter); void resizeContext(); + void updateFramebufferHandle(); void setShaders(struct VDir*); void clearShaders(); VideoShader* shaders(); signals: + void created(); void started(); + void texSwapped(); private slots: void doStop(); @@ -184,6 +199,14 @@ private: QList m_free; QQueue m_queue; uint32_t* m_buffer = nullptr; + + std::array m_bridgeTexes; + QQueue m_freeTex; + QQueue m_queueTex; + + GLuint m_bridgeTexIn = std::numeric_limits::max(); + GLuint m_bridgeTexOut = std::numeric_limits::max(); + QPainter m_painter; QMutex m_mutex; QWindow* m_window;