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)
: 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 @@ bool DisplayGL::supportsShaders() const {
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 @@ void DisplayGL::startDrawing(std::shared_ptr<CoreController> controller) {
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::unpauseDrawing() {
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 @@ void DisplayGL::setVideoScale(int scale) {
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::resizeEvent(QResizeEvent* event) {
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 @@ PainterGL::PainterGL(QWindow* surface, QOpenGLContext* parent, int forceVersion)
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 @@ PainterGL::PainterGL(QWindow* surface, QOpenGLContext* parent, int forceVersion)
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 @@ PainterGL::~PainterGL() {
#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::filter(bool filter) {
}
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 @@ void PainterGL::start() {
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 @@ void PainterGL::draw() {
}
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 @@ void PainterGL::stop() {
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());
}
}

View File

@ -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 @@ private:
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;