Qt: Create OpenGL context on a thread without moving it
@@ -34,44 +34,15 @@ using namespace QGBA;
DisplayGL::DisplayGL(const QSurfaceFormat& format, QWidget* parent) : Display(parent) - , m_gl(nullptr) { setAttribute(Qt::WA_NativeWindow); windowHandle()->create(); - // This can spontaneously re-enter into this->resizeEvent before creation is done, so we - // need to make sure it's initialized to nullptr before we assign the new object to it - m_gl = new QOpenGLContext; - m_gl->setFormat(format); - m_gl->create(); - - m_gl->makeCurrent(windowHandle()); -#if defined(_WIN32) && defined(USE_EPOXY) - epoxy_handle_external_wglMakeCurrent(); -#endif - auto version = m_gl->format().version(); - QStringList extensions = QString(reinterpret_cast<const char*>(glGetString(GL_EXTENSIONS))).split(' '); - - int forceVersion = 0; - if (format.majorVersion() < 2) { - forceVersion = 1; - } - - if ((version == qMakePair(2, 1) && !extensions.contains("GL_ARB_framebuffer_object")) || version == qMakePair(2, 0)) { - QSurfaceFormat newFormat(format); - newFormat.setVersion(1, 4); - forceVersion = 1; - m_gl->setFormat(newFormat); - m_gl->create(); - } - - m_painter = new PainterGL(windowHandle(), m_gl, forceVersion); + m_painter = std::make_unique<PainterGL>(windowHandle(), format); } DisplayGL::~DisplayGL() { stopDrawing(); - delete m_painter; - delete m_gl; } bool DisplayGL::supportsShaders() const {@@ -81,7 +52,7 @@
VideoShader* DisplayGL::shaders() { VideoShader* shaders = nullptr; if (m_drawThread) { - QMetaObject::invokeMethod(m_painter, "shaders", Qt::BlockingQueuedConnection, Q_RETURN_ARG(VideoShader*, shaders)); + QMetaObject::invokeMethod(m_painter.get(), "shaders", Qt::BlockingQueuedConnection, Q_RETURN_ARG(VideoShader*, shaders)); } else { shaders = m_painter->shaders(); }@@ -96,16 +67,13 @@ m_isDrawing = true;
m_painter->setContext(controller); m_painter->setMessagePainter(messagePainter()); m_context = controller; - m_painter->resize(size()); m_drawThread = new QThread(this); m_drawThread->setObjectName("Painter Thread"); - m_gl->doneCurrent(); - m_gl->moveToThread(m_drawThread); m_painter->moveToThread(m_drawThread); if (videoProxy()) { videoProxy()->moveToThread(m_drawThread); } - connect(m_drawThread, &QThread::started, m_painter, &PainterGL::start); + connect(m_drawThread, &QThread::started, m_painter.get(), &PainterGL::start); m_drawThread->start(); lockAspectRatio(isAspectRatioLocked());@@ -126,15 +94,10 @@ void DisplayGL::stopDrawing() {
if (m_drawThread) { m_isDrawing = false; CoreController::Interrupter interrupter(m_context); - QMetaObject::invokeMethod(m_painter, "stop", Qt::BlockingQueuedConnection); + QMetaObject::invokeMethod(m_painter.get(), "stop", Qt::BlockingQueuedConnection); m_drawThread->exit(); m_drawThread->wait(); m_drawThread = nullptr; - - m_gl->makeCurrent(windowHandle()); -#if defined(_WIN32) && defined(USE_EPOXY) - epoxy_handle_external_wglMakeCurrent(); -#endif setUpdatesEnabled(true); } m_context.reset();@@ -144,7 +107,7 @@ void DisplayGL::pauseDrawing() {
if (m_drawThread) { m_isDrawing = false; CoreController::Interrupter interrupter(m_context); - QMetaObject::invokeMethod(m_painter, "pause", Qt::BlockingQueuedConnection); + QMetaObject::invokeMethod(m_painter.get(), "pause", Qt::BlockingQueuedConnection); #ifndef Q_OS_MAC setUpdatesEnabled(true); #endif@@ -155,7 +118,7 @@ void DisplayGL::unpauseDrawing() {
if (m_drawThread) { m_isDrawing = true; CoreController::Interrupter interrupter(m_context); - QMetaObject::invokeMethod(m_painter, "unpause", Qt::BlockingQueuedConnection); + QMetaObject::invokeMethod(m_painter.get(), "unpause", Qt::BlockingQueuedConnection); #ifndef Q_OS_MAC setUpdatesEnabled(false); #endif@@ -164,69 +127,69 @@ }
void DisplayGL::forceDraw() { if (m_drawThread) { - QMetaObject::invokeMethod(m_painter, "forceDraw"); + QMetaObject::invokeMethod(m_painter.get(), "forceDraw"); } } void DisplayGL::lockAspectRatio(bool lock) { Display::lockAspectRatio(lock); if (m_drawThread) { - QMetaObject::invokeMethod(m_painter, "lockAspectRatio", Q_ARG(bool, lock)); + QMetaObject::invokeMethod(m_painter.get(), "lockAspectRatio", Q_ARG(bool, lock)); } } void DisplayGL::lockIntegerScaling(bool lock) { Display::lockIntegerScaling(lock); if (m_drawThread) { - QMetaObject::invokeMethod(m_painter, "lockIntegerScaling", Q_ARG(bool, lock)); + QMetaObject::invokeMethod(m_painter.get(), "lockIntegerScaling", Q_ARG(bool, lock)); } } void DisplayGL::interframeBlending(bool enable) { Display::interframeBlending(enable); if (m_drawThread) { - QMetaObject::invokeMethod(m_painter, "interframeBlending", Q_ARG(bool, enable)); + QMetaObject::invokeMethod(m_painter.get(), "interframeBlending", Q_ARG(bool, enable)); } } void DisplayGL::showOSDMessages(bool enable) { Display::showOSDMessages(enable); if (m_drawThread) { - QMetaObject::invokeMethod(m_painter, "showOSD", Q_ARG(bool, enable)); + QMetaObject::invokeMethod(m_painter.get(), "showOSD", Q_ARG(bool, enable)); } } void DisplayGL::filter(bool filter) { Display::filter(filter); if (m_drawThread) { - QMetaObject::invokeMethod(m_painter, "filter", Q_ARG(bool, filter)); + QMetaObject::invokeMethod(m_painter.get(), "filter", Q_ARG(bool, filter)); } } void DisplayGL::framePosted() { if (m_drawThread) { m_painter->enqueue(m_context->drawContext()); - QMetaObject::invokeMethod(m_painter, "draw"); + QMetaObject::invokeMethod(m_painter.get(), "draw"); } } void DisplayGL::setShaders(struct VDir* shaders) { if (m_drawThread) { - QMetaObject::invokeMethod(m_painter, "setShaders", Qt::BlockingQueuedConnection, Q_ARG(struct VDir*, shaders)); + QMetaObject::invokeMethod(m_painter.get(), "setShaders", Qt::BlockingQueuedConnection, Q_ARG(struct VDir*, shaders)); } else { m_painter->setShaders(shaders); } } void DisplayGL::clearShaders() { - QMetaObject::invokeMethod(m_painter, "clearShaders"); + QMetaObject::invokeMethod(m_painter.get(), "clearShaders"); } void DisplayGL::resizeContext() { if (m_drawThread) { m_isDrawing = false; CoreController::Interrupter interrupter(m_context); - QMetaObject::invokeMethod(m_painter, "resizeContext", Qt::BlockingQueuedConnection); + QMetaObject::invokeMethod(m_painter.get(), "resizeContext", Qt::BlockingQueuedConnection); } }@@ -235,7 +198,7 @@ if (m_drawThread) {
m_isDrawing = false; CoreController::Interrupter interrupter(m_context); mCoreConfigSetIntValue(&m_context->thread()->core->config, "videoScale", scale); - QMetaObject::invokeMethod(m_painter, "resizeContext", Qt::BlockingQueuedConnection); + QMetaObject::invokeMethod(m_painter.get(), "resizeContext", Qt::BlockingQueuedConnection); } }@@ -246,7 +209,7 @@ }
void DisplayGL::resizePainter() { if (m_drawThread) { - QMetaObject::invokeMethod(m_painter, "resize", Qt::BlockingQueuedConnection, Q_ARG(QSize, size())); + QMetaObject::invokeMethod(m_painter.get(), "resize", Qt::BlockingQueuedConnection, Q_ARG(QSize, size())); } }@@ -262,10 +225,47 @@ int DisplayGL::framebufferHandle() {
return m_painter->glTex(); } -PainterGL::PainterGL(QWindow* surface, QOpenGLContext* parent, int forceVersion) - : m_gl(parent) - , m_surface(surface) +PainterGL::PainterGL(QWindow* surface, const QSurfaceFormat& format) + : m_surface(surface) + , m_format(format) { + for (auto& buf : m_buffers) { + m_free.append(&buf.front()); + } +} + +PainterGL::~PainterGL() { + if (m_gl) { + destroy(); + } +} + +void PainterGL::create() { + m_gl = std::make_unique<QOpenGLContext>(); + m_gl->setFormat(m_format); + m_gl->create(); + + m_gl->makeCurrent(m_surface); +#if defined(_WIN32) && defined(USE_EPOXY) + epoxy_handle_external_wglMakeCurrent(); +#endif + auto version = m_gl->format().version(); + QStringList extensions = QString(reinterpret_cast<const char*>(glGetString(GL_EXTENSIONS))).split(' '); + + int forceVersion = 0; + if (m_format.majorVersion() < 2) { + forceVersion = 1; + } + + if ((version == qMakePair(2, 1) && !extensions.contains("GL_ARB_framebuffer_object")) || version == qMakePair(2, 0)) { + QSurfaceFormat newFormat(m_format); + newFormat.setVersion(1, 4); + forceVersion = 1; + m_gl->setFormat(newFormat); + m_gl->create(); + } + m_gl->makeCurrent(m_surface); + #ifdef BUILD_GL mGLContext* glBackend; #endif@@ -273,24 +273,14 @@ #ifdef BUILD_GLES2
mGLES2Context* gl2Backend; #endif - m_gl->makeCurrent(m_surface); - m_window = new QOpenGLPaintDevice; - { - // XXX: Qt creates TLS for OpenGL objects in the local thread - // We need to prevent that thread from being the painter thread - // Qt also caches the engine object on the device if a different - // engine is active, so let's make a temporary one - QOpenGLPaintDevice* fakeDevice = new QOpenGLPaintDevice; - QPainter fakePainter(fakeDevice); - m_window->paintEngine(); - } + m_window = std::make_unique<QOpenGLPaintDevice>(); #if defined(_WIN32) && defined(USE_EPOXY) epoxy_handle_external_wglMakeCurrent(); #endif #ifdef BUILD_GLES2 - auto version = m_gl->format().version(); - QStringList extensions = QString(reinterpret_cast<const char*>(glGetString(GL_EXTENSIONS))).split(' '); + version = m_gl->format().version(); + extensions = QString(reinterpret_cast<const char*>(glGetString(GL_EXTENSIONS))).split(' '); if (forceVersion != 1 && ((version == qMakePair(2, 1) && extensions.contains("GL_ARB_framebuffer_object")) || version.first > 2)) { gl2Backend = static_cast<mGLES2Context*>(malloc(sizeof(mGLES2Context))); mGLES2ContextCreate(gl2Backend);@@ -330,13 +320,12 @@ m_backend->user = this;
m_backend->filter = false; m_backend->lockAspectRatio = false; m_backend->interframeBlending = false; - - for (auto& buf : m_buffers) { - m_free.append(&buf.front()); - } } -PainterGL::~PainterGL() { +void PainterGL::destroy() { + if (!m_gl) { + return; + } m_gl->makeCurrent(m_surface); #if defined(_WIN32) && defined(USE_EPOXY) epoxy_handle_external_wglMakeCurrent();@@ -357,14 +346,15 @@ }
#endif m_backend->deinit(m_backend); m_gl->doneCurrent(); + m_window.reset(); + m_gl.reset(); + free(m_backend); m_backend = nullptr; - delete m_window; } void PainterGL::setContext(std::shared_ptr<CoreController> context) { m_context = context; - resizeContext(); } void PainterGL::resizeContext() {@@ -419,6 +409,9 @@ }
} void PainterGL::start() { + if (!m_gl) { + create(); + } m_gl->makeCurrent(m_surface); #if defined(_WIN32) && defined(USE_EPOXY) epoxy_handle_external_wglMakeCurrent();@@ -429,6 +422,7 @@ if (m_supportsShaders && m_shader.passes) {
mGLES2ShaderAttach(reinterpret_cast<mGLES2Context*>(m_backend), static_cast<mGLES2Shader*>(m_shader.passes), m_shader.nPasses); } #endif + resizeContext(); m_buffer = nullptr; m_active = true;@@ -456,7 +450,7 @@ forceDraw();
} void PainterGL::forceDraw() { - m_painter.begin(m_window); + m_painter.begin(m_window.get()); performDraw(); m_painter.end(); if (!m_context->thread()->impl->sync.audioWait && !m_context->thread()->impl->sync.videoFrameWait) {@@ -477,12 +471,10 @@ m_backend->swap(m_backend);
if (m_videoProxy) { m_videoProxy->reset(); } - m_gl->doneCurrent(); - m_gl->moveToThread(m_surface->thread()); - m_context.reset(); - moveToThread(m_gl->thread()); + destroy(); + moveToThread(m_surface->thread()); if (m_videoProxy) { - m_videoProxy->moveToThread(m_gl->thread()); + m_videoProxy->moveToThread(m_surface->thread()); } }
@@ -74,8 +74,7 @@ private:
void resizePainter(); bool m_isDrawing = false; - QOpenGLContext* m_gl; - PainterGL* m_painter; + std::unique_ptr<PainterGL> m_painter; QThread* m_drawThread = nullptr; std::shared_ptr<CoreController> m_context; };@@ -84,7 +83,7 @@ class PainterGL : public QObject {
Q_OBJECT public: - PainterGL(QWindow* surface, QOpenGLContext* parent, int forceVersion = 0); + PainterGL(QWindow* surface, const QSurfaceFormat& format); ~PainterGL(); void setContext(std::shared_ptr<CoreController>);@@ -120,6 +119,8 @@ private:
void performDraw(); void dequeue(); void dequeueAll(); + void create(); + void destroy(); std::array<std::array<uint32_t, 0x100000>, 3> m_buffers; QList<uint32_t*> m_free;@@ -128,8 +129,9 @@ uint32_t* m_buffer;
QPainter m_painter; QMutex m_mutex; QWindow* m_surface; - QOpenGLPaintDevice* m_window; - QOpenGLContext* m_gl; + QSurfaceFormat m_format; + std::unique_ptr<QOpenGLPaintDevice> m_window; + std::unique_ptr<QOpenGLContext> m_gl; bool m_active = false; bool m_started = false; std::shared_ptr<CoreController> m_context = nullptr;