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" {
#include "gba.h"
#include "gba-audio.h"
#include "gba-thread.h"
}
using namespace QGBA;
AudioDevice::AudioDevice(GBAAudio* audio, QObject* parent)
AudioDevice::AudioDevice(GBAThread* threadContext, QObject* parent)
: QIODevice(parent)
, m_audio(audio)
, m_context(threadContext)
{
setOpenMode(ReadOnly);
}
@ -17,7 +18,7 @@ AudioDevice::AudioDevice(GBAAudio* audio, QObject* parent)
void AudioDevice::setFormat(const QAudioFormat& format) {
// TODO: merge where the fudge rate exists
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) {
@ -25,7 +26,11 @@ qint64 AudioDevice::readData(char* data, qint64 maxSize) {
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) {
@ -38,11 +43,12 @@ AudioThread::AudioThread(QObject* parent)
// Nothing to do
}
void AudioThread::setInput(GBAAudio* input) {
void AudioThread::setInput(GBAThread* input) {
m_input = input;
}
void AudioThread::shutdown() {
disconnect();
m_audioOutput->stop();
quit();
}

View File

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

View File

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

View File

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

View File

@ -11,6 +11,7 @@ GameController::GameController(QObject* parent)
: QObject(parent)
, m_drawContext(new uint32_t[256 * 256])
, m_audioContext(nullptr)
, m_rom(nullptr)
{
m_renderer = new GBAVideoSoftwareRenderer;
GBAVideoSoftwareRendererCreate(m_renderer);
@ -21,15 +22,19 @@ GameController::GameController(QObject* parent)
.frameskip = 0,
.biosFd = -1,
.renderer = &m_renderer->d,
.sync.videoFrameWait = 0,
.sync.audioWait = 1,
.userData = this,
.rewindBufferCapacity = 0
};
m_threadContext.startCallback = [] (GBAThread* context) {
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) {
GameController* controller = static_cast<GameController*>(context->userData);
controller->m_pauseMutex.lock();
@ -69,6 +74,9 @@ void GameController::setDebugger(ARMDebugger* debugger) {
}
void GameController::loadGame(const QString& path) {
closeGame();
m_threadContext.sync.videoFrameWait = 0;
m_threadContext.sync.audioWait = 1;
m_rom = new QFile(path);
if (!m_rom->open(QIODevice::ReadOnly)) {
delete m_rom;
@ -80,7 +88,20 @@ void GameController::loadGame(const QString& path) {
m_threadContext.fd = m_rom->handle();
m_threadContext.fname = path.toLocal8Bit().constData();
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() {

View File

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

View File

@ -22,8 +22,8 @@ Window::Window(QWidget* parent)
m_controller = new GameController(this);
m_display = new 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(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(shutdown()), m_display, SLOT(stopDrawing()));
@ -122,19 +122,18 @@ void Window::gameStarted(GBAThread* context) {
foreach (QAction* action, m_gameActions) {
action->setDisabled(false);
}
}
void Window::setupAudio(GBAAudio* audio) {
AudioThread* thread = new AudioThread(this);
thread->setInput(audio);
thread->setInput(context);
thread->start(QThread::HighPriority);
connect(this, SIGNAL(shutdown()), thread, SLOT(shutdown()));
connect(m_controller, SIGNAL(gameStopped(GBAThread*)), thread, SLOT(shutdown()));
}
void Window::setupMenu(QMenuBar* menubar) {
menubar->clear();
QMenu* fileMenu = menubar->addMenu(tr("&File"));
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"));
QAction* pause = new QAction(tr("&Pause"), 0);

View File

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