GBA thread can be shut down and opened again, cleanly

This commit is contained in:
Jeffrey Pfau 2014-02-03 00:44:19 -08:00
parent acc58fccc9
commit 186e0b1ee5
8 changed files with 62 additions and 23 deletions

View File

@ -3,13 +3,14 @@
extern "C" { extern "C" {
#include "gba.h" #include "gba.h"
#include "gba-audio.h" #include "gba-audio.h"
#include "gba-thread.h"
} }
using namespace QGBA; using namespace QGBA;
AudioDevice::AudioDevice(GBAAudio* audio, QObject* parent) AudioDevice::AudioDevice(GBAThread* threadContext, QObject* parent)
: QIODevice(parent) : QIODevice(parent)
, m_audio(audio) , m_context(threadContext)
{ {
setOpenMode(ReadOnly); setOpenMode(ReadOnly);
} }
@ -17,7 +18,7 @@ AudioDevice::AudioDevice(GBAAudio* audio, QObject* parent)
void AudioDevice::setFormat(const QAudioFormat& format) { void AudioDevice::setFormat(const QAudioFormat& format) {
// TODO: merge where the fudge rate exists // TODO: merge where the fudge rate exists
float fudgeRate = 16853760.0f / GBA_ARM7TDMI_FREQUENCY; float fudgeRate = 16853760.0f / GBA_ARM7TDMI_FREQUENCY;
m_ratio = format.sampleRate() / (float) (m_audio->sampleRate * fudgeRate); m_ratio = format.sampleRate() / (float) (m_context->gba->audio.sampleRate * fudgeRate);
} }
qint64 AudioDevice::readData(char* data, qint64 maxSize) { qint64 AudioDevice::readData(char* data, qint64 maxSize) {
@ -25,7 +26,11 @@ qint64 AudioDevice::readData(char* data, qint64 maxSize) {
maxSize = 0xFFFFFFFF; maxSize = 0xFFFFFFFF;
} }
return GBAAudioResampleNN(m_audio, m_ratio, &m_drift, reinterpret_cast<GBAStereoSample*>(data), maxSize / sizeof(GBAStereoSample)) * sizeof(GBAStereoSample); if (!m_context->gba) {
return 0;
}
return GBAAudioResampleNN(&m_context->gba->audio, m_ratio, &m_drift, reinterpret_cast<GBAStereoSample*>(data), maxSize / sizeof(GBAStereoSample)) * sizeof(GBAStereoSample);
} }
qint64 AudioDevice::writeData(const char*, qint64) { qint64 AudioDevice::writeData(const char*, qint64) {
@ -38,11 +43,12 @@ AudioThread::AudioThread(QObject* parent)
// Nothing to do // Nothing to do
} }
void AudioThread::setInput(GBAAudio* input) { void AudioThread::setInput(GBAThread* input) {
m_input = input; m_input = input;
} }
void AudioThread::shutdown() { void AudioThread::shutdown() {
disconnect();
m_audioOutput->stop(); m_audioOutput->stop();
quit(); quit();
} }

View File

