mirror of https://github.com/mgba-emu/mgba.git
Video drawing in separate thread
This commit is contained in:
parent
2f98f542e5
commit
6407ad3adc
|
@ -1,6 +1,10 @@
|
|||
#include "Display.h"
|
||||
|
||||
#include <QTimer>
|
||||
#include <QResizeEvent>
|
||||
|
||||
extern "C" {
|
||||
#include "gba-thread.h"
|
||||
}
|
||||
|
||||
using namespace QGBA;
|
||||
|
||||
|
@ -18,39 +22,106 @@ static const GLint _glTexCoords[] = {
|
|||
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::initializeGL() {
|
||||
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 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();
|
||||
}
|
||||
|
|
|
@ -2,24 +2,56 @@
|
|||
#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);
|
||||
|
||||
protected:
|
||||
virtual void initializeGL();
|
||||
|
||||
public slots:
|
||||
void draw(const QImage& image);
|
||||
void paintGL();
|
||||
void startDrawing(const uint32_t* buffer, GBAThread* context);
|
||||
void stopDrawing();
|
||||
|
||||
protected:
|
||||
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();
|
||||
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;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -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(QObject* parent)
|
|||
}
|
||||
|
||||
GameController::~GameController() {
|
||||
if (GBAThreadIsPaused(&m_threadContext)) {
|
||||
GBAThreadUnpause(&m_threadContext);
|
||||
}
|
||||
GBAThreadEnd(&m_threadContext);
|
||||
GBAThreadJoin(&m_threadContext);
|
||||
delete m_renderer;
|
||||
|
@ -60,7 +63,7 @@ void GameController::loadGame(const QString& path) {
|
|||
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) {
|
||||
|
|
|
@ -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 @@ public slots:
|
|||
private:
|
||||
void setupAudio(GBAAudio* audio);
|
||||
|
||||
QImage m_drawContext;
|
||||
uint32_t* m_drawContext;
|
||||
AudioDevice* m_audioContext;
|
||||
GBAThread m_threadContext;
|
||||
GBAVideoSoftwareRenderer* m_renderer;
|
||||
|
|
|
@ -12,11 +12,12 @@ Window::Window(QWidget* parent) : QMainWindow(parent) {
|
|||
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 @@ void Window::keyReleaseEvent(QKeyEvent* event) {
|
|||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
|
Loading…
Reference in New Issue