Moved FCEU emulation to its own thread for better timing control.

This commit is contained in:
Matthew Budd 2020-07-05 16:11:53 -04:00
parent c496c0f281
commit acc02ee98e
9 changed files with 157 additions and 24 deletions

View File

@ -7,6 +7,7 @@
#include "Qt/GameSoundConf.h" #include "Qt/GameSoundConf.h"
#include "Qt/fceuWrapper.h" #include "Qt/fceuWrapper.h"
#include "Qt/keyscan.h" #include "Qt/keyscan.h"
#include "Qt/nes_shm.h"
gameWin_t::gameWin_t(QWidget *parent) gameWin_t::gameWin_t(QWidget *parent)
: QMainWindow( parent ) : QMainWindow( parent )
@ -19,25 +20,45 @@ gameWin_t::gameWin_t(QWidget *parent)
setCentralWidget(viewport); 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 ); connect( gameTimer, &QTimer::timeout, this, &gameWin_t::runGameFrame );
gameTimer->setTimerType( Qt::PreciseTimer ); gameTimer->setTimerType( Qt::PreciseTimer );
gameTimer->start( 16 ); gameTimer->start( 16 );
gameThread->start();
gamePadConfWin = NULL; gamePadConfWin = NULL;
} }
gameWin_t::~gameWin_t(void) gameWin_t::~gameWin_t(void)
{ {
nes_shm->runEmulator = 0;
if ( gamePadConfWin != NULL ) if ( gamePadConfWin != NULL )
{ {
gamePadConfWin->closeWindow(); gamePadConfWin->closeWindow();
} }
fceuWrapperLock();
fceuWrapperClose(); fceuWrapperClose();
fceuWrapperUnLock();
//printf("Thread Finished: %i \n", gameThread->isFinished() );
gameThread->exit(0);
gameThread->wait();
delete viewport; delete viewport;
delete mutex;
} }
void gameWin_t::setCyclePeriodms( int ms ) void gameWin_t::setCyclePeriodms( int ms )
@ -142,7 +163,11 @@ void gameWin_t::createMainMenu(void)
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
void gameWin_t::closeApp(void) void gameWin_t::closeApp(void)
{ {
nes_shm->runEmulator = 0;
fceuWrapperLock();
fceuWrapperClose(); fceuWrapperClose();
fceuWrapperUnLock();
// LoadGame() checks for an IP and if it finds one begins a network session // LoadGame() checks for an IP and if it finds one begins a network session
// clear the NetworkIP field so this doesn't happen unintentionally // 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(); qDebug() << "selected file path : " << filename.toUtf8();
g_config->setOption ("SDL.LastOpenFile", filename.toStdString().c_str() ); g_config->setOption ("SDL.LastOpenFile", filename.toStdString().c_str() );
fceuWrapperLock();
CloseGame (); CloseGame ();
LoadGame ( filename.toStdString().c_str() ); LoadGame ( filename.toStdString().c_str() );
fceuWrapperUnLock();
return; return;
} }
void gameWin_t::closeROMCB(void) void gameWin_t::closeROMCB(void)
{ {
fceuWrapperLock();
CloseGame(); CloseGame();
fceuWrapperUnLock();
} }
void gameWin_t::openGamePadConfWin(void) 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); //t = (double)ts.tv_sec + (double)(ts.tv_nsec * 1.0e-9);
//printf("Run Frame %f\n", t); //printf("Run Frame %f\n", t);
fceuWrapperUpdate();
viewport->repaint(); viewport->repaint();
return; 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();
}

View File

