Moved FCEU emulation to its own thread for better timing control.
This commit is contained in:
parent
c496c0f281
commit
acc02ee98e
|
@ -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();
|
||||||
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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 ();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
{
|
{
|
||||||
|
|
|
@ -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 );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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 */
|
||||||
|
|
Loading…
Reference in New Issue