Added an integer frame rate option to Qt GUI better sync with video.

This commit is contained in:
mjbudd77 2021-07-31 20:07:18 -04:00
parent 6b65d1264d
commit c978c1631a
13 changed files with 146 additions and 19 deletions

View File

@ -26,6 +26,7 @@
#include "Qt/main.h" #include "Qt/main.h"
#include "Qt/dface.h" #include "Qt/dface.h"
#include "Qt/config.h" #include "Qt/config.h"
#include "Qt/throttle.h"
#include "Qt/fceuWrapper.h" #include "Qt/fceuWrapper.h"
#include "Qt/ConsoleWindow.h" #include "Qt/ConsoleWindow.h"
#include "Qt/ConsoleUtilities.h" #include "Qt/ConsoleUtilities.h"
@ -148,6 +149,9 @@ ConsoleVideoConfDialog_t::ConsoleVideoConfDialog_t(QWidget *parent)
// Enable New PPU Checkbox // Enable New PPU Checkbox
frmskipcbx = new QCheckBox( tr("Enable Frameskip") ); frmskipcbx = new QCheckBox( tr("Enable Frameskip") );
// Use Integer Frame Rate Checkbox
intFrameRateCbx = new QCheckBox( tr("Use Integer Frame Rate") );
// Disable Sprite Limit Checkbox // Disable Sprite Limit Checkbox
sprtLimCbx = new QCheckBox( tr("Disable Sprite Limit") ); sprtLimCbx = new QCheckBox( tr("Disable Sprite Limit") );
@ -169,6 +173,7 @@ ConsoleVideoConfDialog_t::ConsoleVideoConfDialog_t(QWidget *parent)
setCheckBoxFromProperty( autoRegion , "SDL.AutoDetectPAL"); setCheckBoxFromProperty( autoRegion , "SDL.AutoDetectPAL");
setCheckBoxFromProperty( new_PPU_ena , "SDL.NewPPU"); setCheckBoxFromProperty( new_PPU_ena , "SDL.NewPPU");
setCheckBoxFromProperty( frmskipcbx , "SDL.Frameskip"); setCheckBoxFromProperty( frmskipcbx , "SDL.Frameskip");
setCheckBoxFromProperty( intFrameRateCbx , "SDL.IntFrameRate");
setCheckBoxFromProperty( sprtLimCbx , "SDL.DisableSpriteLimit"); setCheckBoxFromProperty( sprtLimCbx , "SDL.DisableSpriteLimit");
setCheckBoxFromProperty( clipSidesCbx , "SDL.ClipSides"); setCheckBoxFromProperty( clipSidesCbx , "SDL.ClipSides");
setCheckBoxFromProperty( showFPS_cbx , "SDL.ShowFPS"); setCheckBoxFromProperty( showFPS_cbx , "SDL.ShowFPS");
@ -191,6 +196,7 @@ ConsoleVideoConfDialog_t::ConsoleVideoConfDialog_t(QWidget *parent)
connect(new_PPU_ena , SIGNAL(clicked(bool)) , this, SLOT(use_new_PPU_changed(bool)) ); connect(new_PPU_ena , SIGNAL(clicked(bool)) , this, SLOT(use_new_PPU_changed(bool)) );
connect(autoRegion , SIGNAL(stateChanged(int)), this, SLOT(autoRegionChanged(int)) ); connect(autoRegion , SIGNAL(stateChanged(int)), this, SLOT(autoRegionChanged(int)) );
connect(frmskipcbx , SIGNAL(stateChanged(int)), this, SLOT(frameskip_changed(int)) ); connect(frmskipcbx , SIGNAL(stateChanged(int)), this, SLOT(frameskip_changed(int)) );
connect(intFrameRateCbx , SIGNAL(stateChanged(int)), this, SLOT(intFrameRate_changed(int)) );
connect(sprtLimCbx , SIGNAL(stateChanged(int)), this, SLOT(useSpriteLimitChanged(int)) ); connect(sprtLimCbx , SIGNAL(stateChanged(int)), this, SLOT(useSpriteLimitChanged(int)) );
connect(clipSidesCbx , SIGNAL(stateChanged(int)), this, SLOT(clipSidesChanged(int)) ); connect(clipSidesCbx , SIGNAL(stateChanged(int)), this, SLOT(clipSidesChanged(int)) );
connect(showFPS_cbx , SIGNAL(stateChanged(int)), this, SLOT(showFPSChanged(int)) ); connect(showFPS_cbx , SIGNAL(stateChanged(int)), this, SLOT(showFPSChanged(int)) );
@ -201,6 +207,7 @@ ConsoleVideoConfDialog_t::ConsoleVideoConfDialog_t(QWidget *parent)
vbox1->addWidget( autoRegion ); vbox1->addWidget( autoRegion );
vbox1->addWidget( new_PPU_ena ); vbox1->addWidget( new_PPU_ena );
vbox1->addWidget( frmskipcbx ); vbox1->addWidget( frmskipcbx );
vbox1->addWidget( intFrameRateCbx );
vbox1->addWidget( sprtLimCbx ); vbox1->addWidget( sprtLimCbx );
vbox1->addWidget( drawInputAidsCbx ); vbox1->addWidget( drawInputAidsCbx );
vbox1->addWidget( showFPS_cbx ); vbox1->addWidget( showFPS_cbx );
@ -689,6 +696,18 @@ void ConsoleVideoConfDialog_t::frameskip_changed( int value )
fceuWrapperUnLock(); fceuWrapperUnLock();
} }
//---------------------------------------------------- //----------------------------------------------------
void ConsoleVideoConfDialog_t::intFrameRate_changed( int value )
{
//printf("Value:%i \n", value );
useIntFrameRate = (value != Qt::Unchecked);
g_config->setOption("SDL.IntFrameRate", useIntFrameRate );
g_config->save ();
fceuWrapperLock();
RefreshThrottleFPS();
fceuWrapperUnLock();
}
//----------------------------------------------------
void ConsoleVideoConfDialog_t::useSpriteLimitChanged( int value ) void ConsoleVideoConfDialog_t::useSpriteLimitChanged( int value )
{ {
//printf("Value:%i \n", value ); //printf("Value:%i \n", value );

View File

@ -47,6 +47,7 @@ class ConsoleVideoConfDialog_t : public QDialog
QCheckBox *aspectCbx; QCheckBox *aspectCbx;
QCheckBox *cursorVisCbx; QCheckBox *cursorVisCbx;
QCheckBox *drawInputAidsCbx; QCheckBox *drawInputAidsCbx;
QCheckBox *intFrameRateCbx;
QDoubleSpinBox *xScaleBox; QDoubleSpinBox *xScaleBox;
QDoubleSpinBox *yScaleBox; QDoubleSpinBox *yScaleBox;
QLabel *aspectSelectLabel; QLabel *aspectSelectLabel;
@ -78,6 +79,7 @@ class ConsoleVideoConfDialog_t : public QDialog
void aspectEnableChanged( int value ); void aspectEnableChanged( int value );
void use_new_PPU_changed( bool value ); void use_new_PPU_changed( bool value );
void frameskip_changed( int value ); void frameskip_changed( int value );
void intFrameRate_changed( int value );
void useSpriteLimitChanged( int value ); void useSpriteLimitChanged( int value );
void clipSidesChanged( int value ); void clipSidesChanged( int value );
void showFPSChanged( int value ); void showFPSChanged( int value );

View File

@ -38,6 +38,7 @@
#endif #endif
#include "Qt/nes_shm.h" #include "Qt/nes_shm.h"
#include "Qt/throttle.h"
#include "Qt/fceuWrapper.h" #include "Qt/fceuWrapper.h"
#include "Qt/ConsoleViewerGL.h" #include "Qt/ConsoleViewerGL.h"
@ -100,6 +101,8 @@ ConsoleViewGL_t::ConsoleViewGL_t(QWidget *parent)
g_config->getOption ("SDL.ForceAspect", &forceAspect); g_config->getOption ("SDL.ForceAspect", &forceAspect);
} }
connect( this, SIGNAL(frameSwapped(void)), this, SLOT(renderFinished(void)) );
} }
ConsoleViewGL_t::~ConsoleViewGL_t(void) ConsoleViewGL_t::~ConsoleViewGL_t(void)
@ -496,6 +499,11 @@ void ConsoleViewGL_t::getNormalizedCursorPos( double &x, double &y )
//printf("Normalized Cursor (%f,%f) \n", x, y ); //printf("Normalized Cursor (%f,%f) \n", x, y );
} }
void ConsoleViewGL_t::renderFinished(void)
{
videoBufferSwapMark();
}
void ConsoleViewGL_t::paintGL(void) void ConsoleViewGL_t::paintGL(void)
{ {
int texture_width = nes_shm->video.ncol; int texture_width = nes_shm->video.ncol;

View File

@ -80,5 +80,6 @@ class ConsoleViewGL_t : public QOpenGLWidget, protected QOpenGLFunctions
private slots: private slots:
void cleanupGL(void); void cleanupGL(void);
void renderFinished(void);
}; };

