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/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();
}

View File

@ -14,11 +14,23 @@
#include <QHBoxLayout>
#include <QKeyEvent>
#include <QTimer>
#include <QThread>
#include <QMutex>
#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;

View File

@ -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;
}

View File

@ -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);

View File

@ -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 ();
}

View File

@ -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;
}

View File

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

View File

@ -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 );
}
}
/**

View File

@ -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 */