mirror of https://github.com/mgba-emu/mgba.git
GBA thread can be shut down and opened again, cleanly
This commit is contained in:
parent
acc58fccc9
commit
186e0b1ee5
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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*);
|
||||||
|
|
Loading…
Reference in New Issue