View File

@ -26,6 +26,7 @@
//#include <unistd.h> //#include <unistd.h>
#include "Qt/nes_shm.h" #include "Qt/nes_shm.h"
#include "Qt/throttle.h"
#include "Qt/fceuWrapper.h" #include "Qt/fceuWrapper.h"
#include "Qt/ConsoleViewerSDL.h" #include "Qt/ConsoleViewerSDL.h"
@ -650,5 +651,7 @@ void ConsoleViewSDL_t::render(void)
SDL_RenderPresent(sdlRenderer); SDL_RenderPresent(sdlRenderer);
videoBufferSwapMark();
nes_shm->render_count++; nes_shm->render_count++;
} }

View File

@ -152,6 +152,7 @@ consoleWin_t::consoleWin_t(QWidget *parent)
emulatorThread = new emulatorThread_t(this); emulatorThread = new emulatorThread_t(this);
connect(emulatorThread, &QThread::finished, emulatorThread, &QObject::deleteLater); connect(emulatorThread, &QThread::finished, emulatorThread, &QObject::deleteLater);
connect(emulatorThread, SIGNAL(frameFinished(void)), this, SLOT(emuFrameFinish(void)) );
connect( gameTimer, &QTimer::timeout, this, &consoleWin_t::updatePeriodic ); connect( gameTimer, &QTimer::timeout, this, &consoleWin_t::updatePeriodic );
@ -3974,15 +3975,8 @@ void consoleWin_t::loadMostRecentROM(void)
fceuWrapperUnLock(); fceuWrapperUnLock();
} }
void consoleWin_t::updatePeriodic(void) void consoleWin_t::transferVideoBuffer(void)
{ {
// Process all events before attempting to render viewport
QCoreApplication::processEvents();
// Update Input Devices
FCEUD_UpdateInput();
// RePaint Game Viewport
if ( nes_shm->blitUpdated ) if ( nes_shm->blitUpdated )
{ {
nes_shm->blitUpdated = 0; nes_shm->blitUpdated = 0;
@ -3992,12 +3986,31 @@ void consoleWin_t::updatePeriodic(void)
viewport_SDL->transfer2LocalBuffer(); viewport_SDL->transfer2LocalBuffer();
viewport_SDL->render(); viewport_SDL->render();
} }
else else if ( viewport_GL )
{ {
viewport_GL->transfer2LocalBuffer(); viewport_GL->transfer2LocalBuffer();
viewport_GL->update(); viewport_GL->update();
} }
} }
}
void consoleWin_t::emuFrameFinish(void)
{
//printf("EMU Frame Finish\n");
transferVideoBuffer();
}
void consoleWin_t::updatePeriodic(void)
{
// Process all events before attempting to render viewport
QCoreApplication::processEvents();
// Update Input Devices
FCEUD_UpdateInput();
// RePaint Game Viewport
transferVideoBuffer();
// Low Rate Updates // Low Rate Updates
if ( (updateCounter % 30) == 0 ) if ( (updateCounter % 30) == 0 )
@ -4271,6 +4284,11 @@ void emulatorThread_t::run(void)
emit finished(); emit finished();
} }
void emulatorThread_t::signalFrameFinished(void)
{
emit frameFinished();
}
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
// Custom QMenuBar for Console // Custom QMenuBar for Console
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------

