diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 74382718..82db5765 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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 diff --git a/src/drivers/Qt/ConsoleWindow.cpp b/src/drivers/Qt/ConsoleWindow.cpp index bfead09d..fe910d7e 100644 --- a/src/drivers/Qt/ConsoleWindow.cpp +++ b/src/drivers/Qt/ConsoleWindow.cpp @@ -1,5 +1,14 @@ // GameApp.cpp // +#ifdef __linux__ +#include +#include +#include +#include +#include +#include +#endif + #include #include #include @@ -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(); diff --git a/src/drivers/Qt/ConsoleWindow.h b/src/drivers/Qt/ConsoleWindow.h index d883d779..971f2c85 100644 --- a/src/drivers/Qt/ConsoleWindow.h +++ b/src/drivers/Qt/ConsoleWindow.h @@ -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 &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); diff --git a/src/drivers/Qt/TimingConf.cpp b/src/drivers/Qt/TimingConf.cpp new file mode 100644 index 00000000..7dc92dbc --- /dev/null +++ b/src/drivers/Qt/TimingConf.cpp @@ -0,0 +1,534 @@ +// TimingConf.cpp +// +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#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; jcount(); 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; jcount(); 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; jcount(); j++) + { + if ( timingDevSelBox->itemData(j).toInt() == mode ) + { + timingDevSelBox->setCurrentIndex( j ); + } + } +} +//---------------------------------------------------------------------------- diff --git a/src/drivers/Qt/TimingConf.h b/src/drivers/Qt/TimingConf.h new file mode 100644 index 00000000..51b3d8e7 --- /dev/null +++ b/src/drivers/Qt/TimingConf.h @@ -0,0 +1,65 @@ +// TimingConf.h +// + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 ); + +}; diff --git a/src/drivers/Qt/config.cpp b/src/drivers/Qt/config.cpp index 7c5aba61..125f017f 100644 --- a/src/drivers/Qt/config.cpp +++ b/src/drivers/Qt/config.cpp @@ -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", ""); diff --git a/src/drivers/Qt/fceuWrapper.cpp b/src/drivers/Qt/fceuWrapper.cpp index 484ad880..79abd540 100644 --- a/src/drivers/Qt/fceuWrapper.cpp +++ b/src/drivers/Qt/fceuWrapper.cpp @@ -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; diff --git a/src/drivers/Qt/sdl-throttle.cpp b/src/drivers/Qt/sdl-throttle.cpp index 2ade8ae4..f94612b8 100644 --- a/src/drivers/Qt/sdl-throttle.cpp +++ b/src/drivers/Qt/sdl-throttle.cpp @@ -4,16 +4,112 @@ #include "Qt/sdl.h" #include "Qt/throttle.h" +#if defined(__linux) || defined(__APPLE__) +#include +#endif + +#ifdef __linux__ +#include +#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(); diff --git a/src/drivers/Qt/throttle.h b/src/drivers/Qt/throttle.h index 5709c88a..410ebff9 100644 --- a/src/drivers/Qt/throttle.h +++ b/src/drivers/Qt/throttle.h @@ -1,2 +1,5 @@ -void RefreshThrottleFPS(); +// throttle.h int SpeedThrottle(void); +void RefreshThrottleFPS(void); +int getTimingMode(void); +int setTimingMode(int mode);