GBA thread can be shut down and opened again, cleanly
@@ -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 @@
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 @@ if (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) {@@ -38,11 +43,12 @@ {
// Nothing to do } -void AudioThread::setInput(GBAAudio* input) { +void AudioThread::setInput(GBAThread* input) { m_input = input; } void AudioThread::shutdown() { + disconnect(); m_audioOutput->stop(); quit(); }
@@ -6,11 +6,9 @@ #include <QAudioOutput>
#include <QIODevice> #include <QThread> -struct GBAAudio; +struct GBAThread; namespace QGBA { - - class AudioThread : public QThread { Q_OBJECT@@ -18,7 +16,7 @@
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 @@ virtual qint64 readData(char* data, qint64 maxSize) override;
virtual qint64 writeData(const char* data, qint64 maxSize) override; private: - GBAAudio* m_audio; + GBAThread* m_context; float m_drift; float m_ratio; };
@@ -1,5 +1,6 @@
#include "Display.h" +#include <QApplication> #include <QResizeEvent> extern "C" {@@ -32,6 +33,9 @@ setAutoBufferSwap(false);
} 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,7 +52,14 @@ 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) {@@ -123,5 +134,8 @@ m_drawTimer->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()); }
@@ -21,6 +21,7 @@ void startDrawing(const uint32_t* buffer, GBAThread* context);
void stopDrawing(); protected: + virtual void initializeGL() override; virtual void paintEvent(QPaintEvent*) override {}; virtual void resizeEvent(QResizeEvent*) override;
@@ -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 @@ .debugger = 0,
.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 @@ setPaused(wasPaused);
} 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 @@
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() {
@@ -36,11 +36,12 @@ #endif
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);
@@ -22,8 +22,8 @@
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 @@ emit startDrawing(m_controller->drawContext(), 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);
@@ -40,7 +40,6 @@ virtual void closeEvent(QCloseEvent*) override;
private slots: void gameStarted(GBAThread*); - void setupAudio(GBAAudio*); private: void setupMenu(QMenuBar*);