Threading.

This commit is contained in:
BearOso 2023-07-13 18:00:16 -05:00
parent 177a802186
commit a438d3fa42
8 changed files with 290 additions and 52 deletions

2
external/glslang vendored

@ -1 +1 @@
Subproject commit 3ebb72cc7429f0ab8218104dc3687c659c0f364d Subproject commit 9c7fd1a33e5cecbe465e1cd70170167d5e40d398

View File

@ -26,6 +26,7 @@ EmuApplication::~EmuApplication()
void EmuApplication::restartAudio() void EmuApplication::restartAudio()
{ {
suspendThread();
sound_driver.reset(); sound_driver.reset();
core->sound_output_function = nullptr; core->sound_output_function = nullptr;
@ -55,6 +56,8 @@ void EmuApplication::restartAudio()
core->sound_output_function = [&](int16_t *data, int samples) { core->sound_output_function = [&](int16_t *data, int samples) {
writeSamples(data, samples); writeSamples(data, samples);
}; };
unsuspendThread();
} }
#ifdef SOUND_BUFFER_WINDOW #ifdef SOUND_BUFFER_WINDOW
@ -124,13 +127,14 @@ void EmuApplication::writeSamples(int16_t *data, int samples)
void EmuApplication::startGame() void EmuApplication::startGame()
{ {
suspendThread();
if (!sound_driver) if (!sound_driver)
restartAudio(); restartAudio();
core->screen_output_function = [&](uint16_t *data, int width, int height, int stride_bytes, double frame_rate) { core->screen_output_function = [&](uint16_t *data, int width, int height, int stride_bytes, double frame_rate) {
if (window->canvas) if (window->canvas)
{ {
QMetaObject::invokeMethod(window.get(), "output", Qt::ConnectionType::QueuedConnection, QMetaObject::invokeMethod(window.get(), "output", Qt::ConnectionType::DirectConnection,
Q_ARG(uint8_t *, (uint8_t *)data), Q_ARG(uint8_t *, (uint8_t *)data),
Q_ARG(int, width), Q_ARG(int, width),
Q_ARG(int, height), Q_ARG(int, height),
@ -143,7 +147,12 @@ void EmuApplication::startGame()
updateSettings(); updateSettings();
updateBindings(); updateBindings();
startIdleLoop(); emu_thread->setMainLoop([&] {
mainLoop();
});
unsuspendThread();
unpause();
} }
bool EmuApplication::isPaused() bool EmuApplication::isPaused()
@ -151,21 +160,61 @@ bool EmuApplication::isPaused()
return (pause_count != 0); return (pause_count != 0);
} }
void EmuApplication::suspendThread()
{
suspend_count++;
if (!emu_thread)
return;
if (suspend_count > 0)
{
printf("Suspend %d\n", suspend_count);
emu_thread->runOnThread([&] { emu_thread->setStatusBits(EmuThread::eSuspended); });
emu_thread->waitForStatusBit(EmuThread::eSuspended);
}
}
void EmuApplication::unsuspendThread()
{
suspend_count--;
assert(suspend_count >= 0);
if (!emu_thread)
return;
printf("Un Suspend %d\n", suspend_count);
if (suspend_count == 0)
{
emu_thread->runOnThread([&] { emu_thread->unsetStatusBits(EmuThread::eSuspended); });
emu_thread->waitForStatusBitCleared(EmuThread::eSuspended);
}
}
void EmuApplication::pause() void EmuApplication::pause()
{ {
pause_count++; pause_count++;
if (pause_count > 0) if (pause_count > 0)
{ {
if (emu_thread)
emu_thread->pause();
core->setPaused(true); core->setPaused(true);
if (sound_driver) if (sound_driver)
sound_driver->stop(); sound_driver->stop();
} }
} }
void EmuApplication::stopIdleLoop() void EmuApplication::stopThread()
{ {
idle_loop->stop(); if (!emu_thread)
pause_count = 0; return;
emu_thread->setStatusBits(EmuThread::eQuit);
while (!emu_thread->isFinished())
{
std::this_thread::yield();
}
} }
void EmuApplication::unpause() void EmuApplication::unpause()
@ -179,47 +228,42 @@ void EmuApplication::unpause()
core->setPaused(false); core->setPaused(false);
if (core->active && sound_driver) if (core->active && sound_driver)
sound_driver->start(); sound_driver->start();
if (emu_thread)
emu_thread->unpause();
} }
void EmuApplication::startIdleLoop() void EmuApplication::startThread()
{ {
if (!idle_loop) if (!emu_thread)
{ {
idle_loop = std::make_unique<QTimer>(); emu_thread = std::make_unique<EmuThread>(QThread::currentThread());
idle_loop->setTimerType(Qt::TimerType::PreciseTimer); emu_thread->start();
idle_loop->setInterval(0); emu_thread->waitForStatusBit(EmuThread::ePaused);
idle_loop->setSingleShot(false); emu_thread->moveToThread(emu_thread.get());
idle_loop->callOnTimeout([&]{ idleLoop(); });
pause_count = 0;
}
idle_loop->start();
}
void EmuApplication::idleLoop()
{
if (core->active && pause_count == 0)
{
idle_loop->setInterval(0);
pollJoysticks();
bool muted = config->mute_audio || (config->mute_audio_during_alternate_speed && core->isAbnormalSpeed());
core->mute(muted);
core->mainLoop();
}
else
{
pollJoysticks();
idle_loop->setInterval(32);
} }
} }
bool EmuApplication::openFile(std::string filename) bool EmuApplication::openFile(std::string filename)
{ {
suspendThread();
auto result = core->openFile(filename); auto result = core->openFile(filename);
unsuspendThread();
return result; return result;
} }
void EmuApplication::mainLoop()
{
if (!core->active)
{
std::this_thread::yield();
return;
}
printf("Here\n");
core->mainLoop();
}
void EmuApplication::reportBinding(EmuBinding b, bool active) void EmuApplication::reportBinding(EmuBinding b, bool active)
{ {
if (binding_callback && active) if (binding_callback && active)
@ -238,7 +282,7 @@ void EmuApplication::reportBinding(EmuBinding b, bool active)
return; return;
} }
core->reportBinding(b, active); emu_thread->runOnThread([&, b, active] { core->reportBinding(b, active); });
} }
void EmuApplication::updateBindings() void EmuApplication::updateBindings()
@ -273,7 +317,9 @@ void EmuApplication::updateBindings()
} }
} }
suspendThread();
core->updateBindings(config.get()); core->updateBindings(config.get());
unsuspendThread();
} }
void EmuApplication::handleBinding(std::string name, bool pressed) void EmuApplication::handleBinding(std::string name, bool pressed)
@ -296,14 +342,18 @@ void EmuApplication::handleBinding(std::string name, bool pressed)
save_slot++; save_slot++;
if (save_slot > 999) if (save_slot > 999)
save_slot = 0; save_slot = 0;
core->setMessage("Current slot: " + std::to_string(save_slot)); emu_thread->runOnThread([&] {
core->setMessage("Current slot: " + std::to_string(save_slot));
});
} }
else if (name == "DecreaseSlot") else if (name == "DecreaseSlot")
{ {
save_slot--; save_slot--;
if (save_slot < 0) if (save_slot < 0)
save_slot = 999; save_slot = 999;
core->setMessage("Current slot: " + std::to_string(save_slot)); emu_thread->runOnThread([&] {
core->setMessage("Current slot: " + std::to_string(save_slot));
});
} }
else if (name == "SaveState") else if (name == "SaveState")
{ {
@ -349,7 +399,9 @@ void EmuApplication::updateSettings()
config->input_rate /= 4; config->input_rate /= 4;
} }
core->updateSettings(config.get()); emu_thread->runOnThread([&] {
core->updateSettings(config.get());
});
} }
void EmuApplication::pollJoysticks() void EmuApplication::pollJoysticks()
@ -401,39 +453,63 @@ void EmuApplication::pollJoysticks()
} }
} }
void EmuApplication::startInputTimer()
{
poll_input_timer = std::make_unique<QTimer>();
poll_input_timer->setTimerType(Qt::TimerType::PreciseTimer);
poll_input_timer->setInterval(4);
poll_input_timer->setSingleShot(false);
poll_input_timer->callOnTimeout([&] { pollJoysticks(); });
poll_input_timer->start();
}
void EmuApplication::loadState(int slot) void EmuApplication::loadState(int slot)
{ {
core->loadState(slot); emu_thread->runOnThread([&] {
core->loadState(slot);
});
} }
void EmuApplication::loadState(std::string filename) void EmuApplication::loadState(std::string filename)
{ {
core->loadState(filename); emu_thread->runOnThread([&] {
core->loadState(filename);
});
} }
void EmuApplication::saveState(int slot) void EmuApplication::saveState(int slot)
{ {
core->saveState(slot); emu_thread->runOnThread([&] {
core->saveState(slot);
});
} }
void EmuApplication::saveState(std::string filename) void EmuApplication::saveState(std::string filename)
{ {
core->saveState(filename); emu_thread->runOnThread([&] {
core->saveState(filename);
});
} }
void EmuApplication::reset() void EmuApplication::reset()
{ {
core->softReset(); emu_thread->runOnThread([&] {
core->softReset();
});
} }
void EmuApplication::powerCycle() void EmuApplication::powerCycle()
{ {
core->reset(); emu_thread->runOnThread([&] {
core->reset();
});
} }
void EmuApplication::loadUndoState() void EmuApplication::loadUndoState()
{ {
core->loadUndoState(); emu_thread->runOnThread([&] {
core->loadUndoState();
});
} }
std::string EmuApplication::getStateFolder() std::string EmuApplication::getStateFolder()
@ -444,4 +520,110 @@ std::string EmuApplication::getStateFolder()
bool EmuApplication::isCoreActive() bool EmuApplication::isCoreActive()
{ {
return core->active; return core->active;
}
void EmuThread::runOnThread(std::function<void()> func)
{
if (QThread::currentThread() != this)
{
QMetaObject::invokeMethod(this, "runOnThread", Q_ARG(std::function<void()>, func));
return;
}
func();
}
EmuThread::EmuThread(QThread *main_thread_)
: main_thread(main_thread_), QThread()
{
qRegisterMetaType<std::function<void()>>("std::function<void()>");
}
void EmuThread::setStatusBits(int new_status)
{
std::unique_lock lock(status_mutex);
status |= new_status;
lock.unlock();
status_cond.notify_all();
}
void EmuThread::unsetStatusBits(int new_status)
{
printf("Old: %08x, new: %08x\n", status, new_status);
std::unique_lock lock(status_mutex);
status &= ~new_status;
printf("Final: %08x\n", status);
lock.unlock();
status_cond.notify_all();
}
void EmuThread::waitForStatusBit(int new_status)
{
if (status & new_status)
return;
while (1)
{
std::unique_lock lock(status_mutex);
status_cond.wait_for(lock, std::chrono::milliseconds(500));
if (status & new_status)
break;
}
}
void EmuThread::waitForStatusBitCleared(int new_status)
{
if (!(status & new_status))
return;
while (1)
{
std::unique_lock lock(status_mutex);
status_cond.wait_for(lock, std::chrono::milliseconds(500));
if (!(status & new_status))
break;
}
}
void EmuThread::pause()
{
runOnThread([&] { setStatusBits(ePaused); });
waitForStatusBit(ePaused);
}
void EmuThread::unpause()
{
runOnThread([&] { unsetStatusBits(ePaused); });
waitForStatusBitCleared(ePaused);
}
void EmuThread::run()
{
auto event_loop = new QEventLoop();
setStatusBits(ePaused);
while (1)
{
event_loop->processEvents();
if (status & eQuit)
break;
if (status & (ePaused | eSuspended))
{
std::this_thread::sleep_for(2ms);
printf("Paused: %08x\n", status);
continue;
}
printf("Loop\n");
if (main_loop)
main_loop();
}
}
void EmuThread::setMainLoop(std::function<void ()> loop)
{
main_loop = loop;
} }

