Qt: Move OpenGL proxy onto its own thread (fixes #2493)

This commit is contained in:
Vicki Pfau 2022-11-20 00:39:00 -08:00
parent 0271f12280
commit 6aa558c4a0
3 changed files with 144 additions and 30 deletions

View File

@ -18,6 +18,7 @@ Misc:
- macOS: Add category to plist (closes mgba.io/i/2691) - macOS: Add category to plist (closes mgba.io/i/2691)
- macOS: Fix modern build with libepoxy (fixes mgba.io/i/2700) - 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: 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) 0.10.0: (2022-10-11)
Features: Features:

View File

@ -175,6 +175,11 @@ DisplayGL::DisplayGL(const QSurfaceFormat& format, QWidget* parent)
m_drawThread.setObjectName("Painter Thread"); m_drawThread.setObjectName("Painter Thread");
m_painter->setThread(&m_drawThread); m_painter->setThread(&m_drawThread);
m_proxyThread.setObjectName("OpenGL Proxy Thread");
m_proxyContext = std::make_unique<QOpenGLContext>();
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_drawThread, &QThread::started, m_painter.get(), &PainterGL::create);
connect(m_painter.get(), &PainterGL::started, this, [this] { connect(m_painter.get(), &PainterGL::started, this, [this] {
m_hasStarted = true; m_hasStarted = true;
@ -189,6 +194,11 @@ DisplayGL::~DisplayGL() {
QMetaObject::invokeMethod(m_painter.get(), "destroy", Qt::BlockingQueuedConnection); QMetaObject::invokeMethod(m_painter.get(), "destroy", Qt::BlockingQueuedConnection);
m_drawThread.exit(); m_drawThread.exit();
m_drawThread.wait(); m_drawThread.wait();
if (m_proxyThread.isRunning()) {
m_proxyThread.exit();
m_proxyThread.wait();
}
} }
bool DisplayGL::supportsShaders() const { bool DisplayGL::supportsShaders() const {
@ -209,9 +219,6 @@ 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;
if (videoProxy()) {
videoProxy()->moveToThread(&m_drawThread);
}
lockAspectRatio(isAspectRatioLocked()); lockAspectRatio(isAspectRatioLocked());
lockIntegerScaling(isIntegerScalingLocked()); lockIntegerScaling(isIntegerScalingLocked());
@ -411,11 +418,34 @@ bool DisplayGL::shouldDisableUpdates() {
void DisplayGL::setVideoProxy(std::shared_ptr<VideoProxy> proxy) { void DisplayGL::setVideoProxy(std::shared_ptr<VideoProxy> proxy) {
Display::setVideoProxy(proxy); Display::setVideoProxy(proxy);
if (proxy) { if (proxy) {
proxy->moveToThread(&m_drawThread); proxy->moveToThread(&m_proxyThread);
} }
m_painter->setVideoProxy(proxy); 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() { int DisplayGL::framebufferHandle() {
return m_painter->glTex(); return m_painter->glTex();
} }
@ -481,9 +511,15 @@ void PainterGL::create() {
#if defined(BUILD_GLES2) || defined(BUILD_GLES3) #if defined(BUILD_GLES2) || defined(BUILD_GLES3)
if (m_supportsShaders) { if (m_supportsShaders) {
QOpenGLFunctions_Baseline* fn = m_gl->versionFunctions<QOpenGLFunctions_Baseline>();
gl2Backend = static_cast<mGLES2Context*>(malloc(sizeof(mGLES2Context))); gl2Backend = static_cast<mGLES2Context*>(malloc(sizeof(mGLES2Context)));
mGLES2ContextCreate(gl2Backend); mGLES2ContextCreate(gl2Backend);
m_backend = &gl2Backend->d; 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 #endif
@ -503,10 +539,10 @@ void PainterGL::create() {
painter->makeCurrent(); painter->makeCurrent();
#if defined(BUILD_GLES2) || defined(BUILD_GLES3) #if defined(BUILD_GLES2) || defined(BUILD_GLES3)
mGLES2Context* gl2Backend = reinterpret_cast<mGLES2Context*>(painter->m_backend);
if (painter->m_widget && painter->supportsShaders()) { if (painter->m_widget && painter->supportsShaders()) {
QOpenGLFunctions_Baseline* fn = painter->m_gl->versionFunctions<QOpenGLFunctions_Baseline>(); QOpenGLFunctions_Baseline* fn = painter->m_gl->versionFunctions<QOpenGLFunctions_Baseline>();
fn->glFinish(); fn->glFinish();
mGLES2Context* gl2Backend = reinterpret_cast<mGLES2Context*>(painter->m_backend);
painter->m_widget->setTex(painter->m_finalTex[painter->m_finalTexIdx]); painter->m_widget->setTex(painter->m_finalTex[painter->m_finalTexIdx]);
painter->m_finalTexIdx ^= 1; painter->m_finalTexIdx ^= 1;
gl2Backend->finalShader.tex = painter->m_finalTex[painter->m_finalTexIdx]; gl2Backend->finalShader.tex = painter->m_finalTex[painter->m_finalTexIdx];
@ -540,6 +576,8 @@ void PainterGL::create() {
m_backend->filter = false; m_backend->filter = false;
m_backend->lockAspectRatio = false; m_backend->lockAspectRatio = false;
m_backend->interframeBlending = false; m_backend->interframeBlending = false;
emit created();
} }
void PainterGL::destroy() { void PainterGL::destroy() {
@ -548,9 +586,11 @@ void PainterGL::destroy() {
} }
makeCurrent(); makeCurrent();
#if defined(BUILD_GLES2) || defined(BUILD_GLES3) #if defined(BUILD_GLES2) || defined(BUILD_GLES3)
QOpenGLFunctions_Baseline* fn = m_gl->versionFunctions<QOpenGLFunctions_Baseline>();
if (m_shader.passes) { if (m_shader.passes) {
mGLES2ShaderFree(&m_shader); mGLES2ShaderFree(&m_shader);
} }
fn->glDeleteTextures(m_bridgeTexes.size(), m_bridgeTexes.data());
#endif #endif
m_backend->deinit(m_backend); m_backend->deinit(m_backend);
m_gl->doneCurrent(); m_gl->doneCurrent();
@ -636,6 +676,7 @@ void PainterGL::start() {
} }
#endif #endif
resizeContext(); resizeContext();
m_context->addFrameAction(std::bind(&PainterGL::swapTex, this));
m_buffer = nullptr; m_buffer = nullptr;
m_active = true; m_active = true;
@ -644,7 +685,7 @@ void PainterGL::start() {
} }
void PainterGL::draw() { void PainterGL::draw() {
if (!m_started || m_queue.isEmpty()) { if (!m_started || (m_queue.isEmpty() && m_queueTex.isEmpty())) {
return; return;
} }
@ -671,7 +712,7 @@ void PainterGL::draw() {
return; return;
} }
dequeue(); dequeue();
bool forceRedraw = !m_videoProxy; bool forceRedraw = true;
if (!m_delayTimer.isValid()) { if (!m_delayTimer.isValid()) {
m_delayTimer.start(); m_delayTimer.start();
} else { } else {
@ -725,11 +766,6 @@ void PainterGL::doStop() {
m_videoProxy->processData(); 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->clear(m_backend);
m_backend->swap(m_backend); m_backend->swap(m_backend);
} }
@ -759,9 +795,11 @@ void PainterGL::performDraw() {
} }
void PainterGL::enqueue(const uint32_t* backing) { void PainterGL::enqueue(const uint32_t* backing) {
if (!backing) {
return;
}
QMutexLocker locker(&m_mutex); QMutexLocker locker(&m_mutex);
uint32_t* buffer = nullptr; uint32_t* buffer = nullptr;
if (backing) {
if (m_free.isEmpty()) { if (m_free.isEmpty()) {
buffer = m_queue.dequeue(); buffer = m_queue.dequeue();
} else { } else {
@ -771,26 +809,46 @@ void PainterGL::enqueue(const uint32_t* backing) {
QSize size = m_context->screenDimensions(); QSize size = m_context->screenDimensions();
memcpy(buffer, backing, size.width() * size.height() * BYTES_PER_PIXEL); memcpy(buffer, backing, size.width() * size.height() * BYTES_PER_PIXEL);
} }
}
m_queue.enqueue(buffer); 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() { void PainterGL::dequeue() {
QMutexLocker locker(&m_mutex); QMutexLocker locker(&m_mutex);
if (m_queue.isEmpty()) { if (!m_queue.isEmpty()) {
return;
}
uint32_t* buffer = m_queue.dequeue(); uint32_t* buffer = m_queue.dequeue();
if (m_buffer) { if (m_buffer) {
m_free.append(m_buffer); m_free.append(m_buffer);
m_buffer = nullptr;
} }
m_buffer = buffer; m_buffer = buffer;
}
if (!m_queueTex.isEmpty()) {
if (m_bridgeTexOut != std::numeric_limits<GLuint>::max()) {
m_freeTex.enqueue(m_bridgeTexOut);
}
m_bridgeTexOut = m_queueTex.dequeue();
#if defined(BUILD_GLES2) || defined(BUILD_GLES3)
if (supportsShaders()) {
mGLES2Context* gl2Backend = reinterpret_cast<mGLES2Context*>(m_backend);
gl2Backend->tex = m_bridgeTexOut;
}
#endif
}
} }
void PainterGL::dequeueAll(bool keep) { void PainterGL::dequeueAll(bool keep) {
QMutexLocker locker(&m_mutex); QMutexLocker locker(&m_mutex);
uint32_t* buffer = 0; uint32_t* buffer = nullptr;
while (!m_queue.isEmpty()) { while (!m_queue.isEmpty()) {
buffer = m_queue.dequeue(); buffer = m_queue.dequeue();
if (keep) { if (keep) {
@ -802,6 +860,13 @@ void PainterGL::dequeueAll(bool keep) {
m_free.append(buffer); 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<GLuint>::max();
if (m_buffer && !keep) { if (m_buffer && !keep) {
m_free.append(m_buffer); m_free.append(m_buffer);
m_buffer = nullptr; m_buffer = nullptr;
@ -861,4 +926,29 @@ int PainterGL::glTex() {
#endif #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<QOpenGLFunctions_Baseline>();
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 #endif

View File

@ -112,6 +112,9 @@ protected:
virtual void paintEvent(QPaintEvent*) override { forceDraw(); } virtual void paintEvent(QPaintEvent*) override { forceDraw(); }
virtual void resizeEvent(QResizeEvent*) override; virtual void resizeEvent(QResizeEvent*) override;
private slots:
void setupProxyThread();
private: private:
void resizePainter(); void resizePainter();
bool shouldDisableUpdates(); bool shouldDisableUpdates();
@ -122,8 +125,11 @@ private:
bool m_hasStarted = false; bool m_hasStarted = false;
std::unique_ptr<PainterGL> m_painter; std::unique_ptr<PainterGL> m_painter;
QThread m_drawThread; QThread m_drawThread;
QThread m_proxyThread;
std::shared_ptr<CoreController> m_context; std::shared_ptr<CoreController> m_context;
mGLWidget* m_gl; mGLWidget* m_gl;
QOffscreenSurface m_proxySurface;
std::unique_ptr<QOpenGLContext> m_proxyContext;
}; };
class PainterGL : public QObject { class PainterGL : public QObject {
@ -137,15 +143,21 @@ public:
void setContext(std::shared_ptr<CoreController>); void setContext(std::shared_ptr<CoreController>);
void setMessagePainter(MessagePainter*); void setMessagePainter(MessagePainter*);
void enqueue(const uint32_t* backing); void enqueue(const uint32_t* backing);
void enqueue(GLuint tex);
void stop(); void stop();
bool supportsShaders() const { return m_supportsShaders; } bool supportsShaders() const { return m_supportsShaders; }
int glTex(); int glTex();
QOpenGLContext* shareContext();
void setVideoProxy(std::shared_ptr<VideoProxy>); void setVideoProxy(std::shared_ptr<VideoProxy>);
void interrupt(); void interrupt();
// Run on main thread
void swapTex();
public slots: public slots:
void create(); void create();
void destroy(); void destroy();
@ -163,13 +175,16 @@ public slots:
void showFrameCounter(bool enable); void showFrameCounter(bool enable);
void filter(bool filter); void filter(bool filter);
void resizeContext(); void resizeContext();
void updateFramebufferHandle();
void setShaders(struct VDir*); void setShaders(struct VDir*);
void clearShaders(); void clearShaders();
VideoShader* shaders(); VideoShader* shaders();
signals: signals:
void created();
void started(); void started();
void texSwapped();
private slots: private slots:
void doStop(); void doStop();
@ -184,6 +199,14 @@ private:
QList<uint32_t*> m_free; QList<uint32_t*> m_free;
QQueue<uint32_t*> m_queue; QQueue<uint32_t*> m_queue;
uint32_t* m_buffer = nullptr; uint32_t* m_buffer = nullptr;
std::array<GLuint, 3> m_bridgeTexes;
QQueue<GLuint> m_freeTex;
QQueue<GLuint> m_queueTex;
GLuint m_bridgeTexIn = std::numeric_limits<GLuint>::max();
GLuint m_bridgeTexOut = std::numeric_limits<GLuint>::max();
QPainter m_painter; QPainter m_painter;
QMutex m_mutex; QMutex m_mutex;
QWindow* m_window; QWindow* m_window;