all repos — mgba @ 6407ad3adcdc6d8241416a4a4a44a62a1ffeaa1c

mGBA Game Boy Advance Emulator

Video drawing in separate thread
Jeffrey Pfau jeffrey@endrift.com
Thu, 30 Jan 2014 03:49:59 -0800
commit

6407ad3adcdc6d8241416a4a4a44a62a1ffeaa1c

parent

2f98f542e5cb5b2018eb9961e62b41b96912c6e7

M src/platform/qt/Display.cppsrc/platform/qt/Display.cpp

@@ -1,6 +1,10 @@

#include "Display.h" -#include <QTimer> +#include <QResizeEvent> + +extern "C" { +#include "gba-thread.h" +} using namespace QGBA;

@@ -18,39 +22,106 @@ 1, 1,

0, 1 }; -Display::Display(QWidget* parent) : QGLWidget(QGLFormat(QGL::Rgba | QGL::DoubleBuffer), parent) { +Display::Display(QWidget* parent) + : QGLWidget(QGLFormat(QGL::Rgba | QGL::SingleBuffer), parent) + , m_painter(nullptr) + , m_drawThread(nullptr) +{ setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored); - QTimer* timer = new QTimer(this); - connect(timer, SIGNAL(timeout()), this, SLOT(updateGL())); - timer->setInterval(0); - timer->start(); + setAutoBufferSwap(false); +} + +void Display::startDrawing(const uint32_t* buffer, GBAThread* thread) { + m_drawThread = new QThread(this); + m_painter = new Painter(this); + m_painter->setGLContext(this); + m_painter->setContext(thread); + m_painter->setBacking(buffer); + m_painter->moveToThread(m_drawThread); + doneCurrent(); + context()->moveToThread(m_drawThread); + connect(m_drawThread, SIGNAL(started()), m_painter, SLOT(start())); + m_drawThread->start(); +} + +void Display::stopDrawing() { + if (m_drawThread) { + QMetaObject::invokeMethod(m_painter, "stop", Qt::BlockingQueuedConnection); + m_drawThread->exit(); + } +} + +void Display::resizeEvent(QResizeEvent* event) { + if (m_painter) { + m_painter->resize(event->size()); + } +} + +Painter::Painter(Display* parent) { + m_size = parent->size(); +} + +void Painter::setContext(GBAThread* context) { + m_context = context; +} + +void Painter::setBacking(const uint32_t* backing) { + m_backing = backing; +} + +void Painter::setGLContext(QGLWidget* context) { + m_gl = context; +} + +void Painter::resize(const QSize& size) { + m_size = size; } -void Display::initializeGL() { +void Painter::start() { + m_gl->makeCurrent(); glEnable(GL_TEXTURE_2D); glGenTextures(1, &m_tex); glBindTexture(GL_TEXTURE_2D, m_tex); glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); -} - -void Display::draw(const QImage& image) { - makeCurrent(); - glEnable(GL_TEXTURE_2D); - glBindTexture(GL_TEXTURE_2D, m_tex); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, image.constBits()); -} - -void Display::paintGL() { glEnableClientState(GL_TEXTURE_COORD_ARRAY); glEnableClientState(GL_VERTEX_ARRAY); glVertexPointer(2, GL_INT, 0, _glVertices); glTexCoordPointer(2, GL_INT, 0, _glTexCoords); - glMatrixMode (GL_PROJECTION); + glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(0, 240, 160, 0, 0, 1); - glEnable(GL_TEXTURE_2D); - glBindTexture(GL_TEXTURE_2D, m_tex); - glDrawArrays(GL_TRIANGLE_FAN, 0, 4); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + m_gl->doneCurrent(); + + m_drawTimer = new QTimer; + m_drawTimer->moveToThread(QThread::currentThread()); + m_drawTimer->setInterval(0); + connect(m_drawTimer, SIGNAL(timeout()), this, SLOT(draw())); + m_drawTimer->start(); +} + +void Painter::draw() { + m_gl->makeCurrent(); + if (GBASyncWaitFrameStart(&m_context->sync, m_context->frameskip)) { + glViewport(0, 0, m_size.width() * m_gl->devicePixelRatio(), m_size.height() * m_gl->devicePixelRatio()); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, m_backing); + glDrawArrays(GL_TRIANGLE_FAN, 0, 4); + if (m_context->sync.videoFrameWait) { + glFlush(); + } + } + GBASyncWaitFrameEnd(&m_context->sync); + m_gl->swapBuffers(); + m_gl->doneCurrent(); +} + +void Painter::stop() { + m_drawTimer->stop(); + delete m_drawTimer; + m_gl->makeCurrent(); + glDeleteTextures(1, &m_tex); + m_gl->doneCurrent(); }
M src/platform/qt/Display.hsrc/platform/qt/Display.h

@@ -2,24 +2,56 @@ #ifndef QGBA_DISPLAY