View File

@ -1,6 +1,7 @@
#pragma once #pragma once
#include <QApplication> #include <QApplication>
#include <QTimer> #include <QTimer>
#include <QThread>
#include "EmuMainWindow.hpp" #include "EmuMainWindow.hpp"
#include "EmuConfig.hpp" #include "EmuConfig.hpp"
@ -8,6 +9,39 @@
#include "Snes9xController.hpp" #include "Snes9xController.hpp"
#include "common/audio/s9x_sound_driver.hpp" #include "common/audio/s9x_sound_driver.hpp"
struct EmuThread : public QThread
{
Q_OBJECT
public:
EmuThread(QThread *main_thread);
QThread *main_thread;
void run() override;
void waitForStatusBit(int);
void waitForStatusBitCleared(int);
void setStatusBits(int);
void unsetStatusBits(int);
void pause();
void unpause();
void setMainLoop(std::function<void()> loop);
std::function<void()> main_loop = nullptr;
enum StatusBits
{
eDead = 0,
ePaused = 1,
eSuspended = 2,
eRunning = 4,
eQuit = 8
};
int status = eDead;
std::mutex status_mutex;
std::condition_variable status_cond;
public slots:
void runOnThread(std::function<void()> func);
};
struct EmuApplication struct EmuApplication
{ {
@ -16,6 +50,7 @@ struct EmuApplication
std::unique_ptr<SDLInputManager> input_manager; std::unique_ptr<SDLInputManager> input_manager;
std::unique_ptr<EmuMainWindow> window; std::unique_ptr<EmuMainWindow> window;
std::unique_ptr<S9xSoundDriver> sound_driver; std::unique_ptr<S9xSoundDriver> sound_driver;
std::unique_ptr<EmuThread> emu_thread;
Snes9xController *core; Snes9xController *core;
EmuApplication(); EmuApplication();
@ -26,12 +61,16 @@ struct EmuApplication
void updateBindings(); void updateBindings();
bool isBound(EmuBinding b); bool isBound(EmuBinding b);
void reportBinding(EmuBinding b, bool active); void reportBinding(EmuBinding b, bool active);
void startInputTimer();
void pollJoysticks(); void pollJoysticks();
void restartAudio(); void restartAudio();
void writeSamples(int16_t *data, int samples); void writeSamples(int16_t *data, int samples);
void mainLoop();
void pause(); void pause();
void reset(); void reset();
void powerCycle(); void powerCycle();
void suspendThread();
void unsuspendThread();
bool isPaused(); bool isPaused();
void unpause(); void unpause();
void loadState(int slot); void loadState(int slot);
@ -41,9 +80,8 @@ struct EmuApplication
std::string getStateFolder(); std::string getStateFolder();
void loadUndoState(); void loadUndoState();
void startGame(); void startGame();
void startIdleLoop(); void startThread();
void stopIdleLoop(); void stopThread();
void idleLoop();
bool isCoreActive(); bool isCoreActive();
enum Handler enum Handler
@ -52,9 +90,10 @@ struct EmuApplication
UI = 1 UI = 1
}; };
std::map<uint32_t, std::pair<std::string, Handler>> bindings; std::map<uint32_t, std::pair<std::string, Handler>> bindings;
std::unique_ptr<QTimer> idle_loop; std::unique_ptr<QTimer> poll_input_timer;
std::function<void(EmuBinding)> binding_callback = nullptr; std::function<void(EmuBinding)> binding_callback = nullptr;
std::function<void()> joypads_changed_callback = nullptr; std::function<void()> joypads_changed_callback = nullptr;
int save_slot = 0; int save_slot = 0;
int pause_count = 0; int pause_count = 0;
int suspend_count = 0;
}; };

