Video drawing in separate thread

This commit is contained in:
Jeffrey Pfau 2014-01-30 03:49:59 -08:00
parent 2f98f542e5
commit 6407ad3adc
6 changed files with 159 additions and 39 deletions

View File

@ -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();
}

View File

@ -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;
};
}

View File

@ -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) {

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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: