Qt: Create OpenGL context on a thread without moving it

This commit is contained in:
Vicki Pfau 2020-11-09 22:31:10 -08:00
parent 7be68ffd1d
commit 74edd964da
2 changed files with 83 additions and 89 deletions

View File

@ -34,44 +34,15 @@ using namespace QGBA;
DisplayGL::DisplayGL(const QSurfaceFormat& format, QWidget* parent) DisplayGL::DisplayGL(const QSurfaceFormat& format, QWidget* parent)
: Display(parent) : Display(parent)
, m_gl(nullptr)
{ {
setAttribute(Qt::WA_NativeWindow); setAttribute(Qt::WA_NativeWindow);
windowHandle()->create(); windowHandle()->create();
// This can spontaneously re-enter into this->resizeEvent before creation is done, so we m_painter = std::make_unique<PainterGL>(windowHandle(), format);
// 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);
} }
DisplayGL::~DisplayGL() { DisplayGL::~DisplayGL() {
stopDrawing(); stopDrawing();
delete m_painter;
delete m_gl;
} }
bool DisplayGL::supportsShaders() const { bool DisplayGL::supportsShaders() const {
@ -81,7 +52,7 @@ bool DisplayGL::supportsShaders() const {
VideoShader* DisplayGL::shaders() { VideoShader* DisplayGL::shaders() {
VideoShader* shaders = nullptr; VideoShader* shaders = nullptr;
if (m_drawThread) { 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 { } else {
shaders = m_painter->shaders(); shaders = m_painter->shaders();
} }
@ -96,16 +67,13 @@ void DisplayGL::startDrawing(std::shared_ptr<CoreController> controller) {
m_painter->setContext(controller); m_painter->setContext(controller);
m_painter->setMessagePainter(messagePainter()); m_painter->setMessagePainter(messagePainter());
m_context = controller; m_context = controller;
m_painter->resize(size());
m_drawThread = new QThread(this); m_drawThread = new QThread(this);
m_drawThread->setObjectName("Painter Thread"); m_drawThread->setObjectName("Painter Thread");
m_gl->doneCurrent();
m_gl->moveToThread(m_drawThread);
m_painter->moveToThread(m_drawThread); m_painter->moveToThread(m_drawThread);
if (videoProxy()) { if (videoProxy()) {
videoProxy()->moveToThread(m_drawThread); 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(); m_drawThread->start();
lockAspectRatio(isAspectRatioLocked()); lockAspectRatio(isAspectRatioLocked());
@ -126,15 +94,10 @@ void DisplayGL::stopDrawing() {
if (m_drawThread) { if (m_drawThread) {
m_isDrawing = false; m_isDrawing = false;
CoreController::Interrupter interrupter(m_context); 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->exit();
m_drawThread->wait(); m_drawThread->wait();
m_drawThread = nullptr; m_drawThread = nullptr;
m_gl->makeCurrent(windowHandle());
#if defined(_WIN32) && defined(USE_EPOXY)
epoxy_handle_external_wglMakeCurrent();
#endif
setUpdatesEnabled(true); setUpdatesEnabled(true);
} }
m_context.reset(); m_context.reset();
@ -144,7 +107,7 @@ void DisplayGL::pauseDrawing() {
if (m_drawThread) { if (m_drawThread) {
m_isDrawing = false; m_isDrawing = false;
CoreController::Interrupter interrupter(m_context); CoreController::Interrupter interrupter(m_context);
QMetaObject::invokeMethod(m_painter, "pause", Qt::BlockingQueuedConnection); QMetaObject::invokeMethod(m_painter.get(), "pause", Qt::BlockingQueuedConnection);
#ifndef Q_OS_MAC #ifndef Q_OS_MAC
setUpdatesEnabled(true); setUpdatesEnabled(true);
#endif #endif
@ -155,7 +118,7 @@ void DisplayGL::unpauseDrawing() {
if (m_drawThread) { if (m_drawThread) {
m_isDrawing = true; m_isDrawing = true;
CoreController::Interrupter interrupter(m_context); CoreController::Interrupter interrupter(m_context);
QMetaObject::invokeMethod(m_painter, "unpause", Qt::BlockingQueuedConnection); QMetaObject::invokeMethod(m_painter.get(), "unpause", Qt::BlockingQueuedConnection);
#ifndef Q_OS_MAC #ifndef Q_OS_MAC
setUpdatesEnabled(false); setUpdatesEnabled(false);
#endif #endif
@ -164,69 +127,69 @@ void DisplayGL::unpauseDrawing() {
void DisplayGL::forceDraw() { void DisplayGL::forceDraw() {
if (m_drawThread) { if (m_drawThread) {
QMetaObject::invokeMethod(m_painter, "forceDraw"); QMetaObject::invokeMethod(m_painter.get(), "forceDraw");
} }
} }
void DisplayGL::lockAspectRatio(bool lock) { void DisplayGL::lockAspectRatio(bool lock) {
Display::lockAspectRatio(lock); Display::lockAspectRatio(lock);
if (m_drawThread) { 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) { void DisplayGL::lockIntegerScaling(bool lock) {
Display::lockIntegerScaling(lock); Display::lockIntegerScaling(lock);
if (m_drawThread) { 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) { void DisplayGL::interframeBlending(bool enable) {
Display::interframeBlending(enable); Display::interframeBlending(enable);
if (m_drawThread) { 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) { void DisplayGL::showOSDMessages(bool enable) {
Display::showOSDMessages(enable); Display::showOSDMessages(enable);
if (m_drawThread) { 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) { void DisplayGL::filter(bool filter) {
Display::filter(filter); Display::filter(filter);
if (m_drawThread) { 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() { void DisplayGL::framePosted() {
if (m_drawThread) { if (m_drawThread) {
m_painter->enqueue(m_context->drawContext()); m_painter->enqueue(m_context->drawContext());
QMetaObject::invokeMethod(m_painter, "draw"); QMetaObject::invokeMethod(m_painter.get(), "draw");
} }
} }
void DisplayGL::setShaders(struct VDir* shaders) { void DisplayGL::setShaders(struct VDir* shaders) {
if (m_drawThread) { 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 { } else {
m_painter->setShaders(shaders); m_painter->setShaders(shaders);
} }
} }
void DisplayGL::clearShaders() { void DisplayGL::clearShaders() {
QMetaObject::invokeMethod(m_painter, "clearShaders"); QMetaObject::invokeMethod(m_painter.get(), "clearShaders");
} }
void DisplayGL::resizeContext() { void DisplayGL::resizeContext() {
if (m_drawThread) { if (m_drawThread) {
m_isDrawing = false; m_isDrawing = false;
CoreController::Interrupter interrupter(m_context); CoreController::Interrupter interrupter(m_context);
QMetaObject::invokeMethod(m_painter, "resizeContext", Qt::BlockingQueuedConnection); QMetaObject::invokeMethod(m_painter.get(), "resizeContext", Qt::BlockingQueuedConnection);
} }
} }
@ -235,7 +198,7 @@ void DisplayGL::setVideoScale(int scale) {
m_isDrawing = false; m_isDrawing = false;
CoreController::Interrupter interrupter(m_context); CoreController::Interrupter interrupter(m_context);
mCoreConfigSetIntValue(&m_context->thread()->core->config, "videoScale", scale); 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::resizeEvent(QResizeEvent* event) {
void DisplayGL::resizePainter() { void DisplayGL::resizePainter() {
if (m_drawThread) { 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(); return m_painter->glTex();
} }
PainterGL::PainterGL(QWindow* surface, QOpenGLContext* parent, int forceVersion) PainterGL::PainterGL(QWindow* surface, const QSurfaceFormat& format)
: m_gl(parent) : m_surface(surface)
, 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 #ifdef BUILD_GL
mGLContext* glBackend; mGLContext* glBackend;
#endif #endif
@ -273,24 +273,14 @@ PainterGL::PainterGL(QWindow* surface, QOpenGLContext* parent, int forceVersion)
mGLES2Context* gl2Backend; mGLES2Context* gl2Backend;
#endif #endif
m_gl->makeCurrent(m_surface); m_window = std::make_unique<QOpenGLPaintDevice>();
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();
}
#if defined(_WIN32) && defined(USE_EPOXY) #if defined(_WIN32) && defined(USE_EPOXY)
epoxy_handle_external_wglMakeCurrent(); epoxy_handle_external_wglMakeCurrent();
#endif #endif
#ifdef BUILD_GLES2 #ifdef BUILD_GLES2
auto version = m_gl->format().version(); version = m_gl->format().version();
QStringList extensions = QString(reinterpret_cast<const char*>(glGetString(GL_EXTENSIONS))).split(' '); 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)) { if (forceVersion != 1 && ((version == qMakePair(2, 1) && extensions.contains("GL_ARB_framebuffer_object")) || version.first > 2)) {
gl2Backend = static_cast<mGLES2Context*>(malloc(sizeof(mGLES2Context))); gl2Backend = static_cast<mGLES2Context*>(malloc(sizeof(mGLES2Context)));
mGLES2ContextCreate(gl2Backend); mGLES2ContextCreate(gl2Backend);
@ -330,13 +320,12 @@ PainterGL::PainterGL(QWindow* surface, QOpenGLContext* parent, int forceVersion)
m_backend->filter = false; m_backend->filter = false;
m_backend->lockAspectRatio = false; m_backend->lockAspectRatio = false;
m_backend->interframeBlending = 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); m_gl->makeCurrent(m_surface);
#if defined(_WIN32) && defined(USE_EPOXY) #if defined(_WIN32) && defined(USE_EPOXY)
epoxy_handle_external_wglMakeCurrent(); epoxy_handle_external_wglMakeCurrent();
@ -357,14 +346,15 @@ PainterGL::~PainterGL() {
#endif #endif
m_backend->deinit(m_backend); m_backend->deinit(m_backend);
m_gl->doneCurrent(); m_gl->doneCurrent();
m_window.reset();
m_gl.reset();
free(m_backend); free(m_backend);
m_backend = nullptr; m_backend = nullptr;
delete m_window;
} }
void PainterGL::setContext(std::shared_ptr<CoreController> context) { void PainterGL::setContext(std::shared_ptr<CoreController> context) {
m_context = context; m_context = context;
resizeContext();
} }
void PainterGL::resizeContext() { void PainterGL::resizeContext() {
@ -419,6 +409,9 @@ void PainterGL::filter(bool filter) {
} }
void PainterGL::start() { void PainterGL::start() {
if (!m_gl) {
create();
}
m_gl->makeCurrent(m_surface); m_gl->makeCurrent(m_surface);
#if defined(_WIN32) && defined(USE_EPOXY) #if defined(_WIN32) && defined(USE_EPOXY)
epoxy_handle_external_wglMakeCurrent(); epoxy_handle_external_wglMakeCurrent();
@ -429,6 +422,7 @@ void PainterGL::start() {
mGLES2ShaderAttach(reinterpret_cast<mGLES2Context*>(m_backend), static_cast<mGLES2Shader*>(m_shader.passes), m_shader.nPasses); mGLES2ShaderAttach(reinterpret_cast<mGLES2Context*>(m_backend), static_cast<mGLES2Shader*>(m_shader.passes), m_shader.nPasses);
} }
#endif #endif
resizeContext();
m_buffer = nullptr; m_buffer = nullptr;
m_active = true; m_active = true;
@ -456,7 +450,7 @@ void PainterGL::draw() {
} }
void PainterGL::forceDraw() { void PainterGL::forceDraw() {
m_painter.begin(m_window); m_painter.begin(m_window.get());
performDraw(); performDraw();
m_painter.end(); m_painter.end();
if (!m_context->thread()->impl->sync.audioWait && !m_context->thread()->impl->sync.videoFrameWait) { if (!m_context->thread()->impl->sync.audioWait && !m_context->thread()->impl->sync.videoFrameWait) {
@ -477,12 +471,10 @@ void PainterGL::stop() {
if (m_videoProxy) { if (m_videoProxy) {
m_videoProxy->reset(); m_videoProxy->reset();
} }
m_gl->doneCurrent(); destroy();
m_gl->moveToThread(m_surface->thread()); moveToThread(m_surface->thread());
m_context.reset();
moveToThread(m_gl->thread());
if (m_videoProxy) { if (m_videoProxy) {
m_videoProxy->moveToThread(m_gl->thread()); m_videoProxy->moveToThread(m_surface->thread());
} }
} }

View File

@ -74,8 +74,7 @@ private:
void resizePainter(); void resizePainter();
bool m_isDrawing = false; bool m_isDrawing = false;
QOpenGLContext* m_gl; std::unique_ptr<PainterGL> m_painter;
PainterGL* m_painter;
QThread* m_drawThread = nullptr; QThread* m_drawThread = nullptr;
std::shared_ptr<CoreController> m_context; std::shared_ptr<CoreController> m_context;
}; };
@ -84,7 +83,7 @@ class PainterGL : public QObject {
Q_OBJECT Q_OBJECT
public: public:
PainterGL(QWindow* surface, QOpenGLContext* parent, int forceVersion = 0); PainterGL(QWindow* surface, const QSurfaceFormat& format);
~PainterGL(); ~PainterGL();
void setContext(std::shared_ptr<CoreController>); void setContext(std::shared_ptr<CoreController>);
@ -120,6 +119,8 @@ private:
void performDraw(); void performDraw();
void dequeue(); void dequeue();
void dequeueAll(); void dequeueAll();
void create();
void destroy();
std::array<std::array<uint32_t, 0x100000>, 3> m_buffers; std::array<std::array<uint32_t, 0x100000>, 3> m_buffers;
QList<uint32_t*> m_free; QList<uint32_t*> m_free;
@ -128,8 +129,9 @@ private:
QPainter m_painter; QPainter m_painter;
QMutex m_mutex; QMutex m_mutex;
QWindow* m_surface; QWindow* m_surface;
QOpenGLPaintDevice* m_window; QSurfaceFormat m_format;
QOpenGLContext* m_gl; std::unique_ptr<QOpenGLPaintDevice> m_window;
std::unique_ptr<QOpenGLContext> m_gl;
bool m_active = false; bool m_active = false;
bool m_started = false; bool m_started = false;
std::shared_ptr<CoreController> m_context = nullptr; std::shared_ptr<CoreController> m_context = nullptr;