@ -6,19 +6,17 @@
#include <QIODevice> #include <QIODevice>
#include <QThread> #include <QThread>
struct GBAAudio; struct GBAThread;
namespace QGBA { namespace QGBA {
class AudioThread : public QThread { class AudioThread : public QThread {
Q_OBJECT Q_OBJECT
public: public:
AudioThread(QObject* parent = nullptr); AudioThread(QObject* parent = nullptr);
void setInput(GBAAudio* input); void setInput(GBAThread* input);
public slots: public slots:
void shutdown(); void shutdown();
@ -27,7 +25,7 @@ protected:
void run(); void run();
private: private:
GBAAudio* m_input; GBAThread* m_input;
QAudioOutput* m_audioOutput; QAudioOutput* m_audioOutput;
}; };
@ -35,7 +33,7 @@ class AudioDevice : public QIODevice {
Q_OBJECT Q_OBJECT
public: public:
AudioDevice(GBAAudio* audio, QObject* parent = nullptr); AudioDevice(GBAThread* threadContext, QObject* parent = nullptr);
void setFormat(const QAudioFormat& format); void setFormat(const QAudioFormat& format);
@ -44,7 +42,7 @@ protected:
virtual qint64 writeData(const char* data, qint64 maxSize) override; virtual qint64 writeData(const char* data, qint64 maxSize) override;
private: private:
GBAAudio* m_audio; GBAThread* m_context;
float m_drift; float m_drift;
float m_ratio; float m_ratio;
}; };

View File

@ -1,5 +1,6 @@
#include "Display.h" #include "Display.h"
#include <QApplication>
#include <QResizeEvent> #include <QResizeEvent>
extern "C" { extern "C" {
@ -32,6 +33,9 @@ Display::Display(QWidget* parent)
} }
void Display::startDrawing(const uint32_t* buffer, GBAThread* thread) { void Display::startDrawing(const uint32_t* buffer, GBAThread* thread) {
if (m_drawThread) {
return;
}
m_drawThread = new QThread(this); m_drawThread = new QThread(this);
m_painter = new Painter(this); m_painter = new Painter(this);
m_painter->setGLContext(this); m_painter->setGLContext(this);
@ -48,9 +52,16 @@ void Display::stopDrawing() {
if (m_drawThread) { if (m_drawThread) {
QMetaObject::invokeMethod(m_painter, "stop", Qt::BlockingQueuedConnection); QMetaObject::invokeMethod(m_painter, "stop", Qt::BlockingQueuedConnection);
m_drawThread->exit(); m_drawThread->exit();
m_drawThread = nullptr;
} }
} }
void Display::initializeGL() {
glClearColor(0, 0, 0, 0);
glClear(GL_COLOR_BUFFER_BIT);
swapBuffers();
}
void Display::resizeEvent(QResizeEvent* event) { void Display::resizeEvent(QResizeEvent* event) {
if (m_painter) { if (m_painter) {
m_painter->resize(event->size()); m_painter->resize(event->size());
@ -123,5 +134,8 @@ void Painter::stop() {
delete m_drawTimer; delete m_drawTimer;
m_gl->makeCurrent(); m_gl->makeCurrent();
glDeleteTextures(1, &m_tex); glDeleteTextures(1, &m_tex);
glClear(GL_COLOR_BUFFER_BIT);
m_gl->swapBuffers();
m_gl->doneCurrent(); m_gl->doneCurrent();
m_gl->context()->moveToThread(QApplication::instance()->thread());
} }

View File

@ -21,6 +21,7 @@ public slots:
void stopDrawing(); void stopDrawing();
protected: protected:
virtual void initializeGL() override;
virtual void paintEvent(QPaintEvent*) override {}; virtual void paintEvent(QPaintEvent*) override {};
virtual void resizeEvent(QResizeEvent*) override; virtual void resizeEvent(QResizeEvent*) override;

View File

@ -11,6 +11,7 @@ GameController::GameController(QObject* parent)
: QObject(parent) : QObject(parent)
, m_drawContext(new uint32_t[256 * 256]) , m_drawContext(new uint32_t[256 * 256])
, m_audioContext(nullptr) , m_audioContext(nullptr)
, m_rom(nullptr)
{ {
m_renderer = new GBAVideoSoftwareRenderer; m_renderer = new GBAVideoSoftwareRenderer;
GBAVideoSoftwareRendererCreate(m_renderer); GBAVideoSoftwareRendererCreate(m_renderer);
@ -21,15 +22,19 @@ GameController::GameController(QObject* parent)
.frameskip = 0, .frameskip = 0,
.biosFd = -1, .biosFd = -1,
.renderer = &m_renderer->d, .renderer = &m_renderer->d,
.sync.videoFrameWait = 0,
.sync.audioWait = 1,
.userData = this, .userData = this,
.rewindBufferCapacity = 0 .rewindBufferCapacity = 0
}; };
m_threadContext.startCallback = [] (GBAThread* context) { m_threadContext.startCallback = [] (GBAThread* context) {
GameController* controller = static_cast<GameController*>(context->userData); GameController* controller = static_cast<GameController*>(context->userData);
controller->audioDeviceAvailable(&context->gba->audio); controller->gameStarted(context);
}; };
m_threadContext.cleanCallback = [] (GBAThread* context) {
GameController* controller = static_cast<GameController*>(context->userData);
controller->gameStopped(context);
};
m_threadContext.frameCallback = [] (GBAThread* context) { m_threadContext.frameCallback = [] (GBAThread* context) {
GameController* controller = static_cast<GameController*>(context->userData); GameController* controller = static_cast<GameController*>(context->userData);
controller->m_pauseMutex.lock(); controller->m_pauseMutex.lock();
@ -69,6 +74,9 @@ void GameController::setDebugger(ARMDebugger* debugger) {
} }
void GameController::loadGame(const QString& path) { void GameController::loadGame(const QString& path) {
closeGame();
m_threadContext.sync.videoFrameWait = 0;
m_threadContext.sync.audioWait = 1;
m_rom = new QFile(path); m_rom = new QFile(path);
if (!m_rom->open(QIODevice::ReadOnly)) { if (!m_rom->open(QIODevice::ReadOnly)) {
delete m_rom; delete m_rom;
@ -80,7 +88,20 @@ 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(&m_threadContext); }
void GameController::closeGame() {
// TODO: Make this threadsafe
if (m_threadContext.state >= THREAD_EXITING) {
return;
}
GBAThreadEnd(&m_threadContext);
GBAThreadJoin(&m_threadContext);
if (m_rom) {
m_rom->close();
delete m_rom;
}
emit gameStopped(&m_threadContext);
} }
bool GameController::isPaused() { bool GameController::isPaused() {

View File

@ -36,11 +36,12 @@ public:
signals: signals:
void frameAvailable(const uint32_t*); void frameAvailable(const uint32_t*);
void audioDeviceAvailable(GBAAudio*);
void gameStarted(GBAThread*); void gameStarted(GBAThread*);
void gameStopped(GBAThread*);
public slots: public slots:
void loadGame(const QString& path); void loadGame(const QString& path);
void closeGame();
void setPaused(bool paused); void setPaused(bool paused);
void frameAdvance(); void frameAdvance();
void keyPressed(int key); void keyPressed(int key);

View File

@ -22,8 +22,8 @@ Window::Window(QWidget* parent)
m_controller = new GameController(this); m_controller = new GameController(this);
m_display = new Display(); m_display = new Display();
setCentralWidget(m_display); setCentralWidget(m_display);
connect(m_controller, SIGNAL(audioDeviceAvailable(GBAAudio*)), this, SLOT(setupAudio(GBAAudio*)));
connect(m_controller, SIGNAL(gameStarted(GBAThread*)), this, SLOT(gameStarted(GBAThread*))); connect(m_controller, SIGNAL(gameStarted(GBAThread*)), this, SLOT(gameStarted(GBAThread*)));
connect(m_controller, SIGNAL(gameStopped(GBAThread*)), m_display, SLOT(stopDrawing()));
connect(this, SIGNAL(startDrawing(const uint32_t*, GBAThread*)), m_display, SLOT(startDrawing(const uint32_t*, GBAThread*)), Qt::QueuedConnection); 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())); connect(this, SIGNAL(shutdown()), m_display, SLOT(stopDrawing()));
@ -122,19 +122,18 @@ void Window::gameStarted(GBAThread* context) {
foreach (QAction* action, m_gameActions) { foreach (QAction* action, m_gameActions) {
action->setDisabled(false); action->setDisabled(false);
} }
}
void Window::setupAudio(GBAAudio* audio) {
AudioThread* thread = new AudioThread(this); AudioThread* thread = new AudioThread(this);
thread->setInput(audio); thread->setInput(context);
thread->start(QThread::HighPriority); thread->start(QThread::HighPriority);
connect(this, SIGNAL(shutdown()), thread, SLOT(shutdown())); connect(this, SIGNAL(shutdown()), thread, SLOT(shutdown()));
connect(m_controller, SIGNAL(gameStopped(GBAThread*)), thread, SLOT(shutdown()));
} }
void Window::setupMenu(QMenuBar* menubar) { void Window::setupMenu(QMenuBar* menubar) {
menubar->clear(); menubar->clear();
QMenu* fileMenu = menubar->addMenu(tr("&File")); QMenu* fileMenu = menubar->addMenu(tr("&File"));
fileMenu->addAction(tr("Load &ROM..."), this, SLOT(selectROM()), QKeySequence::Open); fileMenu->addAction(tr("Load &ROM..."), this, SLOT(selectROM()), QKeySequence::Open);
fileMenu->addAction(tr("Sh&utdown"), m_controller, SLOT(closeGame()));
QMenu* emulationMenu = menubar->addMenu(tr("&Emulation")); QMenu* emulationMenu = menubar->addMenu(tr("&Emulation"));
QAction* pause = new QAction(tr("&Pause"), 0); QAction* pause = new QAction(tr("&Pause"), 0);

View File

@ -40,7 +40,6 @@ protected:
private slots: private slots:
void gameStarted(GBAThread*); void gameStarted(GBAThread*);
void setupAudio(GBAAudio*);
private: private:
void setupMenu(QMenuBar*); void setupMenu(QMenuBar*);