View File

@ -51,6 +51,7 @@ class emulatorThread_t : public QThread
int getMinSchedPriority(void); int getMinSchedPriority(void);
int getMaxSchedPriority(void); int getMaxSchedPriority(void);
#endif #endif
void signalFrameFinished(void);
private: private:
void init(void); void init(void);
@ -60,7 +61,8 @@ class emulatorThread_t : public QThread
#endif #endif
signals: signals:
void finished(); void finished(void);
void frameFinished(void);
}; };
class consoleMenuBar : public QMenuBar class consoleMenuBar : public QMenuBar
@ -280,6 +282,7 @@ class consoleWin_t : public QMainWindow
void changeState(int slot); void changeState(int slot);
void saveState(int slot); void saveState(int slot);
void loadState(int slot); void loadState(int slot);
void transferVideoBuffer(void);
void syncAutoFirePatternMenu(void); void syncAutoFirePatternMenu(void);
public slots: public slots:
@ -413,6 +416,7 @@ class consoleWin_t : public QMainWindow
void wavRecordAsStart(void); void wavRecordAsStart(void);
void wavRecordStop(void); void wavRecordStop(void);
void winScreenChanged( QScreen *scr ); void winScreenChanged( QScreen *scr );
void emuFrameFinish(void);
}; };

