From acc02ee98e74ad15f152dcfa255f6173e7784fbe Mon Sep 17 00:00:00 2001 From: Matthew Budd Date: Sun, 5 Jul 2020 16:11:53 -0400 Subject: [PATCH] Moved FCEU emulation to its own thread for better timing control. --- src/drivers/Qt/GameApp.cpp | 47 ++++++++++++++++++++-- src/drivers/Qt/GameApp.h | 16 ++++++++ src/drivers/Qt/fceuWrapper.cpp | 69 +++++++++++++++++++++++++++++---- src/drivers/Qt/fceuWrapper.h | 4 ++ src/drivers/Qt/input.cpp | 4 +- src/drivers/Qt/main.cpp | 13 +++++-- src/drivers/Qt/nes_shm.h | 2 + src/drivers/Qt/sdl-sound.cpp | 7 +++- src/drivers/Qt/sdl-throttle.cpp | 19 +++++---- 9 files changed, 157 insertions(+), 24 deletions(-) diff --git a/src/drivers/Qt/GameApp.cpp b/src/drivers/Qt/GameApp.cpp index bd811408..f4e98fd7 100644 --- a/src/drivers/Qt/GameApp.cpp +++ b/src/drivers/Qt/GameApp.cpp @@ -7,6 +7,7 @@ #include "Qt/GameSoundConf.h" #include "Qt/fceuWrapper.h" #include "Qt/keyscan.h" +#include "Qt/nes_shm.h" gameWin_t::gameWin_t(QWidget *parent) : QMainWindow( parent ) @@ -19,25 +20,45 @@ gameWin_t::gameWin_t(QWidget *parent) setCentralWidget(viewport); - gameTimer = new QTimer( this ); + gameTimer = new QTimer( this ); + gameThread = new QThread( this ); + worker = new gameWorkerThread_t(); + mutex = new QMutex( QMutex::NonRecursive ); + + worker->moveToThread(gameThread); + connect(gameThread, &QThread::finished, worker, &QObject::deleteLater); + connect(gameThread, SIGNAL (started()), worker, SLOT( runEmulator() )); + connect(worker, SIGNAL (finished()), gameThread, SLOT (quit())); + connect(worker, SIGNAL (finished()), worker, SLOT (deleteLater())); connect( gameTimer, &QTimer::timeout, this, &gameWin_t::runGameFrame ); gameTimer->setTimerType( Qt::PreciseTimer ); gameTimer->start( 16 ); + gameThread->start(); + gamePadConfWin = NULL; } gameWin_t::~gameWin_t(void) { + nes_shm->runEmulator = 0; + if ( gamePadConfWin != NULL ) { gamePadConfWin->closeWindow(); } + fceuWrapperLock(); fceuWrapperClose(); + fceuWrapperUnLock(); + + //printf("Thread Finished: %i \n", gameThread->isFinished() ); + gameThread->exit(0); + gameThread->wait(); delete viewport; + delete mutex; } void gameWin_t::setCyclePeriodms( int ms ) @@ -142,7 +163,11 @@ void gameWin_t::createMainMenu(void) //--------------------------------------------------------------------------- void gameWin_t::closeApp(void) { + nes_shm->runEmulator = 0; + + fceuWrapperLock(); fceuWrapperClose(); + fceuWrapperUnLock(); // LoadGame() checks for an IP and if it finds one begins a network session // clear the NetworkIP field so this doesn't happen unintentionally @@ -197,15 +222,20 @@ void gameWin_t::openROMFile(void) qDebug() << "selected file path : " << filename.toUtf8(); g_config->setOption ("SDL.LastOpenFile", filename.toStdString().c_str() ); + + fceuWrapperLock(); CloseGame (); LoadGame ( filename.toStdString().c_str() ); + fceuWrapperUnLock(); return; } void gameWin_t::closeROMCB(void) { + fceuWrapperLock(); CloseGame(); + fceuWrapperUnLock(); } void gameWin_t::openGamePadConfWin(void) @@ -258,9 +288,20 @@ void gameWin_t::runGameFrame(void) //t = (double)ts.tv_sec + (double)(ts.tv_nsec * 1.0e-9); //printf("Run Frame %f\n", t); - fceuWrapperUpdate(); - viewport->repaint(); return; } + +void gameWorkerThread_t::runEmulator(void) +{ + printf("Emulator Start\n"); + nes_shm->runEmulator = 1; + + while ( nes_shm->runEmulator ) + { + fceuWrapperUpdate(); + } + printf("Emulator Exit\n"); + emit finished(); +} diff --git a/src/drivers/Qt/GameApp.h b/src/drivers/Qt/GameApp.h index 570ef6e8..3cb2963e 100644 --- a/src/drivers/Qt/GameApp.h +++ b/src/drivers/Qt/GameApp.h @@ -14,11 +14,23 @@ #include #include #include +#include +#include #include "Qt/GameViewerGL.h" #include "Qt/GameViewerSDL.h" #include "Qt/GamePadConf.h" +class gameWorkerThread_t : public QObject +{ + Q_OBJECT + + public slots: + void runEmulator( void ); + signals: + void finished(); +}; + class gameWin_t : public QMainWindow { Q_OBJECT @@ -32,6 +44,8 @@ class gameWin_t : public QMainWindow void setCyclePeriodms( int ms ); + QMutex *mutex; + protected: QMenu *fileMenu; QMenu *optMenu; @@ -45,6 +59,8 @@ class gameWin_t : public QMainWindow QAction *aboutAct; QTimer *gameTimer; + QThread *gameThread; + gameWorkerThread_t *worker; GamePadConfDialog_t *gamePadConfWin; diff --git a/src/drivers/Qt/fceuWrapper.cpp b/src/drivers/Qt/fceuWrapper.cpp index f2513509..097ace38 100644 --- a/src/drivers/Qt/fceuWrapper.cpp +++ b/src/drivers/Qt/fceuWrapper.cpp @@ -14,6 +14,7 @@ #include "Qt/sdl-video.h" #include "Qt/nes_shm.h" #include "Qt/unix-netplay.h" +#include "Qt/GameApp.h" #include "common/cheat.h" #include "../../fceu.h" @@ -55,6 +56,7 @@ static int inited = 0; static int noconfig=0; static int frameskip=0; static int periodic_saves = 0; +static bool mutexLocked = 0; extern double g_fpsScale; //***************************************************************** @@ -711,13 +713,13 @@ FCEUD_Update(uint8 *XBuf, } else { - if (!NoWaiting && (!(eoptions&EO_NOTHROTTLE) || FCEUI_EmulationPaused())) - { - while (SpeedThrottle()) - { - FCEUD_UpdateInput(); - } - } + //if (!NoWaiting && (!(eoptions&EO_NOTHROTTLE) || FCEUI_EmulationPaused())) + //{ + // while (SpeedThrottle()) + // { + // FCEUD_UpdateInput(); + // } + //} if (XBuf && (inited&4)) { BlitScreen(XBuf); blitDone = 1; @@ -772,13 +774,64 @@ static void DoFun(int frameskip, int periodic_saves) } } +void fceuWrapperLock(void) +{ + gameWindow->mutex->lock(); + mutexLocked = 1; +} + +bool fceuWrapperTryLock(int timeout) +{ + bool lockAcq; + + lockAcq = gameWindow->mutex->tryLock( timeout ); + + if ( lockAcq ) + { + mutexLocked = 1; + } + return lockAcq; +} + +void fceuWrapperUnLock(void) +{ + if ( mutexLocked ) + { + gameWindow->mutex->unlock(); + mutexLocked = 0; + } + else + { + printf("Error: Mutex is Already UnLocked\n"); + } +} + +bool fceuWrapperIsLocked(void) +{ + return mutexLocked; +} + int fceuWrapperUpdate( void ) { + fceuWrapperLock(); + if (GameInfo) { DoFun(frameskip, periodic_saves); - } + + fceuWrapperUnLock(); + while ( SpeedThrottle() ) + { + FCEUD_UpdateInput(); + } + } + else + { + fceuWrapperUnLock(); + + usleep( 100000 ); + } return 0; } diff --git a/src/drivers/Qt/fceuWrapper.h b/src/drivers/Qt/fceuWrapper.h index 2d4a1ff7..7cd6b70c 100644 --- a/src/drivers/Qt/fceuWrapper.h +++ b/src/drivers/Qt/fceuWrapper.h @@ -25,4 +25,8 @@ int CloseGame(void); int fceuWrapperInit( int argc, char *argv[] ); int fceuWrapperClose( void ); int fceuWrapperUpdate( void ); +void fceuWrapperLock(void); +bool fceuWrapperTryLock(int timeout); +bool fceuWrapperIsLocked(void); +void fceuWrapperUnLock(void); diff --git a/src/drivers/Qt/input.cpp b/src/drivers/Qt/input.cpp index 1e2162a0..049a5a97 100644 --- a/src/drivers/Qt/input.cpp +++ b/src/drivers/Qt/input.cpp @@ -611,10 +611,10 @@ static void KeyboardCommands (void) if (_keyonly (Hotkeys[HK_DECREASE_SPEED])) { - DecreaseEmulationSpeed (); + DecreaseEmulationSpeed(); } - if (_keyonly (Hotkeys[HK_INCREASE_SPEED])) + if (_keyonly(Hotkeys[HK_INCREASE_SPEED])) { IncreaseEmulationSpeed (); } diff --git a/src/drivers/Qt/main.cpp b/src/drivers/Qt/main.cpp index 28e4d882..09caec18 100644 --- a/src/drivers/Qt/main.cpp +++ b/src/drivers/Qt/main.cpp @@ -7,17 +7,24 @@ gameWin_t *gameWindow = NULL; int main( int argc, char *argv[] ) { + int retval; QApplication app(argc, argv); - gameWindow = new gameWin_t(); - fceuWrapperInit( argc, argv ); + gameWindow = new gameWin_t(); + gameWindow->resize( 512, 512 ); gameWindow->show(); gameWindow->viewport->init(); - return app.exec(); + retval = app.exec(); + + //printf("App Return: %i \n", retval ); + + delete gameWindow; + + return retval; } diff --git a/src/drivers/Qt/nes_shm.h b/src/drivers/Qt/nes_shm.h index 0cc52bd6..d626afe4 100644 --- a/src/drivers/Qt/nes_shm.h +++ b/src/drivers/Qt/nes_shm.h @@ -24,6 +24,8 @@ struct nes_shm_t int nrow; int pitch; + char runEmulator; + // Pass Key Events back to QT Gui struct { diff --git a/src/drivers/Qt/sdl-sound.cpp b/src/drivers/Qt/sdl-sound.cpp index 2133d990..28c263ba 100644 --- a/src/drivers/Qt/sdl-sound.cpp +++ b/src/drivers/Qt/sdl-sound.cpp @@ -50,6 +50,7 @@ fillaudio(void *udata, uint8 *stream, int len) { + char bufStarveDetected = 0; static int16_t sample = 0; int16 *tmps = (int16*)stream; len >>= 1; @@ -65,8 +66,8 @@ fillaudio(void *udata, // Retain last known sample value, helps avoid clicking // noise when sound system is starved of audio data. //sample = 0; + bufStarveDetected = 1; nes_shm->sndBuf.starveCounter++; - //printf("Starve:%u\n", nes_shm->sndBuf.starveCounter ); } nes_shm->push_sound_sample( sample ); @@ -75,6 +76,10 @@ fillaudio(void *udata, tmps++; len--; } + if ( bufStarveDetected ) + { + //printf("Starve:%u\n", nes_shm->sndBuf.starveCounter ); + } } /** diff --git a/src/drivers/Qt/sdl-throttle.cpp b/src/drivers/Qt/sdl-throttle.cpp index f6a52eb3..cafaad5c 100644 --- a/src/drivers/Qt/sdl-throttle.cpp +++ b/src/drivers/Qt/sdl-throttle.cpp @@ -4,6 +4,7 @@ #include "Qt/sdl.h" #include "Qt/throttle.h" #include "Qt/GameApp.h" +#include "Qt/fceuWrapper.h" static const double Slowest = 0.015625; // 1/64x speed (around 1 fps on NTSC) static const double Fastest = 32; // 32x speed (around 1920 fps on NTSC) @@ -45,7 +46,7 @@ RefreshThrottleFPS() //printf("FrameTime: %llu %llu %f %lf \n", fps, fps >> 24, hz, desired_frametime ); - gameWindow->setCyclePeriodms( T ); + //gameWindow->setCyclePeriodms( T ); Lasttime=0; Nexttime=0; @@ -58,19 +59,19 @@ RefreshThrottleFPS() int SpeedThrottle() { - return 0; - - if(g_fpsScale >= 32) + if (g_fpsScale >= 32) { return 0; /* Done waiting */ } uint64 time_left; uint64 cur_time; - if(!Lasttime) + if (!Lasttime) + { Lasttime = SDL_GetTicks(); + } - if(!InFrame) + if (!InFrame) { InFrame = 1; Nexttime = Lasttime + desired_frametime * 1000; @@ -89,17 +90,21 @@ SpeedThrottle() /* 50 ms wait gives us a 20 Hz responsetime which is nice. */ } else + { InFrame = 0; + } //fprintf(stderr, "attempting to sleep %Ld ms, frame complete=%s\n", // time_left, InFrame?"no":"yes"); if ( time_left > 0 ) { + //fceuWrapperUnLock(); SDL_Delay(time_left); + //fceuWrapperLock(); } - if(!InFrame) + if (!InFrame) { Lasttime = SDL_GetTicks(); return 0; /* Done waiting */