@ -14,11 +14,23 @@
#include <QHBoxLayout> #include <QHBoxLayout>
#include <QKeyEvent> #include <QKeyEvent>
#include <QTimer> #include <QTimer>
#include <QThread>
#include <QMutex>
#include "Qt/GameViewerGL.h" #include "Qt/GameViewerGL.h"
#include "Qt/GameViewerSDL.h" #include "Qt/GameViewerSDL.h"
#include "Qt/GamePadConf.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 class gameWin_t : public QMainWindow
{ {
Q_OBJECT Q_OBJECT
@ -32,6 +44,8 @@ class gameWin_t : public QMainWindow
void setCyclePeriodms( int ms ); void setCyclePeriodms( int ms );
QMutex *mutex;
protected: protected:
QMenu *fileMenu; QMenu *fileMenu;
QMenu *optMenu; QMenu *optMenu;
@ -45,6 +59,8 @@ class gameWin_t : public QMainWindow
QAction *aboutAct; QAction *aboutAct;
QTimer *gameTimer; QTimer *gameTimer;
QThread *gameThread;
gameWorkerThread_t *worker;
GamePadConfDialog_t *gamePadConfWin; GamePadConfDialog_t *gamePadConfWin;

View File

@ -14,6 +14,7 @@
#include "Qt/sdl-video.h" #include "Qt/sdl-video.h"
#include "Qt/nes_shm.h" #include "Qt/nes_shm.h"
#include "Qt/unix-netplay.h" #include "Qt/unix-netplay.h"
#include "Qt/GameApp.h"
#include "common/cheat.h" #include "common/cheat.h"
#include "../../fceu.h" #include "../../fceu.h"
@ -55,6 +56,7 @@ static int inited = 0;
static int noconfig=0; static int noconfig=0;
static int frameskip=0; static int frameskip=0;
static int periodic_saves = 0; static int periodic_saves = 0;
static bool mutexLocked = 0;
extern double g_fpsScale; extern double g_fpsScale;
//***************************************************************** //*****************************************************************
@ -711,13 +713,13 @@ FCEUD_Update(uint8 *XBuf,
} }
else else
{ {
if (!NoWaiting && (!(eoptions&EO_NOTHROTTLE) || FCEUI_EmulationPaused())) //if (!NoWaiting && (!(eoptions&EO_NOTHROTTLE) || FCEUI_EmulationPaused()))
{ //{
while (SpeedThrottle()) // while (SpeedThrottle())
{ // {
FCEUD_UpdateInput(); // FCEUD_UpdateInput();
} // }
} //}
if (XBuf && (inited&4)) if (XBuf && (inited&4))
{ {
BlitScreen(XBuf); blitDone = 1; 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 ) int fceuWrapperUpdate( void )
{ {
fceuWrapperLock();
if (GameInfo) if (GameInfo)
{ {
DoFun(frameskip, periodic_saves); DoFun(frameskip, periodic_saves);
}
fceuWrapperUnLock();
while ( SpeedThrottle() )
{
FCEUD_UpdateInput();
}
}
else
{
fceuWrapperUnLock();
usleep( 100000 );
}
return 0; return 0;
} }

View File

@ -25,4 +25,8 @@ int CloseGame(void);
int fceuWrapperInit( int argc, char *argv[] ); int fceuWrapperInit( int argc, char *argv[] );
int fceuWrapperClose( void ); int fceuWrapperClose( void );
int fceuWrapperUpdate( void ); int fceuWrapperUpdate( void );
void fceuWrapperLock(void);
bool fceuWrapperTryLock(int timeout);
bool fceuWrapperIsLocked(void);
void fceuWrapperUnLock(void);

View File

@ -611,10 +611,10 @@ static void KeyboardCommands (void)
if (_keyonly (Hotkeys[HK_DECREASE_SPEED])) if (_keyonly (Hotkeys[HK_DECREASE_SPEED]))
{ {
DecreaseEmulationSpeed (); DecreaseEmulationSpeed();
} }
if (_keyonly (Hotkeys[HK_INCREASE_SPEED])) if (_keyonly(Hotkeys[HK_INCREASE_SPEED]))
{ {
IncreaseEmulationSpeed (); IncreaseEmulationSpeed ();
} }

View File

@ -7,17 +7,24 @@ gameWin_t *gameWindow = NULL;
int main( int argc, char *argv[] ) int main( int argc, char *argv[] )
{ {
int retval;
QApplication app(argc, argv); QApplication app(argc, argv);
gameWindow = new gameWin_t();
fceuWrapperInit( argc, argv ); fceuWrapperInit( argc, argv );
gameWindow = new gameWin_t();
gameWindow->resize( 512, 512 ); gameWindow->resize( 512, 512 );
gameWindow->show(); gameWindow->show();
gameWindow->viewport->init(); gameWindow->viewport->init();
return app.exec(); retval = app.exec();
//printf("App Return: %i \n", retval );
delete gameWindow;
return retval;
} }

View File

@ -24,6 +24,8 @@ struct nes_shm_t
int nrow; int nrow;
int pitch; int pitch;
char runEmulator;
// Pass Key Events back to QT Gui // Pass Key Events back to QT Gui
struct struct
{ {

View File

@ -50,6 +50,7 @@ fillaudio(void *udata,
uint8 *stream, uint8 *stream,
int len) int len)
{ {
char bufStarveDetected = 0;
static int16_t sample = 0; static int16_t sample = 0;
int16 *tmps = (int16*)stream; int16 *tmps = (int16*)stream;
len >>= 1; len >>= 1;
@ -65,8 +66,8 @@ fillaudio(void *udata,
// Retain last known sample value, helps avoid clicking // Retain last known sample value, helps avoid clicking
// noise when sound system is starved of audio data. // noise when sound system is starved of audio data.
//sample = 0; //sample = 0;
bufStarveDetected = 1;
nes_shm->sndBuf.starveCounter++; nes_shm->sndBuf.starveCounter++;
//printf("Starve:%u\n", nes_shm->sndBuf.starveCounter );
} }
nes_shm->push_sound_sample( sample ); nes_shm->push_sound_sample( sample );
@ -75,6 +76,10 @@ fillaudio(void *udata,
tmps++; tmps++;
len--; len--;
} }
if ( bufStarveDetected )
{
//printf("Starve:%u\n", nes_shm->sndBuf.starveCounter );
}
} }
/** /**

View File

@ -4,6 +4,7 @@
#include "Qt/sdl.h" #include "Qt/sdl.h"
#include "Qt/throttle.h" #include "Qt/throttle.h"
#include "Qt/GameApp.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 Slowest = 0.015625; // 1/64x speed (around 1 fps on NTSC)
static const double Fastest = 32; // 32x speed (around 1920 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 ); //printf("FrameTime: %llu %llu %f %lf \n", fps, fps >> 24, hz, desired_frametime );
gameWindow->setCyclePeriodms( T ); //gameWindow->setCyclePeriodms( T );
Lasttime=0; Lasttime=0;
Nexttime=0; Nexttime=0;
@ -58,19 +59,19 @@ RefreshThrottleFPS()
int int
SpeedThrottle() SpeedThrottle()
{ {
return 0; if (g_fpsScale >= 32)
if(g_fpsScale >= 32)
{ {
return 0; /* Done waiting */ return 0; /* Done waiting */
} }
uint64 time_left; uint64 time_left;
uint64 cur_time; uint64 cur_time;
if(!Lasttime) if (!Lasttime)
{
Lasttime = SDL_GetTicks(); Lasttime = SDL_GetTicks();
}
if(!InFrame) if (!InFrame)
{ {
InFrame = 1; InFrame = 1;
Nexttime = Lasttime + desired_frametime * 1000; Nexttime = Lasttime + desired_frametime * 1000;
@ -89,17 +90,21 @@ SpeedThrottle()
/* 50 ms wait gives us a 20 Hz responsetime which is nice. */ /* 50 ms wait gives us a 20 Hz responsetime which is nice. */
} }
else else
{
InFrame = 0; InFrame = 0;
}
//fprintf(stderr, "attempting to sleep %Ld ms, frame complete=%s\n", //fprintf(stderr, "attempting to sleep %Ld ms, frame complete=%s\n",
// time_left, InFrame?"no":"yes"); // time_left, InFrame?"no":"yes");
if ( time_left > 0 ) if ( time_left > 0 )
{ {
//fceuWrapperUnLock();
SDL_Delay(time_left); SDL_Delay(time_left);
//fceuWrapperLock();
} }
if(!InFrame) if (!InFrame)
{ {
Lasttime = SDL_GetTicks(); Lasttime = SDL_GetTicks();
return 0; /* Done waiting */ return 0; /* Done waiting */