View File

@ -86,6 +86,7 @@ FrameTimingDialog_t::FrameTimingDialog_t(QWidget *parent)
frameTimeWorkPct = new QTreeWidgetItem(); frameTimeWorkPct = new QTreeWidgetItem();
frameTimeIdlePct = new QTreeWidgetItem(); frameTimeIdlePct = new QTreeWidgetItem();
frameLateCount = new QTreeWidgetItem(); frameLateCount = new QTreeWidgetItem();
videoTimeAbs = new QTreeWidgetItem();
tree->addTopLevelItem(frameTimeAbs); tree->addTopLevelItem(frameTimeAbs);
tree->addTopLevelItem(frameTimeDel); tree->addTopLevelItem(frameTimeDel);
@ -93,6 +94,7 @@ FrameTimingDialog_t::FrameTimingDialog_t(QWidget *parent)
tree->addTopLevelItem(frameTimeIdle); tree->addTopLevelItem(frameTimeIdle);
tree->addTopLevelItem(frameTimeWorkPct); tree->addTopLevelItem(frameTimeWorkPct);
tree->addTopLevelItem(frameTimeIdlePct); tree->addTopLevelItem(frameTimeIdlePct);
tree->addTopLevelItem(videoTimeAbs);
tree->addTopLevelItem(frameLateCount); tree->addTopLevelItem(frameLateCount);
frameTimeAbs->setFlags(Qt::ItemIsEnabled | Qt::ItemNeverHasChildren); frameTimeAbs->setFlags(Qt::ItemIsEnabled | Qt::ItemNeverHasChildren);
@ -105,6 +107,7 @@ FrameTimingDialog_t::FrameTimingDialog_t(QWidget *parent)
frameTimeWorkPct->setText(0, tr("Frame Work %")); frameTimeWorkPct->setText(0, tr("Frame Work %"));
frameTimeIdlePct->setText(0, tr("Frame Idle %")); frameTimeIdlePct->setText(0, tr("Frame Idle %"));
frameLateCount->setText(0, tr("Frame Late Count")); frameLateCount->setText(0, tr("Frame Late Count"));
videoTimeAbs->setText(0, tr("Video Period ms"));
frameTimeAbs->setTextAlignment(0, Qt::AlignLeft); frameTimeAbs->setTextAlignment(0, Qt::AlignLeft);
frameTimeDel->setTextAlignment(0, Qt::AlignLeft); frameTimeDel->setTextAlignment(0, Qt::AlignLeft);
@ -113,6 +116,7 @@ FrameTimingDialog_t::FrameTimingDialog_t(QWidget *parent)
frameTimeWorkPct->setTextAlignment(0, Qt::AlignLeft); frameTimeWorkPct->setTextAlignment(0, Qt::AlignLeft);
frameTimeIdlePct->setTextAlignment(0, Qt::AlignLeft); frameTimeIdlePct->setTextAlignment(0, Qt::AlignLeft);
frameLateCount->setTextAlignment(0, Qt::AlignLeft); frameLateCount->setTextAlignment(0, Qt::AlignLeft);
videoTimeAbs->setTextAlignment(0, Qt::AlignLeft);
for (int i = 0; i < 4; i++) for (int i = 0; i < 4; i++)
{ {
@ -123,6 +127,7 @@ FrameTimingDialog_t::FrameTimingDialog_t(QWidget *parent)
frameTimeWorkPct->setTextAlignment(i + 1, Qt::AlignCenter); frameTimeWorkPct->setTextAlignment(i + 1, Qt::AlignCenter);
frameTimeIdlePct->setTextAlignment(i + 1, Qt::AlignCenter); frameTimeIdlePct->setTextAlignment(i + 1, Qt::AlignCenter);
frameLateCount->setTextAlignment(i + 1, Qt::AlignCenter); frameLateCount->setTextAlignment(i + 1, Qt::AlignCenter);
videoTimeAbs->setTextAlignment(i + 1, Qt::AlignCenter);
} }
hbox = new QHBoxLayout(); hbox = new QHBoxLayout();
@ -268,6 +273,19 @@ void FrameTimingDialog_t::updateTimingStats(void)
sprintf(stmp, "%.1f", 100.0 * stats.frameTimeIdle.max / stats.frameTimeAbs.tgt); sprintf(stmp, "%.1f", 100.0 * stats.frameTimeIdle.max / stats.frameTimeAbs.tgt);
frameTimeIdlePct->setText(4, tr(stmp)); frameTimeIdlePct->setText(4, tr(stmp));
// Video
sprintf(stmp, "%.3f", stats.videoTimeDel.tgt * 1e3);
videoTimeAbs->setText(1, tr(stmp));
sprintf(stmp, "%.3f", stats.videoTimeDel.cur * 1e3);
videoTimeAbs->setText(2, tr(stmp));
sprintf(stmp, "%.3f", stats.videoTimeDel.min * 1e3);
videoTimeAbs->setText(3, tr(stmp));
sprintf(stmp, "%.3f", stats.videoTimeDel.max * 1e3);
videoTimeAbs->setText(4, tr(stmp));
// Late Count // Late Count
sprintf(stmp, "%u", stats.lateCount); sprintf(stmp, "%u", stats.lateCount);
frameLateCount->setText(1, tr("0")); frameLateCount->setText(1, tr("0"));

