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 "Display.h"
#include <QTimer> #include <QResizeEvent>
extern "C" {
#include "gba-thread.h"
}
using namespace QGBA; using namespace QGBA;
@ -18,31 +22,69 @@ static const GLint _glTexCoords[] = {
0, 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); setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);
QTimer* timer = new QTimer(this); setAutoBufferSwap(false);
connect(timer, SIGNAL(timeout()), this, SLOT(updateGL()));
timer->setInterval(0);
timer->start();
} }
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); glEnable(GL_TEXTURE_2D);
glGenTextures(1, &m_tex); glGenTextures(1, &m_tex);
glBindTexture(GL_TEXTURE_2D, m_tex); glBindTexture(GL_TEXTURE_2D, m_tex);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); 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_MIN_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); 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_TEXTURE_COORD_ARRAY);
glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(2, GL_INT, 0, _glVertices); glVertexPointer(2, GL_INT, 0, _glVertices);
@ -50,7 +92,36 @@ void Display::paintGL() {
glMatrixMode(GL_PROJECTION); glMatrixMode(GL_PROJECTION);
glLoadIdentity(); glLoadIdentity();
glOrtho(0, 240, 160, 0, 0, 1); glOrtho(0, 240, 160, 0, 0, 1);
glEnable(GL_TEXTURE_2D); glMatrixMode(GL_MODELVIEW);
glBindTexture(GL_TEXTURE_2D, m_tex); glLoadIdentity();
glDrawArrays(GL_TRIANGLE_FAN, 0, 4); 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 #define QGBA_DISPLAY
#include <QGLWidget> #include <QGLWidget>
#include <QThread>
#include <QTimer>
struct GBAThread;
namespace QGBA { namespace QGBA {
class Painter;
class Display : public QGLWidget { class Display : public QGLWidget {
Q_OBJECT Q_OBJECT
public: public:
Display(QWidget* parent = 0); Display(QWidget* parent = 0);
protected:
virtual void initializeGL();
public slots: public slots:
void draw(const QImage& image); void startDrawing(const uint32_t* buffer, GBAThread* context);
void paintGL(); void stopDrawing();
protected:
virtual void paintEvent(QPaintEvent*) {};
virtual void resizeEvent(QResizeEvent*);
private: 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; GLuint m_tex;
QGLWidget* m_gl;
QSize m_size;
}; };
} }

View File

@ -9,13 +9,13 @@ using namespace QGBA;
GameController::GameController(QObject* parent) GameController::GameController(QObject* parent)
: QObject(parent) : QObject(parent)
, m_drawContext(256, 256, QImage::Format_RGB32) , m_drawContext(new uint32_t[256 * 256])
, m_audioContext(0) , m_audioContext(0)
{ {
m_renderer = new GBAVideoSoftwareRenderer; m_renderer = new GBAVideoSoftwareRenderer;
GBAVideoSoftwareRendererCreate(m_renderer); GBAVideoSoftwareRendererCreate(m_renderer);
m_renderer->outputBuffer = (color_t*) m_drawContext.bits(); m_renderer->outputBuffer = (color_t*) m_drawContext;
m_renderer->outputBufferStride = m_drawContext.bytesPerLine() / 4; m_renderer->outputBufferStride = 256;
m_threadContext = { m_threadContext = {
.useDebugger = 0, .useDebugger = 0,
.frameskip = 0, .frameskip = 0,
@ -43,6 +43,9 @@ GameController::GameController(QObject* parent)
} }
GameController::~GameController() { GameController::~GameController() {
if (GBAThreadIsPaused(&m_threadContext)) {
GBAThreadUnpause(&m_threadContext);
}
GBAThreadEnd(&m_threadContext); GBAThreadEnd(&m_threadContext);
GBAThreadJoin(&m_threadContext); GBAThreadJoin(&m_threadContext);
delete m_renderer; delete m_renderer;
@ -60,7 +63,7 @@ void GameController::loadGame(const QString& path) {
m_threadContext.fd = m_rom->handle(); m_threadContext.fd = m_rom->handle();
m_threadContext.fname = path.toLocal8Bit().constData(); m_threadContext.fname = path.toLocal8Bit().constData();
GBAThreadStart(&m_threadContext); GBAThreadStart(&m_threadContext);
emit gameStarted(); emit gameStarted(&m_threadContext);
} }
void GameController::setPaused(bool paused) { void GameController::setPaused(bool paused) {

View File

@ -25,10 +25,12 @@ public:
GameController(QObject* parent = 0); GameController(QObject* parent = 0);
~GameController(); ~GameController();
const uint32_t* drawContext() const { return m_drawContext; }
signals: signals:
void frameAvailable(const QImage&); void frameAvailable(const uint32_t*);
void audioDeviceAvailable(GBAAudio*); void audioDeviceAvailable(GBAAudio*);
void gameStarted(); void gameStarted(GBAThread*);
public slots: public slots:
void loadGame(const QString& path); void loadGame(const QString& path);
@ -40,7 +42,7 @@ public slots:
private: private:
void setupAudio(GBAAudio* audio); void setupAudio(GBAAudio* audio);
QImage m_drawContext; uint32_t* m_drawContext;
AudioDevice* m_audioContext; AudioDevice* m_audioContext;
GBAThread m_threadContext; GBAThread m_threadContext;
GBAVideoSoftwareRenderer* m_renderer; GBAVideoSoftwareRenderer* m_renderer;

View File

@ -12,11 +12,12 @@ Window::Window(QWidget* parent) : QMainWindow(parent) {
setMinimumSize(240, 160); setMinimumSize(240, 160);
m_controller = new GameController(this); m_controller = new GameController(this);
m_display = new Display(this); m_display = new Display();
setCentralWidget(m_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(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()); setupMenu(menuBar());
} }
@ -93,7 +94,13 @@ void Window::keyReleaseEvent(QKeyEvent* event) {
event->accept(); event->accept();
} }
void Window::gameStarted() { 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) { foreach (QAction* action, m_gameActions) {
action->setDisabled(false); action->setDisabled(false);
} }

View File

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