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/dface.h"
#include "Qt/config.h"
#include "Qt/throttle.h"
#include "Qt/fceuWrapper.h"
#include "Qt/ConsoleWindow.h"
#include "Qt/ConsoleUtilities.h"
@ -148,6 +149,9 @@ ConsoleVideoConfDialog_t::ConsoleVideoConfDialog_t(QWidget *parent)
// Enable New PPU Checkbox
frmskipcbx = new QCheckBox( tr("Enable Frameskip") );
// Use Integer Frame Rate Checkbox
intFrameRateCbx = new QCheckBox( tr("Use Integer Frame Rate") );
// Disable Sprite Limit Checkbox
sprtLimCbx = new QCheckBox( tr("Disable Sprite Limit") );
@ -166,12 +170,13 @@ ConsoleVideoConfDialog_t::ConsoleVideoConfDialog_t(QWidget *parent)
// Draw Input Aids
drawInputAidsCbx = new QCheckBox( tr("Draw Input Aids") );
setCheckBoxFromProperty( autoRegion , "SDL.AutoDetectPAL");
setCheckBoxFromProperty( new_PPU_ena , "SDL.NewPPU");
setCheckBoxFromProperty( frmskipcbx , "SDL.Frameskip");
setCheckBoxFromProperty( sprtLimCbx , "SDL.DisableSpriteLimit");
setCheckBoxFromProperty( clipSidesCbx , "SDL.ClipSides");
setCheckBoxFromProperty( showFPS_cbx , "SDL.ShowFPS");
setCheckBoxFromProperty( autoRegion , "SDL.AutoDetectPAL");
setCheckBoxFromProperty( new_PPU_ena , "SDL.NewPPU");
setCheckBoxFromProperty( frmskipcbx , "SDL.Frameskip");
setCheckBoxFromProperty( intFrameRateCbx , "SDL.IntFrameRate");
setCheckBoxFromProperty( sprtLimCbx , "SDL.DisableSpriteLimit");
setCheckBoxFromProperty( clipSidesCbx , "SDL.ClipSides");
setCheckBoxFromProperty( showFPS_cbx , "SDL.ShowFPS");
setCheckBoxFromProperty( drawInputAidsCbx, "SDL.DrawInputAids" );
if ( consoleWindow )
@ -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(autoRegion , SIGNAL(stateChanged(int)), this, SLOT(autoRegionChanged(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(clipSidesCbx , SIGNAL(stateChanged(int)), this, SLOT(clipSidesChanged(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( new_PPU_ena );
vbox1->addWidget( frmskipcbx );
vbox1->addWidget( intFrameRateCbx );
vbox1->addWidget( sprtLimCbx );
vbox1->addWidget( drawInputAidsCbx );
vbox1->addWidget( showFPS_cbx );
@ -689,6 +696,18 @@ void ConsoleVideoConfDialog_t::frameskip_changed( int value )
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 )
{
//printf("Value:%i \n", value );

View File

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

View File

@ -38,6 +38,7 @@
#endif
#include "Qt/nes_shm.h"
#include "Qt/throttle.h"
#include "Qt/fceuWrapper.h"
#include "Qt/ConsoleViewerGL.h"
@ -100,6 +101,8 @@ ConsoleViewGL_t::ConsoleViewGL_t(QWidget *parent)
g_config->getOption ("SDL.ForceAspect", &forceAspect);
}
connect( this, SIGNAL(frameSwapped(void)), this, SLOT(renderFinished(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 );
}
void ConsoleViewGL_t::renderFinished(void)
{
videoBufferSwapMark();
}
void ConsoleViewGL_t::paintGL(void)
{
int texture_width = nes_shm->video.ncol;

View File

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

View File

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

View File

@ -152,6 +152,7 @@ consoleWin_t::consoleWin_t(QWidget *parent)
emulatorThread = new emulatorThread_t(this);
connect(emulatorThread, &QThread::finished, emulatorThread, &QObject::deleteLater);
connect(emulatorThread, SIGNAL(frameFinished(void)), this, SLOT(emuFrameFinish(void)) );
connect( gameTimer, &QTimer::timeout, this, &consoleWin_t::updatePeriodic );
@ -3974,15 +3975,8 @@ void consoleWin_t::loadMostRecentROM(void)
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 )
{
nes_shm->blitUpdated = 0;
@ -3992,12 +3986,31 @@ void consoleWin_t::updatePeriodic(void)
viewport_SDL->transfer2LocalBuffer();
viewport_SDL->render();
}
else
else if ( viewport_GL )
{
viewport_GL->transfer2LocalBuffer();
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
if ( (updateCounter % 30) == 0 )
@ -4271,6 +4284,11 @@ void emulatorThread_t::run(void)
emit finished();
}
void emulatorThread_t::signalFrameFinished(void)
{
emit frameFinished();
}
//-----------------------------------------------------------------------------
// Custom QMenuBar for Console
//-----------------------------------------------------------------------------

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -44,10 +44,15 @@ static double frameDeltaMax = 0.0;
static double frameIdleCur = 0.0;
static double frameIdleMin = 1.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 int InFrame = 0;
double g_fpsScale = Normal; // used by sdl.cpp
bool MaxSpeed = false;
bool useIntFrameRate = false;
double getHighPrecTimeStamp(void)
{
@ -176,9 +181,34 @@ int getFrameTimingStats( struct frameTimingStat_t *stats )
stats->frameTimeWork.max = 0;
}
stats->videoTimeDel.tgt = desired_frametime;
stats->videoTimeDel.cur = videoPeriodCur;
stats->videoTimeDel.min = videoPeriodMin;
stats->videoTimeDel.max = videoPeriodMax;
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)
{
frameLateCounter = 0;
@ -186,6 +216,8 @@ void resetFrameTiming(void)
frameDeltaMin = 1.0;
frameIdleMax = 0.0;
frameIdleMin = 1.0;
videoPeriodMin = 1.0;
videoPeriodMax = 0.0;
}
/* LOGMUL = exp(log(2) / 3)
@ -204,19 +236,23 @@ void resetFrameTiming(void)
void
RefreshThrottleFPS(void)
{
double hz;
double hz;
int32_t fps = FCEUI_GetDesiredFPS(); // Do >> 24 to get in Hz
int32_t T;
hz = ( ((double)fps) / 16777216.0 );
hz = ( ((double)fps) / 16777216.0 );
if ( useIntFrameRate )
{
hz = (double)( (int)(hz) );
}
desired_frametime = 1.0 / ( hz * g_fpsScale );
T = (int32_t)( desired_frametime * 1000.0 );
if ( T < 0 ) T = 1;
//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 );
Lasttime=0;
Nexttime=0;

View File

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