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" {
|
||||
#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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ public slots:
|
|||
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 @@ 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() {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -40,7 +40,6 @@ protected:
|
|||
|
||||
private slots:
|
||||
void gameStarted(GBAThread*);
|
||||
void setupAudio(GBAAudio*);
|
||||
|
||||
private:
|
||||
void setupMenu(QMenuBar*);
|
||||
|
|
Loading…
Reference in New Issue