View File

@ -14,6 +14,7 @@ class EmuCanvas : public QWidget
virtual void draw() = 0; virtual void draw() = 0;
void output(uint8_t *buffer, int width, int height, QImage::Format format, int bytes_per_line, double frame_rate); void output(uint8_t *buffer, int width, int height, QImage::Format format, int bytes_per_line, double frame_rate);
void throttle(); void throttle();
void resizeEvent(QResizeEvent *event) override = 0;
virtual std::vector<std::string> getDeviceList() virtual std::vector<std::string> getDeviceList()
{ {

View File

@ -44,4 +44,9 @@ void EmuCanvasQt::paintEvent(QPaintEvent *event)
} }
paint.drawImage(dest, image, QRect(0, 0, output_data.width, output_data.height)); paint.drawImage(dest, image, QRect(0, 0, output_data.width, output_data.height));
}
void EmuCanvasQt::resizeEvent(QResizeEvent *event)
{
QWidget::resizeEvent(event);
} }

View File

@ -15,6 +15,7 @@ class EmuCanvasQt : public EmuCanvas
virtual void draw() override; virtual void draw() override;
void paintEvent(QPaintEvent *event) override; void paintEvent(QPaintEvent *event) override;
void resizeEvent(QResizeEvent *event) override;
}; };
#endif #endif

