mirror of https://github.com/mgba-emu/mgba.git
Qt: Add QOpenGLWidget cross-thread codepath for macOS (fixes #1754)
This commit is contained in:
parent
c4e481c110
commit
b6e2faaba9
1
CHANGES
1
CHANGES
|
@ -35,6 +35,7 @@ Misc:
|
|||
- Qt: Save positions of multiplayer windows (closes mgba.io/i/2128)
|
||||
- Qt: Add optional frame counter to OSD (closes mgba.io/i/1728)
|
||||
- Qt: Add optional emulation-related information on reset (closes mgba.io/i/1780)
|
||||
- Qt: Add QOpenGLWidget cross-thread codepath for macOS (fixes mgba.io/i/1754)
|
||||
- Windows: Attach to console if present
|
||||
|
||||
0.9.3: (2021-12-17)
|
||||
|
|
|
@ -235,6 +235,7 @@ static void mGLES2ContextResized(struct VideoBackend* v, unsigned w, unsigned h)
|
|||
context->shaders[n].dirty = true;
|
||||
}
|
||||
}
|
||||
context->finalShader.dirty = true;
|
||||
glBindTexture(GL_TEXTURE_2D, context->finalShader.tex);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, drawW, drawH, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, context->finalShader.fbo);
|
||||
|
@ -375,7 +376,6 @@ void mGLES2ContextDrawFrame(struct VideoBackend* v) {
|
|||
glGetIntegerv(GL_VIEWPORT, viewport);
|
||||
|
||||
context->finalShader.filter = v->filter;
|
||||
context->finalShader.dirty = true;
|
||||
_drawShader(context, &context->initialShader);
|
||||
if (v->interframeBlending) {
|
||||
context->interframeShader.blend = true;
|
||||
|
@ -437,7 +437,10 @@ void mGLES2ContextCreate(struct mGLES2Context* context) {
|
|||
}
|
||||
|
||||
void mGLES2ContextUseFramebuffer(struct mGLES2Context* context) {
|
||||
if (!context->finalShader.fbo) {
|
||||
glGenFramebuffers(1, &context->finalShader.fbo);
|
||||
}
|
||||
if (!context->finalShader.tex) {
|
||||
glGenTextures(1, &context->finalShader.tex);
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, context->finalShader.tex);
|
||||
|
@ -446,6 +449,7 @@ void mGLES2ContextUseFramebuffer(struct mGLES2Context* context) {
|
|||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
}
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, context->finalShader.fbo);
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, context->finalShader.tex, 0);
|
||||
|
|
|
@ -4,6 +4,7 @@ set(CMAKE_CXX_EXTENSIONS OFF)
|
|||
|
||||
set(PLATFORM_SRC)
|
||||
set(QT_STATIC OFF)
|
||||
set(QT_DEFINES)
|
||||
|
||||
if(BUILD_SDL)
|
||||
add_definitions(-DBUILD_SDL)
|
||||
|
@ -35,6 +36,11 @@ if(NOT Qt5Widgets_FOUND)
|
|||
endif()
|
||||
|
||||
if(APPLE)
|
||||
execute_process(COMMAND xcrun --show-sdk-version OUTPUT_VARIABLE MACOSX_SDK)
|
||||
if(MACOSX_SDK VERSION_GREATER 10.14)
|
||||
list(APPEND QT_DEFINES USE_SHARE_WIDGET)
|
||||
endif()
|
||||
|
||||
if(Qt5Widgets_VERSION MATCHES "^5.1[0-9]")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mmacosx-version-min=10.8")
|
||||
else()
|
||||
|
@ -183,7 +189,6 @@ if(M_CORE_GB)
|
|||
list(APPEND PLATFORM_SRC ${GB_SRC})
|
||||
endif()
|
||||
|
||||
set(QT_DEFINES)
|
||||
if(Qt5Multimedia_FOUND)
|
||||
list(APPEND AUDIO_SRC
|
||||
AudioProcessorQt.cpp
|
||||
|
|
|
@ -38,7 +38,9 @@ Display* Display::create(QWidget* parent) {
|
|||
format.setVersion(3, 2);
|
||||
}
|
||||
format.setProfile(QSurfaceFormat::CoreProfile);
|
||||
if (!DisplayGL::supportsFormat(format)) {
|
||||
if (DisplayGL::supportsFormat(format)) {
|
||||
QSurfaceFormat::setDefaultFormat(format);
|
||||
} else {
|
||||
#ifdef BUILD_GL
|
||||
LOG(QT, WARN) << ("Failed to create an OpenGL Core context, trying old-style...");
|
||||
format.setVersion(1, 4);
|
||||
|
|
|
@ -9,14 +9,14 @@
|
|||
|
||||
#include <QApplication>
|
||||
#include <QMutexLocker>
|
||||
#include <QOffscreenSurface>
|
||||
#include <QOpenGLContext>
|
||||
#include <QOpenGLFunctions>
|
||||
#include <QOpenGLFunctions_3_2_Core>
|
||||
#include <QOpenGLPaintDevice>
|
||||
#include <QResizeEvent>
|
||||
#include <QScreen>
|
||||
#include <QTimer>
|
||||
#include <QWindow>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include <cmath>
|
||||
|
||||
|
@ -43,13 +43,96 @@ uint qHash(const QSurfaceFormat& format, uint seed) {
|
|||
return qHash(representation, seed);
|
||||
}
|
||||
|
||||
void mGLWidget::initializeGL() {
|
||||
m_vao.create();
|
||||
m_program.create();
|
||||
|
||||
m_program.addShaderFromSourceCode(QOpenGLShader::Vertex, R"(#version 150 core
|
||||
in vec4 position;
|
||||
out vec2 texCoord;
|
||||
void main() {
|
||||
gl_Position = position;
|
||||
texCoord = (position.st + 1.0) * 0.5;
|
||||
})");
|
||||
|
||||
m_program.addShaderFromSourceCode(QOpenGLShader::Fragment, R"(#version 150 core
|
||||
in vec2 texCoord;
|
||||
out vec4 color;
|
||||
uniform sampler2D tex;
|
||||
void main() {
|
||||
color = vec4(texture(tex, texCoord).rgb, 1.0);
|
||||
})");
|
||||
|
||||
m_program.link();
|
||||
m_program.setUniformValue("tex", 0);
|
||||
m_positionLocation = m_program.attributeLocation("position");
|
||||
|
||||
connect(&m_refresh, &QTimer::timeout, this, static_cast<void (QWidget::*)()>(&QWidget::update));
|
||||
}
|
||||
|
||||
void mGLWidget::finalizeVAO() {
|
||||
QOpenGLFunctions_3_2_Core* fn = context()->versionFunctions<QOpenGLFunctions_3_2_Core>();
|
||||
fn->glGetError(); // Clear the error
|
||||
m_vao.bind();
|
||||
fn->glBindBuffer(GL_ARRAY_BUFFER, m_vbo);
|
||||
fn->glEnableVertexAttribArray(m_positionLocation);
|
||||
fn->glVertexAttribPointer(m_positionLocation, 2, GL_FLOAT, GL_FALSE, 0, NULL);
|
||||
m_vao.release();
|
||||
if (fn->glGetError() == GL_NO_ERROR) {
|
||||
m_vaoDone = true;
|
||||
}
|
||||
}
|
||||
|
||||
void mGLWidget::paintGL() {
|
||||
if (!m_vaoDone) {
|
||||
finalizeVAO();
|
||||
}
|
||||
QOpenGLFunctions_3_2_Core* fn = context()->versionFunctions<QOpenGLFunctions_3_2_Core>();
|
||||
m_program.bind();
|
||||
m_vao.bind();
|
||||
fn->glBindTexture(GL_TEXTURE_2D, m_tex);
|
||||
fn->glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
|
||||
fn->glBindTexture(GL_TEXTURE_2D, 0);
|
||||
m_vao.release();
|
||||
m_program.release();
|
||||
|
||||
// TODO: Better timing
|
||||
++m_refreshResidue;
|
||||
if (m_refreshResidue == 3) {
|
||||
m_refresh.start(16);
|
||||
m_refreshResidue = 0;
|
||||
} else {
|
||||
m_refresh.start(17);
|
||||
}
|
||||
}
|
||||
|
||||
DisplayGL::DisplayGL(const QSurfaceFormat& format, QWidget* parent)
|
||||
: Display(parent)
|
||||
{
|
||||
setAttribute(Qt::WA_NativeWindow);
|
||||
window()->windowHandle()->setFormat(format);
|
||||
windowHandle()->create();
|
||||
|
||||
m_painter = std::make_unique<PainterGL>(windowHandle(), format);
|
||||
#ifdef USE_SHARE_WIDGET
|
||||
bool useShareWidget = true;
|
||||
#else
|
||||
// TODO: Does using this on Wayland help?
|
||||
bool useShareWidget = false;
|
||||
#endif
|
||||
|
||||
if (useShareWidget) {
|
||||
m_gl = new mGLWidget;
|
||||
m_gl->setAttribute(Qt::WA_NativeWindow);
|
||||
m_gl->setFormat(format);
|
||||
QBoxLayout* layout = new QVBoxLayout;
|
||||
layout->addWidget(m_gl);
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
setLayout(layout);
|
||||
} else {
|
||||
m_gl = nullptr;
|
||||
}
|
||||
|
||||
m_painter = std::make_unique<PainterGL>(windowHandle(), m_gl, format);
|
||||
m_drawThread.setObjectName("Painter Thread");
|
||||
m_painter->setThread(&m_drawThread);
|
||||
|
||||
|
@ -106,7 +189,9 @@ void DisplayGL::startDrawing(std::shared_ptr<CoreController> controller) {
|
|||
|
||||
CoreController::Interrupter interrupter(controller);
|
||||
QMetaObject::invokeMethod(m_painter.get(), "start");
|
||||
if (!m_gl) {
|
||||
setUpdatesEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
bool DisplayGL::supportsFormat(const QSurfaceFormat& format) {
|
||||
|
@ -188,7 +273,9 @@ void DisplayGL::unpauseDrawing() {
|
|||
m_isDrawing = true;
|
||||
QMetaObject::invokeMethod(m_painter.get(), "unpause", Qt::BlockingQueuedConnection);
|
||||
#ifndef Q_OS_MAC
|
||||
if (!m_gl) {
|
||||
setUpdatesEnabled(false);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
@ -278,10 +365,21 @@ int DisplayGL::framebufferHandle() {
|
|||
return m_painter->glTex();
|
||||
}
|
||||
|
||||
PainterGL::PainterGL(QWindow* surface, const QSurfaceFormat& format)
|
||||
: m_surface(surface)
|
||||
PainterGL::PainterGL(QWindow* window, mGLWidget* widget, const QSurfaceFormat& format)
|
||||
: m_window(window)
|
||||
, m_format(format)
|
||||
, m_widget(widget)
|
||||
{
|
||||
if (widget) {
|
||||
m_format = widget->format();
|
||||
QOffscreenSurface* surface = new QOffscreenSurface;
|
||||
surface->setScreen(window->screen());
|
||||
surface->setFormat(m_format);
|
||||
surface->create();
|
||||
m_surface = surface;
|
||||
} else {
|
||||
m_surface = m_window;
|
||||
}
|
||||
m_supportsShaders = m_format.version() >= qMakePair(2, 0);
|
||||
for (auto& buf : m_buffers) {
|
||||
m_free.append(&buf.front());
|
||||
|
@ -311,6 +409,9 @@ void PainterGL::makeCurrent() {
|
|||
void PainterGL::create() {
|
||||
m_gl = std::make_unique<QOpenGLContext>();
|
||||
m_gl->setFormat(m_format);
|
||||
if (m_widget) {
|
||||
m_gl->setShareContext(m_widget->context());
|
||||
}
|
||||
m_gl->create();
|
||||
makeCurrent();
|
||||
|
||||
|
@ -321,7 +422,7 @@ void PainterGL::create() {
|
|||
mGLES2Context* gl2Backend;
|
||||
#endif
|
||||
|
||||
m_window = std::make_unique<QOpenGLPaintDevice>();
|
||||
m_paintDev = std::make_unique<QOpenGLPaintDevice>();
|
||||
|
||||
#if defined(BUILD_GLES2) || defined(BUILD_GLES3)
|
||||
auto version = m_format.version();
|
||||
|
@ -346,11 +447,36 @@ void PainterGL::create() {
|
|||
}
|
||||
painter->m_gl->swapBuffers(painter->m_surface);
|
||||
painter->makeCurrent();
|
||||
|
||||
if (painter->m_widget && painter->supportsShaders()) {
|
||||
QOpenGLFunctions_3_2_Core* fn = painter->m_gl->versionFunctions<QOpenGLFunctions_3_2_Core>();
|
||||
fn->glFinish();
|
||||
mGLES2Context* gl2Backend = reinterpret_cast<mGLES2Context*>(painter->m_backend);
|
||||
painter->m_widget->setTex(painter->m_finalTex[painter->m_finalTexIdx]);
|
||||
painter->m_finalTexIdx ^= 1;
|
||||
gl2Backend->finalShader.tex = painter->m_finalTex[painter->m_finalTexIdx];
|
||||
mGLES2ContextUseFramebuffer(gl2Backend);
|
||||
}
|
||||
};
|
||||
|
||||
m_backend->init(m_backend, 0);
|
||||
#if defined(BUILD_GLES2) || defined(BUILD_GLES3)
|
||||
if (m_supportsShaders) {
|
||||
if (m_widget) {
|
||||
m_widget->setVBO(gl2Backend->vbo);
|
||||
|
||||
gl2Backend->finalShader.tex = 0;
|
||||
mGLES2ContextUseFramebuffer(gl2Backend);
|
||||
m_finalTex[0] = gl2Backend->finalShader.tex;
|
||||
|
||||
gl2Backend->finalShader.tex = 0;
|
||||
mGLES2ContextUseFramebuffer(gl2Backend);
|
||||
m_finalTex[1] = gl2Backend->finalShader.tex;
|
||||
|
||||
m_finalTexIdx = 0;
|
||||
gl2Backend->finalShader.tex = m_finalTex[m_finalTexIdx];
|
||||
m_widget->setTex(m_finalTex[m_finalTexIdx]);
|
||||
}
|
||||
m_shader.preprocessShader = static_cast<void*>(&reinterpret_cast<mGLES2Context*>(m_backend)->initialShader);
|
||||
}
|
||||
#endif
|
||||
|
@ -373,7 +499,7 @@ void PainterGL::destroy() {
|
|||
#endif
|
||||
m_backend->deinit(m_backend);
|
||||
m_gl->doneCurrent();
|
||||
m_window.reset();
|
||||
m_paintDev.reset();
|
||||
m_gl.reset();
|
||||
|
||||
free(m_backend);
|
||||
|
@ -408,10 +534,10 @@ void PainterGL::setMessagePainter(MessagePainter* messagePainter) {
|
|||
}
|
||||
|
||||
void PainterGL::resize(const QSize& size) {
|
||||
qreal r = m_surface->devicePixelRatio();
|
||||
qreal r = m_window->devicePixelRatio();
|
||||
m_size = size;
|
||||
m_window->setSize(m_size * r);
|
||||
m_window->setDevicePixelRatio(r);
|
||||
m_paintDev->setSize(m_size * r);
|
||||
m_paintDev->setDevicePixelRatio(r);
|
||||
if (m_started && !m_active) {
|
||||
forceDraw();
|
||||
}
|
||||
|
@ -472,7 +598,7 @@ void PainterGL::draw() {
|
|||
if (!sync->audioWait && !sync->videoFrameWait) {
|
||||
return;
|
||||
}
|
||||
if (m_delayTimer.elapsed() >= 1000 / m_surface->screen()->refreshRate()) {
|
||||
if (m_delayTimer.elapsed() >= 1000 / m_window->screen()->refreshRate()) {
|
||||
return;
|
||||
}
|
||||
if (!m_drawTimer.isActive()) {
|
||||
|
@ -492,7 +618,7 @@ void PainterGL::draw() {
|
|||
forceRedraw = sync->videoFrameWait;
|
||||
}
|
||||
if (!forceRedraw) {
|
||||
forceRedraw = m_delayTimer.nsecsElapsed() + 1000000 >= 1000000000 / m_surface->screen()->refreshRate();
|
||||
forceRedraw = m_delayTimer.nsecsElapsed() + 1000000 >= 1000000000 / m_window->screen()->refreshRate();
|
||||
}
|
||||
}
|
||||
mCoreSyncWaitFrameEnd(sync);
|
||||
|
@ -507,7 +633,7 @@ void PainterGL::draw() {
|
|||
void PainterGL::forceDraw() {
|
||||
performDraw();
|
||||
if (!m_context->thread()->impl->sync.audioWait && !m_context->thread()->impl->sync.videoFrameWait) {
|
||||
if (m_delayTimer.elapsed() < 1000 / m_surface->screen()->refreshRate()) {
|
||||
if (m_delayTimer.elapsed() < 1000 / m_window->screen()->refreshRate()) {
|
||||
return;
|
||||
}
|
||||
m_delayTimer.restart();
|
||||
|
@ -532,7 +658,7 @@ void PainterGL::stop() {
|
|||
}
|
||||
if (m_videoProxy) {
|
||||
m_videoProxy->reset();
|
||||
m_videoProxy->moveToThread(m_surface->thread());
|
||||
m_videoProxy->moveToThread(m_window->thread());
|
||||
m_videoProxy.reset();
|
||||
}
|
||||
m_backend->clear(m_backend);
|
||||
|
@ -550,14 +676,14 @@ void PainterGL::unpause() {
|
|||
}
|
||||
|
||||
void PainterGL::performDraw() {
|
||||
float r = m_surface->devicePixelRatio();
|
||||
float r = m_window->devicePixelRatio();
|
||||
m_backend->resized(m_backend, m_size.width() * r, m_size.height() * r);
|
||||
if (m_buffer) {
|
||||
m_backend->postFrame(m_backend, m_buffer);
|
||||
}
|
||||
m_backend->drawFrame(m_backend);
|
||||
if (m_showOSD && m_messagePainter) {
|
||||
m_painter.begin(m_window.get());
|
||||
m_painter.begin(m_paintDev.get());
|
||||
m_messagePainter->paint(&m_painter);
|
||||
m_painter.end();
|
||||
}
|
||||
|
|
|
@ -21,7 +21,11 @@
|
|||
#include <QHash>
|
||||
#include <QList>
|
||||
#include <QMouseEvent>
|
||||
#include <QOffscreenSurface>
|
||||
#include <QOpenGLContext>
|
||||
#include <QOpenGLShaderProgram>
|
||||
#include <QOpenGLVertexArrayObject>
|
||||
#include <QOpenGLWidget>
|
||||
#include <QPainter>
|
||||
#include <QQueue>
|
||||
#include <QThread>
|
||||
|
@ -35,11 +39,37 @@
|
|||
#include "platform/video-backend.h"
|
||||
|
||||
class QOpenGLPaintDevice;
|
||||
class QOpenGLWidget;
|
||||
|
||||
uint qHash(const QSurfaceFormat&, uint seed = 0);
|
||||
|
||||
namespace QGBA {
|
||||
|
||||
class mGLWidget : public QOpenGLWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
void setTex(GLuint tex) { m_tex = tex; }
|
||||
void setVBO(GLuint vbo) { m_vbo = vbo; }
|
||||
void finalizeVAO();
|
||||
|
||||
protected:
|
||||
void initializeGL() override;
|
||||
void paintGL() override;
|
||||
|
||||
private:
|
||||
GLuint m_tex;
|
||||
GLuint m_vbo;
|
||||
|
||||
bool m_vaoDone = false;
|
||||
QOpenGLVertexArrayObject m_vao;
|
||||
QOpenGLShaderProgram m_program;
|
||||
GLuint m_positionLocation;
|
||||
|
||||
QTimer m_refresh;
|
||||
int m_refreshResidue = 0;
|
||||
};
|
||||
|
||||
class PainterGL;
|
||||
class DisplayGL : public Display {
|
||||
Q_OBJECT
|
||||
|
@ -88,13 +118,14 @@ private:
|
|||
std::unique_ptr<PainterGL> m_painter;
|
||||
QThread m_drawThread;
|
||||
std::shared_ptr<CoreController> m_context;
|
||||
mGLWidget* m_gl;
|
||||
};
|
||||
|
||||
class PainterGL : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
PainterGL(QWindow* surface, const QSurfaceFormat& format);
|
||||
PainterGL(QWindow* surface, mGLWidget* widget, const QSurfaceFormat& format);
|
||||
~PainterGL();
|
||||
|
||||
void setThread(QThread*);
|
||||
|
@ -146,10 +177,14 @@ private:
|
|||
uint32_t* m_buffer = nullptr;
|
||||
QPainter m_painter;
|
||||
QMutex m_mutex;
|
||||
QWindow* m_surface;
|
||||
QWindow* m_window;
|
||||
QSurface* m_surface;
|
||||
QSurfaceFormat m_format;
|
||||
std::unique_ptr<QOpenGLPaintDevice> m_window;
|
||||
std::unique_ptr<QOpenGLPaintDevice> m_paintDev;
|
||||
std::unique_ptr<QOpenGLContext> m_gl;
|
||||
int m_finalTexIdx = 0;
|
||||
GLuint m_finalTex[2];
|
||||
mGLWidget* m_widget;
|
||||
bool m_active = false;
|
||||
bool m_started = false;
|
||||
QTimer m_drawTimer;
|
||||
|
|
|
@ -754,7 +754,9 @@ void Window::focusInEvent(QFocusEvent*) {
|
|||
updateMultiplayerActive(true);
|
||||
}
|
||||
}
|
||||
if (m_display) {
|
||||
m_display->forceDraw();
|
||||
}
|
||||
}
|
||||
|
||||
void Window::focusOutEvent(QFocusEvent*) {
|
||||
|
|
Loading…
Reference in New Issue