Merge pull request #247 from mjbudd77/master

Frame Timing and Thread Scheduling Enhancements for Qt GUI
This commit is contained in:
mjbudd77 2020-11-28 22:40:37 -05:00 committed by GitHub
commit 3ccbc61aa1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 1256 additions and 28 deletions

View File

@ -430,6 +430,7 @@ set(SRC_DRIVERS_SDL
${CMAKE_CURRENT_SOURCE_DIR}/drivers/Qt/InputConf.cpp
${CMAKE_CURRENT_SOURCE_DIR}/drivers/Qt/GamePadConf.cpp
${CMAKE_CURRENT_SOURCE_DIR}/drivers/Qt/HotKeyConf.cpp
${CMAKE_CURRENT_SOURCE_DIR}/drivers/Qt/TimingConf.cpp
${CMAKE_CURRENT_SOURCE_DIR}/drivers/Qt/PaletteConf.cpp
${CMAKE_CURRENT_SOURCE_DIR}/drivers/Qt/GuiConf.cpp
${CMAKE_CURRENT_SOURCE_DIR}/drivers/Qt/MoviePlay.cpp

View File

@ -1,5 +1,14 @@
// GameApp.cpp
//
#ifdef __linux__
#include <unistd.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/types.h>
#include <sys/syscall.h>
#include <pthread.h>
#endif
#include <fstream>
#include <iostream>
#include <cstdlib>
@ -27,6 +36,7 @@
#include "Qt/GuiConf.h"
#include "Qt/MoviePlay.h"
#include "Qt/MovieOptions.h"
#include "Qt/TimingConf.h"
#include "Qt/LuaControl.h"
#include "Qt/CheatsConf.h"
#include "Qt/GameGenie.h"
@ -50,6 +60,7 @@
consoleWin_t::consoleWin_t(QWidget *parent)
: QMainWindow( parent )
{
int opt;
int use_SDL_video = false;
int setFullScreen = false;
@ -95,11 +106,24 @@ consoleWin_t::consoleWin_t(QWidget *parent)
connect( gameTimer, &QTimer::timeout, this, &consoleWin_t::updatePeriodic );
gameTimer->setTimerType( Qt::PreciseTimer );
//gameTimer->start( 16 ); // 60hz
gameTimer->start( 8 ); // 120hz
emulatorThread->start();
g_config->getOption( "SDL.SetSchedParam", &opt );
if ( opt )
{
int policy, prio, nice;
g_config->getOption( "SDL.GuiSchedPolicy", &policy );
g_config->getOption( "SDL.GuiSchedPrioRt", &prio );
g_config->getOption( "SDL.GuiSchedNice" , &nice );
setNicePriority( nice );
setSchedParam( policy, prio );
}
}
consoleWin_t::~consoleWin_t(void)
@ -404,6 +428,14 @@ void consoleWin_t::createMainMenu(void)
optMenu->addAction(guiConfig);
// Options -> Timing Config
timingConfig = new QAction(tr("Timing Config"), this);
//timingConfig->setShortcut( QKeySequence(tr("Ctrl+C")));
timingConfig->setStatusTip(tr("Timing Configure"));
connect(timingConfig, SIGNAL(triggered()), this, SLOT(openTimingConfWin(void)) );
optMenu->addAction(timingConfig);
// Options -> Movie Options
movieConfig = new QAction(tr("Movie Options"), this);
//movieConfig->setShortcut( QKeySequence(tr("Ctrl+C")));
@ -1252,6 +1284,17 @@ void consoleWin_t::openGuiConfWin(void)
guiConfWin->show();
}
void consoleWin_t::openTimingConfWin(void)
{
TimingConfDialog_t *tmConfWin;
//printf("Open Timing Config Window\n");
tmConfWin = new TimingConfDialog_t(this);
tmConfWin->show();
}
void consoleWin_t::openMovieOptWin(void)
{
MovieOptionsDialog_t *win;
@ -1770,6 +1813,159 @@ void consoleWin_t::aboutQt(void)
return;
}
int consoleWin_t::setNicePriority( int value )
{
int ret = 0;
#if defined(__linux__)
if ( value < -20 )
{
value = -20;
}
else if ( value > 19 )
{
value = 19;
}
if ( ::setpriority( PRIO_PROCESS, getpid(), value ) )
{
perror("Emulator thread setpriority error: ");
ret = -1;
}
#elif defined(__APPLE__)
if ( value < -20 )
{
value = -20;
}
else if ( value > 20 )
{
value = 20;
}
if ( ::setpriority( PRIO_PROCESS, getpid(), value ) )
{
perror("Emulator thread setpriority error: ");
ret = -1;
}
#endif
return ret;
}
int consoleWin_t::getNicePriority(void)
{
return ::getpriority( PRIO_PROCESS, getpid() );
}
int consoleWin_t::getMinSchedPriority(void)
{
int policy, prio;
if ( getSchedParam( policy, prio ) )
{
return 0;
}
return sched_get_priority_min( policy );
}
int consoleWin_t::getMaxSchedPriority(void)
{
int policy, prio;
if ( getSchedParam( policy, prio ) )
{
return 0;
}
return sched_get_priority_max( policy );
}
int consoleWin_t::getSchedParam( int &policy, int &priority )
{
int ret = 0;
#if defined(__linux__)
struct sched_param p;
policy = sched_getscheduler( getpid() );
if ( sched_getparam( getpid(), &p ) )
{
perror("GUI thread sched_getparam error: ");
ret = -1;
priority = 0;
}
else
{
priority = p.sched_priority;
}
#elif defined(__APPLE__)
struct sched_param p;
if ( pthread_getschedparam( pthread_self(), &policy, &p ) )
{
perror("GUI thread pthread_getschedparam error: ");
ret = -1;
priority = 0;
}
else
{
priority = p.sched_priority;
}
#endif
return ret;
}
int consoleWin_t::setSchedParam( int policy, int priority )
{
int ret = 0;
#if defined(__linux__)
struct sched_param p;
int minPrio, maxPrio;
minPrio = sched_get_priority_min( policy );
maxPrio = sched_get_priority_max( policy );
if ( priority < minPrio )
{
priority = minPrio;
}
else if ( priority > maxPrio )
{
priority = maxPrio;
}
p.sched_priority = priority;
if ( sched_setscheduler( getpid(), policy, &p ) )
{
perror("GUI thread sched_setscheduler error");
ret = -1;
}
#elif defined(__APPLE__)
struct sched_param p;
int minPrio, maxPrio;
minPrio = sched_get_priority_min( policy );
maxPrio = sched_get_priority_max( policy );
if ( priority < minPrio )
{
priority = minPrio;
}
else if ( priority > maxPrio )
{
priority = maxPrio;
}
p.sched_priority = priority;
if ( ::pthread_setschedparam( pthread_self(), policy, &p ) != 0 )
{
perror("GUI thread pthread_setschedparam error: ");
}
#endif
return ret;
}
void consoleWin_t::syncActionConfig( QAction *act, const char *property )
{
if ( act->isCheckable() )
@ -1821,11 +2017,205 @@ void consoleWin_t::updatePeriodic(void)
return;
}
emulatorThread_t::emulatorThread_t(void)
{
#if defined(__linux__) || defined(__APPLE__)
pself = 0;
#endif
}
#ifdef __linux__
#ifndef SYS_gettid
#error "SYS_gettid unavailable on this system"
#endif
#define gettid() ((pid_t)syscall(SYS_gettid))
#endif
void emulatorThread_t::init(void)
{
int opt;
#if defined(__linux__) || defined(__APPLE__)
if ( pthread_self() == (pthread_t)QThread::currentThreadId() )
{
pself = pthread_self();
//printf("EMU is using PThread: %p\n", (void*)pself);
}
#endif
#if defined(__linux__)
pid = gettid();
#elif defined(__APPLE__)
pid = getpid();
#endif
g_config->getOption( "SDL.SetSchedParam", &opt );
if ( opt )
{
int policy, prio, nice;
g_config->getOption( "SDL.EmuSchedPolicy", &policy );
g_config->getOption( "SDL.EmuSchedPrioRt", &prio );
g_config->getOption( "SDL.EmuSchedNice" , &nice );
setNicePriority( nice );
setSchedParam( policy, prio );
}
}
void emulatorThread_t::setPriority( QThread::Priority priority_req )
{
//printf("New Priority: %i \n", priority_req );
//printf("Old Priority: %i \n", priority() );
QThread::setPriority( priority_req );
//printf("Set Priority: %i \n", priority() );
}
int emulatorThread_t::setNicePriority( int value )
{
int ret = 0;
#if defined(__linux__)
if ( value < -20 )
{
value = -20;
}
else if ( value > 19 )
{
value = 19;
}
if ( ::setpriority( PRIO_PROCESS, pid, value ) )
{
perror("Emulator thread setpriority error: ");
ret = -1;
}
#elif defined(__APPLE__)
if ( value < -20 )
{
value = -20;
}
else if ( value > 20 )
{
value = 20;
}
if ( ::setpriority( PRIO_PROCESS, pid, value ) )
{
perror("Emulator thread setpriority error: ");
ret = -1;
}
#endif
return ret;
}
int emulatorThread_t::getNicePriority(void)
{
return ::getpriority( PRIO_PROCESS, pid );
}
int emulatorThread_t::getMinSchedPriority(void)
{
int policy, prio;
if ( getSchedParam( policy, prio ) )
{
return 0;
}
return sched_get_priority_min( policy );
}
int emulatorThread_t::getMaxSchedPriority(void)
{
int policy, prio;
if ( getSchedParam( policy, prio ) )
{
return 0;
}
return sched_get_priority_max( policy );
}
int emulatorThread_t::getSchedParam( int &policy, int &priority )
{
struct sched_param p;
if ( pthread_getschedparam( pself, &policy, &p ) )
{
perror("Emulator thread pthread_getschedparam error: ");
return -1;
}
priority = p.sched_priority;
return 0;
}
int emulatorThread_t::setSchedParam( int policy, int priority )
{
int ret = 0;
#if defined(__linux__)
struct sched_param p;
int minPrio, maxPrio;
minPrio = sched_get_priority_min( policy );
maxPrio = sched_get_priority_max( policy );
if ( priority < minPrio )
{
priority = minPrio;
}
else if ( priority > maxPrio )
{
priority = maxPrio;
}
p.sched_priority = priority;
if ( ::pthread_setschedparam( pself, policy, &p ) != 0 )
{
perror("Emulator thread pthread_setschedparam error: ");
ret = -1;
}
#elif defined(__APPLE__)
struct sched_param p;
int minPrio, maxPrio;
minPrio = sched_get_priority_min( policy );
maxPrio = sched_get_priority_max( policy );
if ( priority < minPrio )
{
priority = minPrio;
}
else if ( priority > maxPrio )
{
priority = maxPrio;
}
p.sched_priority = priority;
if ( ::pthread_setschedparam( pself, policy, &p ) != 0 )
{
perror("Emulator thread pthread_setschedparam error: ");
}
#endif
return ret;
}
void emulatorThread_t::run(void)
{
printf("Emulator Start\n");
nes_shm->runEmulator = 1;
init();
while ( nes_shm->runEmulator )
{
fceuWrapperUpdate();

View File

@ -28,10 +28,32 @@ class emulatorThread_t : public QThread
{
Q_OBJECT
//public slots:
protected:
void run( void ) override;
public:
emulatorThread_t(void);
void setPriority( QThread::Priority priority );
#if defined(__linux__) || defined(__APPLE__)
int setSchedParam( int policy, int priority );
int getSchedParam( int &policy, int &priority );
int setNicePriority( int value );
int getNicePriority( void );
int getMinSchedPriority(void);
int getMaxSchedPriority(void);
#endif
private:
void init(void);
#if defined(__linux__) || defined(__APPLE__)
pthread_t pself;
int pid;
#endif
signals:
void finished();
void finished();
};
class consoleWin_t : public QMainWindow
@ -53,6 +75,17 @@ class consoleWin_t : public QMainWindow
int showListSelectDialog( const char *title, std::vector <std::string> &l );
#if defined(__linux__) || defined(__APPLE__)
int setSchedParam( int policy, int priority );
int getSchedParam( int &policy, int &priority );
int setNicePriority( int value );
int getNicePriority( void );
int getMinSchedPriority(void);
int getMaxSchedPriority(void);
#endif
emulatorThread_t *emulatorThread;
protected:
QMenu *fileMenu;
QMenu *optMenu;
@ -78,6 +111,7 @@ class consoleWin_t : public QMainWindow
QAction *hotkeyConfig;
QAction *paletteConfig;
QAction *guiConfig;
QAction *timingConfig;
QAction *movieConfig;
QAction *autoResume;
QAction *fullscreen;
@ -112,8 +146,6 @@ class consoleWin_t : public QMainWindow
QTimer *gameTimer;
emulatorThread_t *emulatorThread;
std::string errorMsg;
bool errorMsgValid;
@ -148,6 +180,7 @@ class consoleWin_t : public QMainWindow
void openHotkeyConfWin(void);
void openPaletteConfWin(void);
void openGuiConfWin(void);
void openTimingConfWin(void);
void openMovieOptWin(void);
void openCodeDataLogger(void);
void openTraceLogger(void);

View File

@ -0,0 +1,534 @@
// TimingConf.cpp
//
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <string>
#include <unistd.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <SDL.h>
#include <QHeaderView>
#include <QCloseEvent>
#include "Qt/main.h"
#include "Qt/dface.h"
#include "Qt/input.h"
#include "Qt/config.h"
#include "Qt/keyscan.h"
#include "Qt/throttle.h"
#include "Qt/fceuWrapper.h"
#include "Qt/ConsoleWindow.h"
#include "Qt/TimingConf.h"
//----------------------------------------------------------------------------
static bool hasNicePermissions( int val )
{
int usrID;
bool usrRoot;
usrID = geteuid();
usrRoot = (usrID == 0);
if ( usrRoot )
{
return true;
}
#ifdef __linux__
struct rlimit r;
if ( getrlimit( RLIMIT_NICE, &r ) == 0 )
{
int ncur = 20 - r.rlim_cur;
if ( val >= ncur )
{
return true;
}
//printf("RLim Cur: %lu \n", r.rlim_cur );
//printf("RLim Max: %lu \n", r.rlim_max );
}
#endif
return false;
}
//----------------------------------------------------------------------------
TimingConfDialog_t::TimingConfDialog_t(QWidget *parent)
: QDialog( parent )
{
int opt;
QVBoxLayout *mainLayout;
QHBoxLayout *hbox;
QGridLayout *grid;
QGroupBox *emuPrioBox, *guiPrioBox;
setWindowTitle("Timing Configuration");
mainLayout = new QVBoxLayout();
emuPrioCtlEna = new QCheckBox( tr("Set Scheduling Parameters at Startup") );
emuPrioBox = new QGroupBox( tr("EMU Thread Scheduling Parameters") );
guiPrioBox = new QGroupBox( tr("GUI Thread Scheduling Parameters") );
grid = new QGridLayout();
emuPrioBox->setLayout( grid );
emuSchedPolicyBox = new QComboBox();
emuSchedPrioSlider = new QSlider( Qt::Horizontal );
emuSchedNiceSlider = new QSlider( Qt::Horizontal );
emuSchedPrioLabel = new QLabel( tr("Priority (RT)") );
emuSchedNiceLabel = new QLabel( tr("Priority (Nice)") );
emuSchedPolicyBox->addItem( tr("SCHED_OTHER") , SCHED_OTHER );
emuSchedPolicyBox->addItem( tr("SCHED_FIFO") , SCHED_FIFO );
emuSchedPolicyBox->addItem( tr("SCHED_RR") , SCHED_RR );
grid->addWidget( new QLabel( tr("Policy") ), 0, 0 );
grid->addWidget( emuSchedPolicyBox, 0, 1 );
grid->addWidget( emuSchedPrioLabel, 1, 0 );
grid->addWidget( emuSchedPrioSlider, 1, 1 );
grid->addWidget( emuSchedNiceLabel, 2, 0 );
grid->addWidget( emuSchedNiceSlider, 2, 1 );
mainLayout->addWidget( emuPrioCtlEna );
mainLayout->addWidget( emuPrioBox );
grid = new QGridLayout();
guiPrioBox->setLayout( grid );
guiSchedPolicyBox = new QComboBox();
guiSchedPrioSlider = new QSlider( Qt::Horizontal );
guiSchedNiceSlider = new QSlider( Qt::Horizontal );
guiSchedPrioLabel = new QLabel( tr("Priority (RT)") );
guiSchedNiceLabel = new QLabel( tr("Priority (Nice)") );
guiSchedPolicyBox->addItem( tr("SCHED_OTHER") , SCHED_OTHER );
guiSchedPolicyBox->addItem( tr("SCHED_FIFO") , SCHED_FIFO );
guiSchedPolicyBox->addItem( tr("SCHED_RR") , SCHED_RR );
grid->addWidget( new QLabel( tr("Policy") ), 0, 0 );
grid->addWidget( guiSchedPolicyBox, 0, 1 );
grid->addWidget( guiSchedPrioLabel, 1, 0 );
grid->addWidget( guiSchedPrioSlider, 1, 1 );
grid->addWidget( guiSchedNiceLabel, 2, 0 );
grid->addWidget( guiSchedNiceSlider, 2, 1 );
mainLayout->addWidget( guiPrioBox );
hbox = new QHBoxLayout();
timingDevSelBox = new QComboBox();
timingDevSelBox->addItem( tr("NanoSleep") , 0 );
#ifdef __linux__
timingDevSelBox->addItem( tr("Timer FD") , 1 );
#endif
hbox->addWidget( new QLabel( tr("Timing Mechanism:") ) );
hbox->addWidget( timingDevSelBox );
mainLayout->addLayout( hbox );
setLayout( mainLayout );
g_config->getOption( "SDL.SetSchedParam", &opt );
emuPrioCtlEna->setChecked( opt );
updatePolicyBox();
updateSliderLimits();
updateSliderValues();
updateTimingMech();
connect( emuSchedPolicyBox , SIGNAL(activated(int)) , this, SLOT(emuSchedPolicyChange(int)) );
connect( emuSchedNiceSlider , SIGNAL(valueChanged(int)), this, SLOT(emuSchedNiceChange(int)) );
connect( emuSchedPrioSlider , SIGNAL(valueChanged(int)), this, SLOT(emuSchedPrioChange(int)) );
connect( guiSchedPolicyBox , SIGNAL(activated(int)) , this, SLOT(guiSchedPolicyChange(int)) );
connect( guiSchedNiceSlider , SIGNAL(valueChanged(int)), this, SLOT(guiSchedNiceChange(int)) );
connect( guiSchedPrioSlider , SIGNAL(valueChanged(int)), this, SLOT(guiSchedPrioChange(int)) );
connect( emuPrioCtlEna , SIGNAL(stateChanged(int)), this, SLOT(emuSchedCtlChange(int)) );
connect( timingDevSelBox , SIGNAL(activated(int)) , this, SLOT(emuTimingMechChange(int)) );
}
//----------------------------------------------------------------------------
TimingConfDialog_t::~TimingConfDialog_t(void)
{
printf("Destroy Timing Config Window\n");
saveValues();
}
//----------------------------------------------------------------------------
void TimingConfDialog_t::closeEvent(QCloseEvent *event)
{
printf("Timing Close Window Event\n");
done(0);
deleteLater();
event->accept();
}
//----------------------------------------------------------------------------
void TimingConfDialog_t::closeWindow(void)
{
//printf("Close Window\n");
done(0);
deleteLater();
}
//----------------------------------------------------------------------------
void TimingConfDialog_t::emuSchedCtlChange( int state )
{
g_config->setOption( "SDL.SetSchedParam", (state != Qt::Unchecked) );
}
//----------------------------------------------------------------------------
void TimingConfDialog_t::saveValues(void)
{
int policy, prio, nice;
if ( consoleWindow == NULL )
{
return;
}
nice = consoleWindow->emulatorThread->getNicePriority();
consoleWindow->emulatorThread->getSchedParam( policy, prio );
g_config->setOption( "SDL.EmuSchedPolicy", policy );
g_config->setOption( "SDL.EmuSchedPrioRt", prio );
g_config->setOption( "SDL.EmuSchedNice" , nice );
//printf("EMU Sched: %i %i %i\n", policy, prio, nice );
nice = consoleWindow->getNicePriority();
consoleWindow->getSchedParam( policy, prio );
g_config->setOption( "SDL.GuiSchedPolicy", policy );
g_config->setOption( "SDL.GuiSchedPrioRt", prio );
g_config->setOption( "SDL.GuiSchedNice" , nice );
//printf("GUI Sched: %i %i %i\n", policy, prio, nice );
g_config->save();
}
//----------------------------------------------------------------------------
void TimingConfDialog_t::emuSchedNiceChange(int val)
{
if ( consoleWindow == NULL )
{
return;
}
fceuWrapperLock();
if ( consoleWindow->emulatorThread->setNicePriority( -val ) )
{
char msg[1024];
sprintf( msg, "Error: system call setPriority Failed\nReason: %s\n", strerror(errno) );
#ifdef __linux__
strcat( msg, "Ensure that your system has the proper resource permissions set in the file:\n\n");
strcat( msg, " /etc/security/limits.conf \n\n");
strcat( msg, "Adding the following lines to that file and rebooting will usually fix the issue:\n\n");
strcat( msg, "* - priority 99 \n");
strcat( msg, "* - rtprio 99 \n");
strcat( msg, "* - nice -20 \n");
#endif
printf("%s\n", msg );
consoleWindow->QueueErrorMsgWindow( msg );
updateSliderValues();
}
fceuWrapperUnLock();
}
//----------------------------------------------------------------------------
void TimingConfDialog_t::emuSchedPrioChange(int val)
{
int policy, prio;
if ( consoleWindow == NULL )
{
return;
}
fceuWrapperLock();
consoleWindow->emulatorThread->getSchedParam( policy, prio );
if ( consoleWindow->emulatorThread->setSchedParam( policy, val ) )
{
char msg[1024];
sprintf( msg, "Error: system call pthread_setschedparam Failed\nReason: %s\n", strerror(errno) );
#ifdef __linux__
strcat( msg, "Ensure that your system has the proper resource permissions set in the file:\n\n");
strcat( msg, " /etc/security/limits.conf \n\n");
strcat( msg, "Adding the following lines to that file and rebooting will usually fix the issue:\n\n");
strcat( msg, "* - priority 99 \n");
strcat( msg, "* - rtprio 99 \n");
strcat( msg, "* - nice -20 \n");
#endif
printf("%s\n", msg );
consoleWindow->QueueErrorMsgWindow( msg );
updateSliderValues();
}
fceuWrapperUnLock();
}
//----------------------------------------------------------------------------
void TimingConfDialog_t::emuSchedPolicyChange( int index )
{
int policy, prio;
if ( consoleWindow == NULL )
{
return;
}
fceuWrapperLock();
consoleWindow->emulatorThread->getSchedParam( policy, prio );
policy = emuSchedPolicyBox->itemData( index ).toInt();
if ( consoleWindow->emulatorThread->setSchedParam( policy, prio ) )
{
char msg[1024];
sprintf( msg, "Error: system call pthread_setschedparam Failed\nReason: %s\n", strerror(errno) );
#ifdef __linux__
strcat( msg, "Ensure that your system has the proper resource permissions set in the file:\n\n");
strcat( msg, " /etc/security/limits.conf \n\n");
strcat( msg, "Adding the following lines to that file and rebooting will usually fix the issue:\n\n");
strcat( msg, "* - priority 99 \n");
strcat( msg, "* - rtprio 99 \n");
strcat( msg, "* - nice -20 \n");
#endif
printf("%s\n", msg );
consoleWindow->QueueErrorMsgWindow( msg );
}
updatePolicyBox();
updateSliderLimits();
updateSliderValues();
fceuWrapperUnLock();
}
//----------------------------------------------------------------------------
void TimingConfDialog_t::guiSchedNiceChange(int val)
{
if ( consoleWindow == NULL )
{
return;
}
fceuWrapperLock();
if ( consoleWindow->setNicePriority( -val ) )
{
char msg[1024];
sprintf( msg, "Error: system call setPriority Failed\nReason: %s\n", strerror(errno) );
#ifdef __linux__
strcat( msg, "Ensure that your system has the proper resource permissions set in the file:\n\n");
strcat( msg, " /etc/security/limits.conf \n\n");
strcat( msg, "Adding the following lines to that file and rebooting will usually fix the issue:\n\n");
strcat( msg, "* - priority 99 \n");
strcat( msg, "* - rtprio 99 \n");
strcat( msg, "* - nice -20 \n");
#endif
printf("%s\n", msg );
consoleWindow->QueueErrorMsgWindow( msg );
updateSliderValues();
}
fceuWrapperUnLock();
}
//----------------------------------------------------------------------------
void TimingConfDialog_t::guiSchedPrioChange(int val)
{
int policy, prio;
if ( consoleWindow == NULL )
{
return;
}
fceuWrapperLock();
consoleWindow->getSchedParam( policy, prio );
if ( consoleWindow->setSchedParam( policy, val ) )
{
char msg[1024];
sprintf( msg, "Error: system call pthread_setschedparam Failed\nReason: %s\n", strerror(errno) );
#ifdef __linux__
strcat( msg, "Ensure that your system has the proper resource permissions set in the file:\n\n");
strcat( msg, " /etc/security/limits.conf \n\n");
strcat( msg, "Adding the following lines to that file and rebooting will usually fix the issue:\n\n");
strcat( msg, "* - priority 99 \n");
strcat( msg, "* - rtprio 99 \n");
strcat( msg, "* - nice -20 \n");
#endif
printf("%s\n", msg );
consoleWindow->QueueErrorMsgWindow( msg );
updateSliderValues();
}
fceuWrapperUnLock();
}
//----------------------------------------------------------------------------
void TimingConfDialog_t::guiSchedPolicyChange( int index )
{
int policy, prio;
if ( consoleWindow == NULL )
{
return;
}
fceuWrapperLock();
consoleWindow->getSchedParam( policy, prio );
policy = guiSchedPolicyBox->itemData( index ).toInt();
if ( consoleWindow->setSchedParam( policy, prio ) )
{
char msg[1024];
sprintf( msg, "Error: system call pthread_setschedparam Failed\nReason: %s\n", strerror(errno) );
#ifdef __linux__
strcat( msg, "Ensure that your system has the proper resource permissions set in the file:\n\n");
strcat( msg, " /etc/security/limits.conf \n\n");
strcat( msg, "Adding the following lines to that file and rebooting will usually fix the issue:\n\n");
strcat( msg, "* - priority 99 \n");
strcat( msg, "* - rtprio 99 \n");
strcat( msg, "* - nice -20 \n");
#endif
printf("%s\n", msg );
consoleWindow->QueueErrorMsgWindow( msg );
}
updatePolicyBox();
updateSliderLimits();
updateSliderValues();
fceuWrapperUnLock();
}
//----------------------------------------------------------------------------
void TimingConfDialog_t::updatePolicyBox(void)
{
int policy, prio;
if ( consoleWindow == NULL )
{
return;
}
consoleWindow->emulatorThread->getSchedParam( policy, prio );
for (int j=0; j<emuSchedPolicyBox->count(); j++)
{
if ( emuSchedPolicyBox->itemData(j).toInt() == policy )
{
//printf("Found Policy %i %i\n", j , policy );
emuSchedPolicyBox->setCurrentIndex( j );
}
}
consoleWindow->getSchedParam( policy, prio );
for (int j=0; j<guiSchedPolicyBox->count(); j++)
{
if ( guiSchedPolicyBox->itemData(j).toInt() == policy )
{
//printf("Found Policy %i %i\n", j , policy );
guiSchedPolicyBox->setCurrentIndex( j );
}
}
}
//----------------------------------------------------------------------------
void TimingConfDialog_t::updateSliderValues(void)
{
int policy, prio;
bool hasNicePerms;
if ( consoleWindow == NULL )
{
return;
}
consoleWindow->emulatorThread->getSchedParam( policy, prio );
emuSchedNiceSlider->setValue( -consoleWindow->emulatorThread->getNicePriority() );
emuSchedPrioSlider->setValue( prio );
if ( (policy == SCHED_RR) || (policy == SCHED_FIFO) )
{
emuSchedPrioLabel->setEnabled(true);
emuSchedPrioSlider->setEnabled(true);
}
else
{
emuSchedPrioLabel->setEnabled(false);
emuSchedPrioSlider->setEnabled(false);
}
hasNicePerms = hasNicePermissions( consoleWindow->emulatorThread->getNicePriority() );
emuSchedNiceLabel->setEnabled( hasNicePerms );
emuSchedNiceSlider->setEnabled( hasNicePerms );
consoleWindow->getSchedParam( policy, prio );
guiSchedNiceSlider->setValue( -consoleWindow->getNicePriority() );
guiSchedPrioSlider->setValue( prio );
if ( (policy == SCHED_RR) || (policy == SCHED_FIFO) )
{
guiSchedPrioLabel->setEnabled(true);
guiSchedPrioSlider->setEnabled(true);
}
else
{
guiSchedPrioLabel->setEnabled(false);
guiSchedPrioSlider->setEnabled(false);
}
hasNicePerms = hasNicePermissions( consoleWindow->getNicePriority() );
guiSchedNiceLabel->setEnabled( hasNicePerms );
guiSchedNiceSlider->setEnabled( hasNicePerms );
}
//----------------------------------------------------------------------------
void TimingConfDialog_t::updateSliderLimits(void)
{
if ( consoleWindow == NULL )
{
return;
}
emuSchedNiceSlider->setMinimum( -20 );
emuSchedNiceSlider->setMaximum( 20 );
emuSchedPrioSlider->setMinimum( consoleWindow->emulatorThread->getMinSchedPriority() );
emuSchedPrioSlider->setMaximum( consoleWindow->emulatorThread->getMaxSchedPriority() );
guiSchedNiceSlider->setMinimum( -20 );
guiSchedNiceSlider->setMaximum( 20 );
guiSchedPrioSlider->setMinimum( consoleWindow->getMinSchedPriority() );
guiSchedPrioSlider->setMaximum( consoleWindow->getMaxSchedPriority() );
}
//----------------------------------------------------------------------------
void TimingConfDialog_t::emuTimingMechChange( int index )
{
int mode;
if ( consoleWindow == NULL )
{
return;
}
fceuWrapperLock();
mode = timingDevSelBox->itemData( index ).toInt();
setTimingMode( mode );
RefreshThrottleFPS();
g_config->setOption("SDL.EmuTimingMech", mode);
fceuWrapperUnLock();
}
//----------------------------------------------------------------------------
void TimingConfDialog_t::updateTimingMech(void)
{
int mode = getTimingMode();
for (int j=0; j<timingDevSelBox->count(); j++)
{
if ( timingDevSelBox->itemData(j).toInt() == mode )
{
timingDevSelBox->setCurrentIndex( j );
}
}
}
//----------------------------------------------------------------------------

View File

@ -0,0 +1,65 @@
// TimingConf.h
//
#pragma once
#include <QWidget>
#include <QDialog>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QComboBox>
#include <QCheckBox>
#include <QPushButton>
#include <QLabel>
#include <QSlider>
#include <QFrame>
#include <QGroupBox>
#include <QTreeView>
#include <QTreeWidget>
#include "Qt/main.h"
class TimingConfDialog_t : public QDialog
{
Q_OBJECT
public:
TimingConfDialog_t(QWidget *parent = 0);
~TimingConfDialog_t(void);
protected:
void closeEvent(QCloseEvent *event);
QCheckBox *emuPrioCtlEna;
QComboBox *emuSchedPolicyBox;
QSlider *emuSchedPrioSlider;
QSlider *emuSchedNiceSlider;
QLabel *emuSchedPrioLabel;
QLabel *emuSchedNiceLabel;
QComboBox *guiSchedPolicyBox;
QSlider *guiSchedPrioSlider;
QSlider *guiSchedNiceSlider;
QLabel *guiSchedPrioLabel;
QLabel *guiSchedNiceLabel;
QComboBox *timingDevSelBox;
private:
void updatePolicyBox(void);
void updateSliderLimits(void);
void updateSliderValues(void);
void updateTimingMech(void);
void saveValues(void);
public slots:
void closeWindow(void);
private slots:
void emuSchedCtlChange( int state );
void emuSchedNiceChange( int val );
void emuSchedPrioChange( int val );
void emuSchedPolicyChange( int index );
void guiSchedNiceChange( int val );
void guiSchedPrioChange( int val );
void guiSchedPolicyChange( int index );
void emuTimingMechChange( int index );
};

View File

@ -315,6 +315,15 @@ InitConfig()
config->addOption("_useNativeFileDialog", "SDL.UseNativeFileDialog", false);
config->addOption("_useNativeMenuBar" , "SDL.UseNativeMenuBar", false);
config->addOption("_setSchedParam" , "SDL.SetSchedParam" , 0);
config->addOption("_emuSchedPolicy" , "SDL.EmuSchedPolicy", 0);
config->addOption("_emuSchedNice" , "SDL.EmuSchedNice" , 0);
config->addOption("_emuSchedPrioRt" , "SDL.EmuSchedPrioRt", 40);
config->addOption("_guiSchedPolicy" , "SDL.GuiSchedPolicy", 0);
config->addOption("_guiSchedNice" , "SDL.GuiSchedNice" , 0);
config->addOption("_guiSchedPrioRt" , "SDL.GuiSchedPrioRt", 40);
config->addOption("_emuTimingMech" , "SDL.EmuTimingMech" , 0);
// fcm -> fm2 conversion
config->addOption("fcmconvert", "SDL.FCMConvert", "");

View File

@ -737,6 +737,15 @@ int fceuWrapperInit( int argc, char *argv[] )
g_config->getOption("SDL.SubtitleDisplay", &id);
movieSubtitles = id ? true : false;
}
// Emulation Timing Mechanism
{
int timingMode;
g_config->getOption("SDL.EmuTimingMech", &timingMode);
setTimingMode( timingMode );
}
// load the hotkeys from the config life
setHotKeys();
@ -958,13 +967,6 @@ FCEUD_Update(uint8 *XBuf,
}
else
{
//if (!NoWaiting && (!(eoptions&EO_NOTHROTTLE) || FCEUI_EmulationPaused()))
//{
// while (SpeedThrottle())
// {
// FCEUD_UpdateInput();
// }
//}
if (XBuf && (inited&4))
{
BlitScreen(XBuf); blitDone = 1;

View File

@ -4,16 +4,112 @@
#include "Qt/sdl.h"
#include "Qt/throttle.h"
#if defined(__linux) || defined(__APPLE__)
#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 uint64 Lasttime, Nexttime;
static uint32 frameLateCounter = 0;
static double Lasttime=0, Nexttime=0, Latetime=0;
static double desired_frametime = (1.0 / 60.099823);
static double frameDeltaMin = 99999.0;
static double frameDeltaMax = 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__)
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;
}
#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;
}
/* LOGMUL = exp(log(2) / 3)
*
* This gives us a value such that if we do x*=LOGMUL three times,
@ -28,7 +124,7 @@ bool MaxSpeed = false;
* Refreshes the FPS throttling variables.
*/
void
RefreshThrottleFPS()
RefreshThrottleFPS(void)
{
double hz;
int32_t fps = FCEUI_GetDesiredFPS(); // Do >> 24 to get in Hz
@ -47,37 +143,67 @@ RefreshThrottleFPS()
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__)
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)(time_left * 1e3) );
#endif
return ret;
}
/**
* Perform FPS speed throttling by delaying until the next time slot.
*/
int
SpeedThrottle()
SpeedThrottle(void)
{
if (g_fpsScale >= 32)
{
return 0; /* Done waiting */
}
uint64 time_left;
uint64 cur_time;
double time_left;
double cur_time;
double frame_time = desired_frametime;
double quarterFrame = 0.250 * frame_time;
if (!Lasttime)
cur_time = getHighPrecTimeStamp();
if (Lasttime < 1.0)
{
Lasttime = SDL_GetTicks();
Lasttime = cur_time;
Latetime = Lasttime + frame_time;
}
if (!InFrame)
{
InFrame = 1;
Nexttime = Lasttime + desired_frametime * 1000;
Nexttime = Lasttime + frame_time;
Latetime = Nexttime + frame_time;
}
cur_time = SDL_GetTicks();
if(cur_time >= Nexttime)
if (cur_time >= Nexttime)
{
time_left = 0;
}
else
{
time_left = Nexttime - cur_time;
}
if (time_left > 50)
{
@ -93,16 +219,81 @@ SpeedThrottle()
//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 )
{
SDL_Delay(time_left);
highPrecSleep( time_left );
}
if (!InFrame)
else
{
Lasttime = SDL_GetTicks();
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 )
{
double frameDelta;
frameDelta = (cur_time - Lasttime);
if ( frameDelta < frameDeltaMin )
{
frameDeltaMin = frameDelta;
}
if ( frameDelta > frameDeltaMax )
{
frameDeltaMax = frameDelta;
}
//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 + frame_time;
if ( cur_time >= Nexttime )
{
Lasttime = cur_time;
Nexttime = Lasttime + frame_time;
Latetime = Nexttime + frame_time;
}
return 0; /* Done waiting */
}
return 1; /* Must still wait some more */
}
@ -113,7 +304,7 @@ void IncreaseEmulationSpeed(void)
{
g_fpsScale *= LOGMUL;
if(g_fpsScale > Fastest) g_fpsScale = Fastest;
if (g_fpsScale > Fastest) g_fpsScale = Fastest;
RefreshThrottleFPS();

View File

@ -1,2 +1,5 @@
void RefreshThrottleFPS();
// throttle.h
int SpeedThrottle(void);
void RefreshThrottleFPS(void);
int getTimingMode(void);
int setTimingMode(int mode);