mirror of https://github.com/mgba-emu/mgba.git
Qt: Move OpenGL proxy onto its own thread (fixes #2493)
This commit is contained in:
parent
0271f12280
commit
6aa558c4a0
1
CHANGES
1
CHANGES
|
@ -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:
|
||||||
|
|
|
@ -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,38 +795,60 @@ 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 {
|
buffer = m_free.takeLast();
|
||||||
buffer = m_free.takeLast();
|
}
|
||||||
}
|
if (buffer) {
|
||||||
if (buffer) {
|
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();
|
||||||
|
if (m_buffer) {
|
||||||
|
m_free.append(m_buffer);
|
||||||
|
}
|
||||||
|
m_buffer = buffer;
|
||||||
}
|
}
|
||||||
uint32_t* buffer = m_queue.dequeue();
|
|
||||||
if (m_buffer) {
|
if (!m_queueTex.isEmpty()) {
|
||||||
m_free.append(m_buffer);
|
if (m_bridgeTexOut != std::numeric_limits<GLuint>::max()) {
|
||||||
m_buffer = nullptr;
|
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
|
||||||
}
|
}
|
||||||
m_buffer = buffer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in New Issue