diff --git a/external/glslang b/external/glslang index 3ebb72cc..9c7fd1a3 160000 --- a/external/glslang +++ b/external/glslang @@ -1 +1 @@ -Subproject commit 3ebb72cc7429f0ab8218104dc3687c659c0f364d +Subproject commit 9c7fd1a33e5cecbe465e1cd70170167d5e40d398 diff --git a/qt/src/EmuApplication.cpp b/qt/src/EmuApplication.cpp index dc1014e8..2db05c1a 100644 --- a/qt/src/EmuApplication.cpp +++ b/qt/src/EmuApplication.cpp @@ -26,6 +26,7 @@ EmuApplication::~EmuApplication() void EmuApplication::restartAudio() { + suspendThread(); sound_driver.reset(); core->sound_output_function = nullptr; @@ -55,6 +56,8 @@ void EmuApplication::restartAudio() core->sound_output_function = [&](int16_t *data, int samples) { writeSamples(data, samples); }; + + unsuspendThread(); } #ifdef SOUND_BUFFER_WINDOW @@ -124,13 +127,14 @@ void EmuApplication::writeSamples(int16_t *data, int samples) void EmuApplication::startGame() { + suspendThread(); if (!sound_driver) restartAudio(); core->screen_output_function = [&](uint16_t *data, int width, int height, int stride_bytes, double frame_rate) { 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(int, width), Q_ARG(int, height), @@ -143,7 +147,12 @@ void EmuApplication::startGame() updateSettings(); updateBindings(); - startIdleLoop(); + emu_thread->setMainLoop([&] { + mainLoop(); + }); + + unsuspendThread(); + unpause(); } bool EmuApplication::isPaused() @@ -151,21 +160,61 @@ bool EmuApplication::isPaused() 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() { pause_count++; if (pause_count > 0) { + if (emu_thread) + emu_thread->pause(); core->setPaused(true); if (sound_driver) sound_driver->stop(); } } -void EmuApplication::stopIdleLoop() +void EmuApplication::stopThread() { - idle_loop->stop(); - pause_count = 0; + if (!emu_thread) + return; + + emu_thread->setStatusBits(EmuThread::eQuit); + while (!emu_thread->isFinished()) + { + std::this_thread::yield(); + } } void EmuApplication::unpause() @@ -179,47 +228,42 @@ void EmuApplication::unpause() core->setPaused(false); if (core->active && sound_driver) 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(); - idle_loop->setTimerType(Qt::TimerType::PreciseTimer); - idle_loop->setInterval(0); - idle_loop->setSingleShot(false); - 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); + emu_thread = std::make_unique(QThread::currentThread()); + emu_thread->start(); + emu_thread->waitForStatusBit(EmuThread::ePaused); + emu_thread->moveToThread(emu_thread.get()); } } bool EmuApplication::openFile(std::string filename) { + suspendThread(); auto result = core->openFile(filename); + unsuspendThread(); 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) { if (binding_callback && active) @@ -238,7 +282,7 @@ void EmuApplication::reportBinding(EmuBinding b, bool active) return; } - core->reportBinding(b, active); + emu_thread->runOnThread([&, b, active] { core->reportBinding(b, active); }); } void EmuApplication::updateBindings() @@ -273,7 +317,9 @@ void EmuApplication::updateBindings() } } + suspendThread(); core->updateBindings(config.get()); + unsuspendThread(); } void EmuApplication::handleBinding(std::string name, bool pressed) @@ -296,14 +342,18 @@ void EmuApplication::handleBinding(std::string name, bool pressed) save_slot++; if (save_slot > 999) 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") { save_slot--; if (save_slot < 0) 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") { @@ -349,7 +399,9 @@ void EmuApplication::updateSettings() config->input_rate /= 4; } - core->updateSettings(config.get()); + emu_thread->runOnThread([&] { + core->updateSettings(config.get()); + }); } void EmuApplication::pollJoysticks() @@ -401,39 +453,63 @@ void EmuApplication::pollJoysticks() } } +void EmuApplication::startInputTimer() +{ + poll_input_timer = std::make_unique(); + 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) { - core->loadState(slot); + emu_thread->runOnThread([&] { + core->loadState(slot); + }); } void EmuApplication::loadState(std::string filename) { - core->loadState(filename); + emu_thread->runOnThread([&] { + core->loadState(filename); + }); } void EmuApplication::saveState(int slot) { - core->saveState(slot); + emu_thread->runOnThread([&] { + core->saveState(slot); + }); } void EmuApplication::saveState(std::string filename) { - core->saveState(filename); + emu_thread->runOnThread([&] { + core->saveState(filename); + }); } void EmuApplication::reset() { - core->softReset(); + emu_thread->runOnThread([&] { + core->softReset(); + }); } void EmuApplication::powerCycle() { - core->reset(); + emu_thread->runOnThread([&] { + core->reset(); + }); } void EmuApplication::loadUndoState() { - core->loadUndoState(); + emu_thread->runOnThread([&] { + core->loadUndoState(); + }); } std::string EmuApplication::getStateFolder() @@ -444,4 +520,110 @@ std::string EmuApplication::getStateFolder() bool EmuApplication::isCoreActive() { return core->active; +} + +void EmuThread::runOnThread(std::function func) +{ + if (QThread::currentThread() != this) + { + QMetaObject::invokeMethod(this, "runOnThread", Q_ARG(std::function, func)); + return; + } + + func(); +} + +EmuThread::EmuThread(QThread *main_thread_) + : main_thread(main_thread_), QThread() +{ + qRegisterMetaType>("std::function"); +} + +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 loop) +{ + main_loop = loop; } \ No newline at end of file diff --git a/qt/src/EmuApplication.hpp b/qt/src/EmuApplication.hpp index 5f91a0f0..0b30777a 100644 --- a/qt/src/EmuApplication.hpp +++ b/qt/src/EmuApplication.hpp @@ -1,6 +1,7 @@ #pragma once #include #include +#include #include "EmuMainWindow.hpp" #include "EmuConfig.hpp" @@ -8,6 +9,39 @@ #include "Snes9xController.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 loop); + + std::function 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 func); +}; struct EmuApplication { @@ -16,6 +50,7 @@ struct EmuApplication std::unique_ptr input_manager; std::unique_ptr window; std::unique_ptr sound_driver; + std::unique_ptr emu_thread; Snes9xController *core; EmuApplication(); @@ -26,12 +61,16 @@ struct EmuApplication void updateBindings(); bool isBound(EmuBinding b); void reportBinding(EmuBinding b, bool active); + void startInputTimer(); void pollJoysticks(); void restartAudio(); void writeSamples(int16_t *data, int samples); + void mainLoop(); void pause(); void reset(); void powerCycle(); + void suspendThread(); + void unsuspendThread(); bool isPaused(); void unpause(); void loadState(int slot); @@ -41,9 +80,8 @@ struct EmuApplication std::string getStateFolder(); void loadUndoState(); void startGame(); - void startIdleLoop(); - void stopIdleLoop(); - void idleLoop(); + void startThread(); + void stopThread(); bool isCoreActive(); enum Handler @@ -52,9 +90,10 @@ struct EmuApplication UI = 1 }; std::map> bindings; - std::unique_ptr idle_loop; + std::unique_ptr poll_input_timer; std::function binding_callback = nullptr; std::function joypads_changed_callback = nullptr; int save_slot = 0; int pause_count = 0; + int suspend_count = 0; }; \ No newline at end of file diff --git a/qt/src/EmuCanvas.hpp b/qt/src/EmuCanvas.hpp index 938d422f..6cceda44 100644 --- a/qt/src/EmuCanvas.hpp +++ b/qt/src/EmuCanvas.hpp @@ -14,6 +14,7 @@ class EmuCanvas : public QWidget virtual void draw() = 0; void output(uint8_t *buffer, int width, int height, QImage::Format format, int bytes_per_line, double frame_rate); void throttle(); + void resizeEvent(QResizeEvent *event) override = 0; virtual std::vector getDeviceList() { diff --git a/qt/src/EmuCanvasQt.cpp b/qt/src/EmuCanvasQt.cpp index 84c487eb..4f0ef70c 100644 --- a/qt/src/EmuCanvasQt.cpp +++ b/qt/src/EmuCanvasQt.cpp @@ -44,4 +44,9 @@ void EmuCanvasQt::paintEvent(QPaintEvent *event) } paint.drawImage(dest, image, QRect(0, 0, output_data.width, output_data.height)); +} + +void EmuCanvasQt::resizeEvent(QResizeEvent *event) +{ + QWidget::resizeEvent(event); } \ No newline at end of file diff --git a/qt/src/EmuCanvasQt.hpp b/qt/src/EmuCanvasQt.hpp index ca5e1bc3..b754daf4 100644 --- a/qt/src/EmuCanvasQt.hpp +++ b/qt/src/EmuCanvasQt.hpp @@ -15,6 +15,7 @@ class EmuCanvasQt : public EmuCanvas virtual void draw() override; void paintEvent(QPaintEvent *event) override; + void resizeEvent(QResizeEvent *event) override; }; #endif \ No newline at end of file diff --git a/qt/src/EmuMainWindow.cpp b/qt/src/EmuMainWindow.cpp index 94677508..cb75c4f4 100644 --- a/qt/src/EmuMainWindow.cpp +++ b/qt/src/EmuMainWindow.cpp @@ -109,8 +109,10 @@ void EmuMainWindow::createCanvas() void EmuMainWindow::recreateCanvas() { + app->suspendThread(); destroyCanvas(); createCanvas(); + app->unsuspendThread(); } void EmuMainWindow::setCoreActionsEnabled(bool enable) @@ -528,6 +530,15 @@ void EmuMainWindow::toggleFullscreen() 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) return false; diff --git a/qt/src/main.cpp b/qt/src/main.cpp index e837cc3f..76101103 100644 --- a/qt/src/main.cpp +++ b/qt/src/main.cpp @@ -26,12 +26,11 @@ int main(int argc, char *argv[]) emu.window->show(); emu.updateBindings(); - emu.startIdleLoop(); - - - + emu.startInputTimer(); + emu.startThread(); emu.qtapp->exec(); + emu.stopThread(); emu.config->saveFile(EmuConfig::findConfigFile()); return 0;