View File

@ -109,8 +109,10 @@ void EmuMainWindow::createCanvas()
void EmuMainWindow::recreateCanvas() void EmuMainWindow::recreateCanvas()
{ {
app->suspendThread();
destroyCanvas(); destroyCanvas();
createCanvas(); createCanvas();
app->unsuspendThread();
} }
void EmuMainWindow::setCoreActionsEnabled(bool enable) void EmuMainWindow::setCoreActionsEnabled(bool enable)
@ -528,6 +530,15 @@ void EmuMainWindow::toggleFullscreen()
bool EmuMainWindow::eventFilter(QObject *watched, QEvent *event) bool EmuMainWindow::eventFilter(QObject *watched, QEvent *event)
{ {
if (watched == canvas && event->type() == QEvent::Resize)
{
app->suspendThread();
canvas->resizeEvent((QResizeEvent *)event);
event->accept();
app->unsuspendThread();
return true;
}
if (event->type() != QEvent::KeyPress && event->type() != QEvent::KeyRelease) if (event->type() != QEvent::KeyPress && event->type() != QEvent::KeyRelease)
return false; return false;

View File

@ -26,12 +26,11 @@ int main(int argc, char *argv[])
emu.window->show(); emu.window->show();
emu.updateBindings(); emu.updateBindings();
emu.startIdleLoop(); emu.startInputTimer();
emu.startThread();
emu.qtapp->exec(); emu.qtapp->exec();
emu.stopThread();
emu.config->saveFile(EmuConfig::findConfigFile()); emu.config->saveFile(EmuConfig::findConfigFile());
return 0; return 0;