View File

@ -39,6 +39,7 @@ protected:
QTreeWidgetItem *frameTimeIdle; QTreeWidgetItem *frameTimeIdle;
QTreeWidgetItem *frameTimeIdlePct; QTreeWidgetItem *frameTimeIdlePct;
QTreeWidgetItem *frameLateCount; QTreeWidgetItem *frameLateCount;
QTreeWidgetItem *videoTimeAbs;
QGroupBox *statFrame; QGroupBox *statFrame;
QTreeWidget *tree; QTreeWidget *tree;

View File

@ -490,6 +490,7 @@ InitConfig()
config->addOption("pal", "SDL.PAL", 0); config->addOption("pal", "SDL.PAL", 0);
config->addOption("autoPal", "SDL.AutoDetectPAL", 1); config->addOption("autoPal", "SDL.AutoDetectPAL", 1);
config->addOption("frameskip", "SDL.Frameskip", 0); config->addOption("frameskip", "SDL.Frameskip", 0);
config->addOption("intFrameRate", "SDL.IntFrameRate", 0);
config->addOption("clipsides", "SDL.ClipSides", 0); config->addOption("clipsides", "SDL.ClipSides", 0);
config->addOption("nospritelim", "SDL.DisableSpriteLimit", 1); config->addOption("nospritelim", "SDL.DisableSpriteLimit", 1);
config->addOption("swapduty", "SDL.SwapDuty", 0); config->addOption("swapduty", "SDL.SwapDuty", 0);
@ -922,6 +923,7 @@ UpdateEMUCore(Config *config)
config->getOption("SDL.Hue", &ntschue); config->getOption("SDL.Hue", &ntschue);
FCEUI_SetNTSCTH(ntsccol, ntsctint, ntschue); FCEUI_SetNTSCTH(ntsccol, ntsctint, ntschue);
config->getOption("SDL.IntFrameRate" , &useIntFrameRate);
config->getOption("SDL.ForceGrayScale", &force_grayscale); config->getOption("SDL.ForceGrayScale", &force_grayscale);
config->getOption("SDL.DeempBitSwap" , &paldeemphswap); config->getOption("SDL.DeempBitSwap" , &paldeemphswap);
config->getOption("SDL.PalNotch" , &palnotch); config->getOption("SDL.PalNotch" , &palnotch);