#define QGBA_DISPLAY #include <QGLWidget> +#include <QThread> +#include <QTimer> + +struct GBAThread; namespace QGBA { +class Painter; class Display : public QGLWidget { Q_OBJECT public: Display(QWidget* parent = 0); +public slots: + void startDrawing(const uint32_t* buffer, GBAThread* context); + void stopDrawing(); + protected: - virtual void initializeGL(); + virtual void paintEvent(QPaintEvent*) {}; + virtual void resizeEvent(QResizeEvent*); + +private: + Painter* m_painter; + QThread* m_drawThread; +}; + +class Painter : public QObject { +Q_OBJECT + +public: + Painter(Display* parent); + + void setContext(GBAThread*); + void setBacking(const uint32_t*); + void setGLContext(QGLWidget*); + void resize(const QSize& size); public slots: - void draw(const QImage& image); - void paintGL(); + void draw(); + void start(); + void stop(); private: + QTimer* m_drawTimer; + GBAThread* m_context; + const uint32_t* m_backing; GLuint m_tex; + QGLWidget* m_gl; + QSize m_size; }; }
M src/platform/qt/GameController.cppsrc/platform/qt/GameController.cpp

@@ -9,13 +9,13 @@ using namespace QGBA;

GameController::GameController(QObject* parent) : QObject(parent) - , m_drawContext(256, 256, QImage::Format_RGB32) + , m_drawContext(new uint32_t[256 * 256]) , m_audioContext(0) { m_renderer = new GBAVideoSoftwareRenderer; GBAVideoSoftwareRendererCreate(m_renderer); - m_renderer->outputBuffer = (color_t*) m_drawContext.bits(); - m_renderer->outputBufferStride = m_drawContext.bytesPerLine() / 4; + m_renderer->outputBuffer = (color_t*) m_drawContext; + m_renderer->outputBufferStride = 256; m_threadContext = { .useDebugger = 0, .frameskip = 0,

@@ -43,6 +43,9 @@ };

} GameController::~GameController() { + if (GBAThreadIsPaused(&m_threadContext)) { + GBAThreadUnpause(&m_threadContext); + } GBAThreadEnd(&m_threadContext); GBAThreadJoin(&m_threadContext); delete m_renderer;

@@ -60,7 +63,7 @@

m_threadContext.fd = m_rom->handle(); m_threadContext.fname = path.toLocal8Bit().constData(); GBAThreadStart(&m_threadContext); - emit gameStarted(); + emit gameStarted(&m_threadContext); } void GameController::setPaused(bool paused) {
M src/platform/qt/GameController.hsrc/platform/qt/GameController.h

@@ -25,10 +25,12 @@ public:

GameController(QObject* parent = 0); ~GameController(); + const uint32_t* drawContext() const { return m_drawContext; } + signals: - void frameAvailable(const QImage&); + void frameAvailable(const uint32_t*); void audioDeviceAvailable(GBAAudio*); - void gameStarted(); + void gameStarted(GBAThread*); public slots: void loadGame(const QString& path);

@@ -40,7 +42,7 @@

private: void setupAudio(GBAAudio* audio); - QImage m_drawContext; + uint32_t* m_drawContext; AudioDevice* m_audioContext; GBAThread m_threadContext; GBAVideoSoftwareRenderer* m_renderer;
M src/platform/qt/Window.cppsrc/platform/qt/Window.cpp

@@ -12,11 +12,12 @@ setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);

setMinimumSize(240, 160); m_controller = new GameController(this); - m_display = new Display(this); + m_display = new Display(); setCentralWidget(m_display); - connect(m_controller, SIGNAL(frameAvailable(const QImage&)), m_display, SLOT(draw(const QImage&))); connect(m_controller, SIGNAL(audioDeviceAvailable(GBAAudio*)), this, SLOT(setupAudio(GBAAudio*))); - connect(m_controller, SIGNAL(gameStarted()), this, SLOT(gameStarted())); + connect(m_controller, SIGNAL(gameStarted(GBAThread*)), this, SLOT(gameStarted(GBAThread*))); + connect(this, SIGNAL(startDrawing(const uint32_t*, GBAThread*)), m_display, SLOT(startDrawing(const uint32_t*, GBAThread*)), Qt::QueuedConnection); + connect(this, SIGNAL(shutdown()), m_display, SLOT(stopDrawing())); setupMenu(menuBar()); }

@@ -93,8 +94,14 @@ m_controller->keyReleased(key);

event->accept(); } -void Window::gameStarted() { - foreach (QAction* action, m_gameActions) { +void Window::closeEvent(QCloseEvent* event) { + emit shutdown(); + QMainWindow::closeEvent(event); +} + +void Window::gameStarted(GBAThread* context) { + emit startDrawing(m_controller->drawContext(), context); + foreach (QAction* action, m_gameActions) { action->setDisabled(false); } }
M src/platform/qt/Window.hsrc/platform/qt/Window.h

@@ -20,15 +20,20 @@ public:

Window(QWidget* parent = 0); static GBAKey mapKey(int qtKey); +signals: + void startDrawing(const uint32_t*, GBAThread*); + void shutdown(); + public slots: void selectROM(); protected: virtual void keyPressEvent(QKeyEvent* event); virtual void keyReleaseEvent(QKeyEvent* event); + virtual void closeEvent(QCloseEvent*) override; private slots: - void gameStarted(); + void gameStarted(GBAThread*); void setupAudio(GBAAudio*); private: