475 lines
9.9 KiB
C++
475 lines
9.9 KiB
C++
/* FCE Ultra - NES/Famicom Emulator
|
|
*
|
|
* Copyright notice for this file:
|
|
* Copyright (C) 2020 mjbudd77
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
/// \file
|
|
/// \brief Handles emulation speed throttling using the SDL timing functions.
|
|
|
|
#include "Qt/sdl.h"
|
|
#include "Qt/throttle.h"
|
|
|
|
#if defined(__linux__) || defined(__APPLE__) || defined(__unix__)
|
|
#include <time.h>
|
|
#endif
|
|
|
|
#ifdef __linux__
|
|
#include <sys/timerfd.h>
|
|
#endif
|
|
|
|
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 Normal = 1.0; // 1x speed (around 60 fps on NTSC)
|
|
|
|
static uint32 frameLateCounter = 0;
|
|
static double Lasttime=0, Nexttime=0, Latetime=0;
|
|
static double desired_frametime = (1.0 / 60.099823);
|
|
static double frameDeltaCur = 0.0;
|
|
static double frameDeltaMin = 1.0;
|
|
static double frameDeltaMax = 0.0;
|
|
static double frameIdleCur = 0.0;
|
|
static double frameIdleMin = 1.0;
|
|
static double frameIdleMax = 0.0;
|
|
static bool keepFrameTimeStats = false;
|
|
static int InFrame = 0;
|
|
double g_fpsScale = Normal; // used by sdl.cpp
|
|
bool MaxSpeed = false;
|
|
|
|
double getHighPrecTimeStamp(void)
|
|
{
|
|
#if defined(__linux__) || defined(__APPLE__) || defined(__unix__)
|
|
struct timespec ts;
|
|
double t;
|
|
|
|
clock_gettime( CLOCK_REALTIME, &ts );
|
|
|
|
t = (double)ts.tv_sec + (double)(ts.tv_nsec * 1.0e-9);
|
|
#else
|
|
double t;
|
|
|
|
t = (double)SDL_GetTicks();
|
|
|
|
t = t * 1e-3;
|
|
#endif
|
|
|
|
return t;
|
|
}
|
|
|
|
#ifdef __linux__
|
|
static char useTimerFD = 0;
|
|
static int timerfd = -1;
|
|
|
|
static void setTimer( double hz )
|
|
{
|
|
struct itimerspec ispec;
|
|
|
|
if ( !useTimerFD )
|
|
{
|
|
if ( timerfd != -1 )
|
|
{
|
|
::close( timerfd ); timerfd = -1;
|
|
}
|
|
return;
|
|
}
|
|
|
|
if ( timerfd == -1 )
|
|
{
|
|
timerfd = timerfd_create( CLOCK_REALTIME, 0 );
|
|
|
|
if ( timerfd == -1 )
|
|
{
|
|
perror("timerfd_create failed: ");
|
|
return;
|
|
}
|
|
}
|
|
ispec.it_interval.tv_sec = 0;
|
|
ispec.it_interval.tv_nsec = (long)( 1.0e9 / hz );
|
|
ispec.it_value.tv_sec = ispec.it_interval.tv_sec;
|
|
ispec.it_value.tv_nsec = ispec.it_interval.tv_nsec;
|
|
|
|
if ( timerfd_settime( timerfd, 0, &ispec, NULL ) == -1 )
|
|
{
|
|
perror("timerfd_settime failed: ");
|
|
}
|
|
|
|
//printf("Timer Set: %li ns\n", ispec.it_value.tv_nsec );
|
|
|
|
Lasttime = getHighPrecTimeStamp();
|
|
Nexttime = Lasttime + desired_frametime;
|
|
Latetime = Nexttime + (desired_frametime*0.50);
|
|
}
|
|
#endif
|
|
|
|
int getTimingMode(void)
|
|
{
|
|
#ifdef __linux__
|
|
if ( useTimerFD )
|
|
{
|
|
return 1;
|
|
}
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
int setTimingMode( int mode )
|
|
{
|
|
#ifdef __linux__
|
|
useTimerFD = (mode == 1);
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
void setFrameTimingEnable( bool enable )
|
|
{
|
|
keepFrameTimeStats = enable;
|
|
}
|
|
|
|
int getFrameTimingStats( struct frameTimingStat_t *stats )
|
|
{
|
|
stats->enabled = keepFrameTimeStats;
|
|
stats->lateCount = frameLateCounter;
|
|
|
|
stats->frameTimeAbs.tgt = desired_frametime;
|
|
stats->frameTimeAbs.cur = frameDeltaCur;
|
|
stats->frameTimeAbs.min = frameDeltaMin;
|
|
stats->frameTimeAbs.max = frameDeltaMax;
|
|
|
|
stats->frameTimeDel.tgt = 0.0;
|
|
stats->frameTimeDel.cur = frameDeltaCur - desired_frametime;
|
|
stats->frameTimeDel.min = frameDeltaMin - desired_frametime;
|
|
stats->frameTimeDel.max = frameDeltaMax - desired_frametime;
|
|
|
|
stats->frameTimeIdle.tgt = desired_frametime * 0.50;
|
|
stats->frameTimeIdle.cur = frameIdleCur;
|
|
stats->frameTimeIdle.min = frameIdleMin;
|
|
stats->frameTimeIdle.max = frameIdleMax;
|
|
|
|
stats->frameTimeWork.tgt = desired_frametime - stats->frameTimeIdle.tgt;
|
|
stats->frameTimeWork.cur = desired_frametime - frameIdleCur;
|
|
stats->frameTimeWork.min = desired_frametime - frameIdleMax;
|
|
stats->frameTimeWork.max = desired_frametime - frameIdleMin;
|
|
|
|
if ( stats->frameTimeWork.cur < 0 )
|
|
{
|
|
stats->frameTimeWork.cur = 0;
|
|
}
|
|
if ( stats->frameTimeWork.min < 0 )
|
|
{
|
|
stats->frameTimeWork.min = 0;
|
|
}
|
|
if ( stats->frameTimeWork.max < 0 )
|
|
{
|
|
stats->frameTimeWork.max = 0;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void resetFrameTiming(void)
|
|
{
|
|
frameLateCounter = 0;
|
|
frameDeltaMax = 0.0;
|
|
frameDeltaMin = 1.0;
|
|
frameIdleMax = 0.0;
|
|
frameIdleMin = 1.0;
|
|
}
|
|
|
|
/* LOGMUL = exp(log(2) / 3)
|
|
*
|
|
* This gives us a value such that if we do x*=LOGMUL three times,
|
|
* then after that, x is twice the value it was before.
|
|
*
|
|
* This gives us three speed steps per order of magnitude.
|
|
*
|
|
*/
|
|
#define LOGMUL 1.259921049894873
|
|
|
|
/**
|
|
* Refreshes the FPS throttling variables.
|
|
*/
|
|
void
|
|
RefreshThrottleFPS(void)
|
|
{
|
|
double hz;
|
|
int32_t fps = FCEUI_GetDesiredFPS(); // Do >> 24 to get in Hz
|
|
int32_t T;
|
|
|
|
hz = ( ((double)fps) / 16777216.0 );
|
|
|
|
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 );
|
|
|
|
Lasttime=0;
|
|
Nexttime=0;
|
|
InFrame=0;
|
|
|
|
#ifdef __linux__
|
|
setTimer( hz * g_fpsScale );
|
|
#endif
|
|
|
|
}
|
|
|
|
int highPrecSleep( double timeSeconds )
|
|
{
|
|
int ret = 0;
|
|
#if defined(__linux__) || defined(__APPLE__) || defined(__unix__)
|
|
struct timespec req, rem;
|
|
|
|
req.tv_sec = (long)timeSeconds;
|
|
req.tv_nsec = (long)((timeSeconds - (double)req.tv_sec) * 1e9);
|
|
|
|
ret = nanosleep( &req, &rem );
|
|
#else
|
|
SDL_Delay( (long)(timeSeconds * 1e3) );
|
|
#endif
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* Perform FPS speed throttling by delaying until the next time slot.
|
|
*/
|
|
int
|
|
SpeedThrottle(void)
|
|
{
|
|
if (g_fpsScale >= 32)
|
|
{
|
|
return 0; /* Done waiting */
|
|
}
|
|
double time_left;
|
|
double cur_time, idleStart;
|
|
double frame_time = desired_frametime;
|
|
double halfFrame = 0.500 * frame_time;
|
|
double quarterFrame = 0.250 * frame_time;
|
|
|
|
idleStart = cur_time = getHighPrecTimeStamp();
|
|
|
|
if (Lasttime < 1.0)
|
|
{
|
|
Lasttime = cur_time;
|
|
Latetime = Lasttime + 2.0*frame_time;
|
|
}
|
|
|
|
if (!InFrame)
|
|
{
|
|
InFrame = 1;
|
|
Nexttime = Lasttime + frame_time;
|
|
Latetime = Nexttime + halfFrame;
|
|
}
|
|
|
|
if (cur_time >= Nexttime)
|
|
{
|
|
time_left = 0;
|
|
}
|
|
else
|
|
{
|
|
time_left = Nexttime - cur_time;
|
|
}
|
|
|
|
if (time_left > 50)
|
|
{
|
|
time_left = 50;
|
|
/* In order to keep input responsive, don't wait too long at once */
|
|
/* 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");
|
|
|
|
#ifdef __linux__
|
|
if ( timerfd != -1 )
|
|
{
|
|
uint64_t val;
|
|
|
|
if ( read( timerfd, &val, sizeof(val) ) > 0 )
|
|
{
|
|
if ( val > 1 )
|
|
{
|
|
frameLateCounter += (val - 1);
|
|
//printf("Late Frame: %u \n", frameLateCounter);
|
|
}
|
|
}
|
|
}
|
|
else if ( time_left > 0 )
|
|
{
|
|
highPrecSleep( time_left );
|
|
}
|
|
else
|
|
{
|
|
if ( cur_time >= Latetime )
|
|
{
|
|
frameLateCounter++;
|
|
//printf("Late Frame: %u - %llu ms\n", frameLateCounter, cur_time - Latetime);
|
|
}
|
|
}
|
|
#else
|
|
if ( time_left > 0 )
|
|
{
|
|
highPrecSleep( time_left );
|
|
}
|
|
else
|
|
{
|
|
if ( cur_time >= Latetime )
|
|
{
|
|
frameLateCounter++;
|
|
//printf("Late Frame: %u - %llu ms\n", frameLateCounter, cur_time - Latetime);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
cur_time = getHighPrecTimeStamp();
|
|
|
|
if ( cur_time >= (Nexttime - quarterFrame) )
|
|
{
|
|
if ( keepFrameTimeStats )
|
|
{
|
|
|
|
frameDeltaCur = (cur_time - Lasttime);
|
|
|
|
if ( frameDeltaCur < frameDeltaMin )
|
|
{
|
|
frameDeltaMin = frameDeltaCur;
|
|
}
|
|
if ( frameDeltaCur > frameDeltaMax )
|
|
{
|
|
frameDeltaMax = frameDeltaCur;
|
|
}
|
|
|
|
frameIdleCur = (cur_time - idleStart);
|
|
|
|
if ( frameIdleCur < frameIdleMin )
|
|
{
|
|
frameIdleMin = frameIdleCur;
|
|
}
|
|
if ( frameIdleCur > frameIdleMax )
|
|
{
|
|
frameIdleMax = frameIdleCur;
|
|
}
|
|
//printf("Frame Delta: %f us min:%f max:%f \n", frameDelta * 1e6, frameDeltaMin * 1e6, frameDeltaMax * 1e6 );
|
|
//printf("Frame Sleep Time: %f Target Error: %f us\n", time_left * 1e6, (cur_time - Nexttime) * 1e6 );
|
|
}
|
|
Lasttime = Nexttime;
|
|
Nexttime = Lasttime + frame_time;
|
|
Latetime = Nexttime + halfFrame;
|
|
|
|
if ( cur_time >= Nexttime )
|
|
{
|
|
Lasttime = cur_time;
|
|
Nexttime = Lasttime + frame_time;
|
|
Latetime = Nexttime + halfFrame;
|
|
}
|
|
return 0; /* Done waiting */
|
|
}
|
|
|
|
return 1; /* Must still wait some more */
|
|
}
|
|
|
|
/**
|
|
* Set the emulation speed throttling to the next entry in the speed table.
|
|
*/
|
|
void IncreaseEmulationSpeed(void)
|
|
{
|
|
g_fpsScale *= LOGMUL;
|
|
|
|
if (g_fpsScale > Fastest) g_fpsScale = Fastest;
|
|
|
|
RefreshThrottleFPS();
|
|
|
|
FCEU_DispMessage("Emulation speed %.1f%%",0, g_fpsScale*100.0);
|
|
}
|
|
|
|
/**
|
|
* Set the emulation speed throttling to the previous entry in the speed table.
|
|
*/
|
|
void DecreaseEmulationSpeed(void)
|
|
{
|
|
g_fpsScale /= LOGMUL;
|
|
if(g_fpsScale < Slowest)
|
|
g_fpsScale = Slowest;
|
|
|
|
RefreshThrottleFPS();
|
|
|
|
FCEU_DispMessage("Emulation speed %.1f%%",0, g_fpsScale*100.0);
|
|
}
|
|
|
|
int CustomEmulationSpeed(int spdPercent)
|
|
{
|
|
if ( spdPercent < 1 )
|
|
{
|
|
return -1;
|
|
}
|
|
g_fpsScale = ((double)spdPercent) / 100.0f;
|
|
|
|
if (g_fpsScale < Slowest)
|
|
{
|
|
g_fpsScale = Slowest;
|
|
}
|
|
else if (g_fpsScale > Fastest)
|
|
{
|
|
g_fpsScale = Fastest;
|
|
}
|
|
|
|
RefreshThrottleFPS();
|
|
|
|
FCEU_DispMessage("Emulation speed %.1f%%",0, g_fpsScale*100.0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* Set the emulation speed throttling to a specific value.
|
|
*/
|
|
void
|
|
FCEUD_SetEmulationSpeed(int cmd)
|
|
{
|
|
MaxSpeed = false;
|
|
|
|
switch(cmd) {
|
|
case EMUSPEED_SLOWEST:
|
|
g_fpsScale = Slowest;
|
|
break;
|
|
case EMUSPEED_SLOWER:
|
|
DecreaseEmulationSpeed();
|
|
break;
|
|
case EMUSPEED_NORMAL:
|
|
g_fpsScale = Normal;
|
|
break;
|
|
case EMUSPEED_FASTER:
|
|
IncreaseEmulationSpeed();
|
|
break;
|
|
case EMUSPEED_FASTEST:
|
|
g_fpsScale = Fastest;
|
|
MaxSpeed = true;
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
|
|
RefreshThrottleFPS();
|
|
|
|
FCEU_DispMessage("Emulation speed %.1f%%",0, g_fpsScale*100.0);
|
|
}
|