View File

@ -1242,6 +1242,10 @@ int fceuWrapperUpdate( void )
hexEditorUpdateMemoryValues(); hexEditorUpdateMemoryValues();
if ( consoleWindow )
{
consoleWindow->emulatorThread->signalFrameFinished();
}
fceuWrapperUnLock(); fceuWrapperUnLock();
emulatorHasMutux = 0; emulatorHasMutux = 0;

View File

@ -44,10 +44,15 @@ static double frameDeltaMax = 0.0;
static double frameIdleCur = 0.0; static double frameIdleCur = 0.0;
static double frameIdleMin = 1.0; static double frameIdleMin = 1.0;
static double frameIdleMax = 0.0; static double frameIdleMax = 0.0;
static double videoLastTs = 0.0;
static double videoPeriodCur = 0.0;
static double videoPeriodMin = 1.0;
static double videoPeriodMax = 0.0;
static bool keepFrameTimeStats = false; static bool keepFrameTimeStats = false;
static int InFrame = 0; static int InFrame = 0;
double g_fpsScale = Normal; // used by sdl.cpp double g_fpsScale = Normal; // used by sdl.cpp
bool MaxSpeed = false; bool MaxSpeed = false;
bool useIntFrameRate = false;
double getHighPrecTimeStamp(void) double getHighPrecTimeStamp(void)
{ {
@ -176,9 +181,34 @@ int getFrameTimingStats( struct frameTimingStat_t *stats )
stats->frameTimeWork.max = 0; stats->frameTimeWork.max = 0;
} }
stats->videoTimeDel.tgt = desired_frametime;
stats->videoTimeDel.cur = videoPeriodCur;
stats->videoTimeDel.min = videoPeriodMin;
stats->videoTimeDel.max = videoPeriodMax;
return 0; return 0;
} }
void videoBufferSwapMark(void)
{
if ( keepFrameTimeStats )
{
double ts = getHighPrecTimeStamp();
videoPeriodCur = ts - videoLastTs;
if ( videoPeriodCur < videoPeriodMin )
{
videoPeriodMin = videoPeriodCur;
}
if ( videoPeriodCur > videoPeriodMax )
{
videoPeriodMax = videoPeriodCur;
}
videoLastTs = ts;
}
}
void resetFrameTiming(void) void resetFrameTiming(void)
{ {
frameLateCounter = 0; frameLateCounter = 0;
@ -186,6 +216,8 @@ void resetFrameTiming(void)
frameDeltaMin = 1.0; frameDeltaMin = 1.0;
frameIdleMax = 0.0; frameIdleMax = 0.0;
frameIdleMin = 1.0; frameIdleMin = 1.0;
videoPeriodMin = 1.0;
videoPeriodMax = 0.0;
} }
/* LOGMUL = exp(log(2) / 3) /* LOGMUL = exp(log(2) / 3)
@ -210,6 +242,10 @@ RefreshThrottleFPS(void)
hz = ( ((double)fps) / 16777216.0 ); hz = ( ((double)fps) / 16777216.0 );
if ( useIntFrameRate )
{
hz = (double)( (int)(hz) );
}
desired_frametime = 1.0 / ( hz * g_fpsScale ); desired_frametime = 1.0 / ( hz * g_fpsScale );
T = (int32_t)( desired_frametime * 1000.0 ); T = (int32_t)( desired_frametime * 1000.0 );

View File

@ -36,6 +36,13 @@ struct frameTimingStat_t
double max; double max;
} frameTimeIdle; } frameTimeIdle;
struct {
double tgt;
double cur;
double min;
double max;
} videoTimeDel;
unsigned int lateCount; unsigned int lateCount;
bool enabled; bool enabled;
@ -44,3 +51,7 @@ struct frameTimingStat_t
void resetFrameTiming(void); void resetFrameTiming(void);
void setFrameTimingEnable( bool enable ); void setFrameTimingEnable( bool enable );
int getFrameTimingStats( struct frameTimingStat_t *stats ); int getFrameTimingStats( struct frameTimingStat_t *stats );
void videoBufferSwapMark(void);
double getHighPrecTimeStamp(void);
extern bool useIntFrameRate;