implement 3 configurable and toggleable framerate targets (#2159)

This pr allows for configuring the framerate target and adds support for two other framerate targets, a "fastforward" and "slowmo" target which can be enabled via either a toggle or holding a button.
this allows for supporting a more accurate framerate target and allows for users to slow down the speed of gameplay if they so desire
This commit is contained in:
Jakly 2024-09-29 03:30:13 -04:00 committed by GitHub
parent 2eb6d44c2c
commit e9446fa9dc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 394 additions and 55 deletions

View File

@ -55,7 +55,6 @@ DefaultList<int> DefaultInts =
{"Screen.VSyncInterval", 1},
{"3D.Renderer", renderer3D_Software},
{"3D.GL.ScaleFactor", 1},
{"MaxFPS", 1000},
#ifdef JIT_ENABLED
{"JIT.MaxBlockSize", 32},
#endif
@ -120,6 +119,13 @@ DefaultList<std::string> DefaultStrings =
{"Instance*.Firmware.Username", "melonDS"}
};
DefaultList<double> DefaultDoubles =
{
{"TargetFPS", 60.0},
{"FastForwardFPS", 1000.0},
{"SlowmoFPS", 30.0},
};
LegacyEntry LegacyFile[] =
{
{"Key_A", 0, "Keyboard.A", true},
@ -153,7 +159,7 @@ LegacyEntry LegacyFile[] =
{"HKKey_Pause", 0, "Keyboard.HK_Pause", true},
{"HKKey_Reset", 0, "Keyboard.HK_Reset", true},
{"HKKey_FastForward", 0, "Keyboard.HK_FastForward", true},
{"HKKey_FastForwardToggle", 0, "Keyboard.HK_FastForwardToggle", true},
{"HKKey_FastForwardToggle", 0, "Keyboard.HK_FrameLimitToggle", true},
{"HKKey_FullscreenToggle", 0, "Keyboard.HK_FullscreenToggle", true},
{"HKKey_SwapScreens", 0, "Keyboard.HK_SwapScreens", true},
{"HKKey_SwapScreenEmphasis", 0, "Keyboard.HK_SwapScreenEmphasis", true},
@ -169,7 +175,7 @@ LegacyEntry LegacyFile[] =
{"HKJoy_Pause", 0, "Joystick.HK_Pause", true},
{"HKJoy_Reset", 0, "Joystick.HK_Reset", true},
{"HKJoy_FastForward", 0, "Joystick.HK_FastForward", true},
{"HKJoy_FastForwardToggle", 0, "Joystick.HK_FastForwardToggle", true},
{"HKJoy_FastForwardToggle", 0, "Joystick.HK_FrameLimitToggle", true},
{"HKJoy_FullscreenToggle", 0, "Joystick.HK_FullscreenToggle", true},
{"HKJoy_SwapScreens", 0, "Joystick.HK_SwapScreens", true},
{"HKJoy_SwapScreenEmphasis", 0, "Joystick.HK_SwapScreenEmphasis", true},
@ -434,6 +440,18 @@ std::string Array::GetString(const int id)
return tval.as_string();
}
double Array::GetDouble(const int id)
{
while (Data.size() < id+1)
Data.push_back(0.0);
toml::value& tval = Data[id];
if (!tval.is_floating())
tval = 0.0;
return tval.as_floating();
}
void Array::SetInt(const int id, int val)
{
while (Data.size() < id+1)
@ -470,6 +488,15 @@ void Array::SetString(const int id, const std::string& val)
tval = val;
}
void Array::SetDouble(const int id, double val)
{
while (Data.size() < id+1)
Data.push_back(0.0);
toml::value& tval = Data[id];
tval = val;
}
/*Table::Table()// : Data(toml::value())
{
@ -562,6 +589,15 @@ std::string Table::GetString(const std::string& path)
return tval.as_string();
}
double Table::GetDouble(const std::string& path)
{
toml::value& tval = ResolvePath(path);
if (!tval.is_floating())
tval = FindDefault(path, 0.0, DefaultDoubles);
return tval.as_floating();
}
void Table::SetInt(const std::string& path, int val)
{
std::string rngkey = GetDefaultKey(PathPrefix+path);
@ -593,6 +629,12 @@ void Table::SetString(const std::string& path, const std::string& val)
tval = val;
}
void Table::SetDouble(const std::string& path, double val)
{
toml::value& tval = ResolvePath(path);
tval = val;
}
toml::value& Table::ResolvePath(const std::string& path)
{
toml::value* ret = &Data;

View File

@ -61,11 +61,13 @@ public:
int64_t GetInt64(const int id);
bool GetBool(const int id);
std::string GetString(const int id);
double GetDouble(const int id);
void SetInt(const int id, int val);
void SetInt64(const int id, int64_t val);
void SetBool(const int id, bool val);
void SetString(const int id, const std::string& val);
void SetDouble(const int id, double val);
// convenience
@ -99,11 +101,13 @@ public:
int64_t GetInt64(const std::string& path);
bool GetBool(const std::string& path);
std::string GetString(const std::string& path);
double GetDouble(const std::string& path);
void SetInt(const std::string& path, int val);
void SetInt64(const std::string& path, int64_t val);
void SetBool(const std::string& path, bool val);
void SetString(const std::string& path, const std::string& val);
void SetDouble(const std::string& path, double val);
// convenience

View File

@ -88,7 +88,31 @@ EmuInstance::EmuInstance(int inst) : deleting(false),
cheatsOn = localCfg.GetBool("EnableCheats");
doLimitFPS = globalCfg.GetBool("LimitFPS");
maxFPS = globalCfg.GetInt("MaxFPS");
double val = globalCfg.GetDouble("TargetFPS");
if (val == 0.0)
{
Platform::Log(Platform::LogLevel::Error, "Target FPS in config invalid\n");
targetFPS = 1.0 / 60.0;
}
else targetFPS = 1.0 / val;
val = globalCfg.GetDouble("FastForwardFPS");
if (val == 0.0)
{
Platform::Log(Platform::LogLevel::Error, "Fast-Forward FPS in config invalid\n");
fastForwardFPS = 1.0 / 60.0;
}
else fastForwardFPS = 1.0 / val;
val = globalCfg.GetDouble("SlowmoFPS");
if (val == 0.0)
{
Platform::Log(Platform::LogLevel::Error, "Slow-Mo FPS in config invalid\n");
slowmoFPS = 1.0 / 60.0;
}
else slowmoFPS = 1.0 / val;
doAudioSync = globalCfg.GetBool("AudioSync");
mpAudioMode = globalCfg.GetInt("MP.AudioMode");

View File

@ -36,7 +36,7 @@ enum
HK_Pause,
HK_Reset,
HK_FastForward,
HK_FastForwardToggle,
HK_FrameLimitToggle,
HK_FullscreenToggle,
HK_SwapScreens,
HK_SwapScreenEmphasis,
@ -46,6 +46,9 @@ enum
HK_PowerButton,
HK_VolumeUp,
HK_VolumeDown,
HK_SlowMo,
HK_FastForwardToggle,
HK_SlowMoToggle,
HK_MAX
};
@ -252,7 +255,12 @@ public:
std::unique_ptr<SaveManager> firmwareSave;
bool doLimitFPS;
int maxFPS;
double curFPS;
double targetFPS;
double fastForwardFPS;
double slowmoFPS;
bool fastForwardToggled;
bool slowmoToggled;
bool doAudioSync;
private:

View File

@ -47,7 +47,7 @@ const char* EmuInstance::hotkeyNames[HK_MAX] =
"HK_Pause",
"HK_Reset",
"HK_FastForward",
"HK_FastForwardToggle",
"HK_FrameLimitToggle",
"HK_FullscreenToggle",
"HK_SwapScreens",
"HK_SwapScreenEmphasis",
@ -56,7 +56,10 @@ const char* EmuInstance::hotkeyNames[HK_MAX] =
"HK_FrameStep",
"HK_PowerButton",
"HK_VolumeUp",
"HK_VolumeDown"
"HK_VolumeDown",
"HK_SlowMo",
"HK_FastForwardToggle",
"HK_SlowMoToggle"
};

View File

@ -149,12 +149,17 @@ void EmuThread::run()
char melontitle[100];
bool fastforward = false;
bool slowmo = false;
emuInstance->fastForwardToggled = false;
emuInstance->slowmoToggled = false;
while (emuStatus != emuStatus_Exit)
{
MPInterface::Get().Process();
emuInstance->inputProcess();
if (emuInstance->hotkeyPressed(HK_FastForwardToggle)) emit windowLimitFPSChange();
if (emuInstance->hotkeyPressed(HK_FrameLimitToggle)) emit windowLimitFPSChange();
if (emuInstance->hotkeyPressed(HK_Pause)) emuTogglePause();
if (emuInstance->hotkeyPressed(HK_Reset)) emuReset();
@ -332,22 +337,34 @@ void EmuThread::run()
emit windowUpdate();
winUpdateCount = 0;
}
if (emuInstance->hotkeyPressed(HK_FastForwardToggle)) emuInstance->fastForwardToggled = !emuInstance->fastForwardToggled;
if (emuInstance->hotkeyPressed(HK_SlowMoToggle)) emuInstance->slowmoToggled = !emuInstance->slowmoToggled;
bool fastforward = emuInstance->hotkeyDown(HK_FastForward);
bool enablefastforward = emuInstance->hotkeyDown(HK_FastForward) | emuInstance->fastForwardToggled;
bool enableslowmo = emuInstance->hotkeyDown(HK_SlowMo) | emuInstance->slowmoToggled;
if (useOpenGL)
{
// when using OpenGL: when toggling fast-forward, change the vsync interval
if (emuInstance->hotkeyPressed(HK_FastForward))
// when using OpenGL: when toggling fast-forward or slowmo, change the vsync interval
if ((enablefastforward || enableslowmo) && !(fastforward || slowmo))
{
emuInstance->setVSyncGL(false);
}
else if (emuInstance->hotkeyReleased(HK_FastForward))
else if (!(enablefastforward || enableslowmo) && (fastforward || slowmo))
{
emuInstance->setVSyncGL(true);
}
}
fastforward = enablefastforward;
slowmo = enableslowmo;
if (slowmo) emuInstance->curFPS = emuInstance->slowmoFPS;
else if (fastforward) emuInstance->curFPS = emuInstance->fastForwardFPS;
else if (!emuInstance->doLimitFPS) emuInstance->curFPS = 1.0 / 1000.0;
else emuInstance->curFPS = emuInstance->targetFPS;
if (emuInstance->audioDSiVolumeSync && emuInstance->nds->ConsoleType == 1)
{
DSi* dsi = static_cast<DSi*>(emuInstance->nds);
@ -361,23 +378,19 @@ void EmuThread::run()
emuInstance->audioVolume = volumeLevel * (256.0 / 31.0);
}
if (emuInstance->doAudioSync && !fastforward)
if (emuInstance->doAudioSync && !(fastforward || slowmo))
emuInstance->audioSync();
double frametimeStep = nlines / (60.0 * 263.0);
{
bool limitfps = emuInstance->doLimitFPS && !fastforward;
double practicalFramelimit = limitfps ? frametimeStep : 1.0 / emuInstance->maxFPS;
double curtime = SDL_GetPerformanceCounter() * perfCountsSec;
frameLimitError += practicalFramelimit - (curtime - lastTime);
if (frameLimitError < -practicalFramelimit)
frameLimitError = -practicalFramelimit;
if (frameLimitError > practicalFramelimit)
frameLimitError = practicalFramelimit;
frameLimitError += emuInstance->curFPS - (curtime - lastTime);
if (frameLimitError < -emuInstance->curFPS)
frameLimitError = -emuInstance->curFPS;
if (frameLimitError > emuInstance->curFPS)
frameLimitError = emuInstance->curFPS;
if (round(frameLimitError * 1000.0) > 0.0)
{

View File

@ -49,6 +49,9 @@ static constexpr std::initializer_list<int> hk_general =
HK_FrameStep,
HK_FastForward,
HK_FastForwardToggle,
HK_SlowMo,
HK_SlowMoToggle,
HK_FrameLimitToggle,
HK_FullscreenToggle,
HK_Lid,
HK_Mic,
@ -65,6 +68,9 @@ static constexpr std::initializer_list<const char*> hk_general_labels =
"Reset",
"Frame step",
"Fast forward",
"Toggle fast forward",
"Slow mo",
"Toggle slow mo",
"Toggle FPS limit",
"Toggle fullscreen",
"Close/open lid",

View File

@ -39,7 +39,9 @@ InterfaceSettingsDialog::InterfaceSettingsDialog(QWidget* parent) : QDialog(pare
ui->spinMouseHideSeconds->setEnabled(ui->cbMouseHide->isChecked());
ui->spinMouseHideSeconds->setValue(cfg.GetInt("MouseHideSeconds"));
ui->cbPauseLostFocus->setChecked(cfg.GetBool("PauseLostFocus"));
ui->spinMaxFPS->setValue(cfg.GetInt("MaxFPS"));
ui->spinTargetFPS->setValue(cfg.GetDouble("TargetFPS"));
ui->spinFFW->setValue(cfg.GetDouble("FastForwardFPS"));
ui->spinSlow->setValue(cfg.GetDouble("SlowmoFPS"));
const QList<QString> themeKeys = QStyleFactory::keys();
const QString currentTheme = qApp->style()->objectName();
@ -65,6 +67,41 @@ void InterfaceSettingsDialog::on_cbMouseHide_clicked()
ui->spinMouseHideSeconds->setEnabled(ui->cbMouseHide->isChecked());
}
void InterfaceSettingsDialog::on_pbClean_clicked()
{
ui->spinTargetFPS->setValue(60.0000);
}
void InterfaceSettingsDialog::on_pbAccurate_clicked()
{
ui->spinTargetFPS->setValue(59.8261);
}
void InterfaceSettingsDialog::on_pb2x_clicked()
{
ui->spinFFW->setValue(ui->spinTargetFPS->value() * 2.0);
}
void InterfaceSettingsDialog::on_pb3x_clicked()
{
ui->spinFFW->setValue(ui->spinTargetFPS->value() * 3.0);
}
void InterfaceSettingsDialog::on_pbMAX_clicked()
{
ui->spinFFW->setValue(1000.0);
}
void InterfaceSettingsDialog::on_pbHalf_clicked()
{
ui->spinSlow->setValue(ui->spinTargetFPS->value() / 2.0);
}
void InterfaceSettingsDialog::on_pbQuarter_clicked()
{
ui->spinSlow->setValue(ui->spinTargetFPS->value() / 4.0);
}
void InterfaceSettingsDialog::done(int r)
{
if (r == QDialog::Accepted)
@ -74,7 +111,18 @@ void InterfaceSettingsDialog::done(int r)
cfg.SetBool("MouseHide", ui->cbMouseHide->isChecked());
cfg.SetInt("MouseHideSeconds", ui->spinMouseHideSeconds->value());
cfg.SetBool("PauseLostFocus", ui->cbPauseLostFocus->isChecked());
cfg.SetInt("MaxFPS", ui->spinMaxFPS->value());
double val = ui->spinTargetFPS->value();
if (val == 0.0) cfg.SetDouble("TargetFPS", 0.0001);
else cfg.SetDouble("TargetFPS", val);
val = ui->spinFFW->value();
if (val == 0.0) cfg.SetDouble("FastForwardFPS", 0.0001);
else cfg.SetDouble("FastForwardFPS", val);
val = ui->spinSlow->value();
if (val == 0.0) cfg.SetDouble("SlowmoFPS", 0.0001);
else cfg.SetDouble("SlowmoFPS", val);
QString themeName = ui->cbxUITheme->currentData().toString();
cfg.SetQString("UITheme", themeName);

View File

@ -60,6 +60,16 @@ private slots:
void on_cbMouseHide_clicked();
void on_pbClean_clicked();
void on_pbAccurate_clicked();
void on_pb2x_clicked();
void on_pb3x_clicked();
void on_pbMAX_clicked();
void on_pbHalf_clicked();
void on_pbQuarter_clicked();
private:
Ui::InterfaceSettingsDialog* ui;

View File

@ -6,12 +6,12 @@
<rect>
<x>0</x>
<y>0</y>
<width>337</width>
<height>275</height>
<width>389</width>
<height>356</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
@ -20,6 +20,9 @@
<string>Interface settings - melonDS</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout" stretch="0,0,0">
<property name="sizeConstraint">
<enum>QLayout::SizeConstraint::SetFixedSize</enum>
</property>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
@ -95,32 +98,209 @@
<property name="title">
<string>Framerate </string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_4" stretch="0,0">
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>Fast-forward limit</string>
<layout class="QGridLayout" name="gridLayout">
<property name="horizontalSpacing">
<number>6</number>
</property>
<property name="buddy">
<cstring>spinMaxFPS</cstring>
<property name="verticalSpacing">
<number>2</number>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="spinMaxFPS">
<property name="suffix">
<string> FPS</string>
</property>
<property name="minimum">
<number>60</number>
</property>
<property name="maximum">
<number>1000</number>
</property>
<property name="value">
<number>1000</number>
</property>
</widget>
<item row="0" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Target FPS</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Fast-Forward</string>
</property>
<property name="buddy">
<cstring>spinFFW</cstring>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QDoubleSpinBox" name="spinTargetFPS">
<property name="suffix">
<string> FPS</string>
</property>
<property name="decimals">
<number>4</number>
</property>
<property name="minimum">
<double>0.000100000000000</double>
</property>
<property name="maximum">
<double>1000.000000000000000</double>
</property>
<property name="value">
<double>59.826099999999997</double>
</property>
</widget>
</item>
<item row="2" column="2">
<layout class="QHBoxLayout" name="horizontalLayout_5">
<property name="spacing">
<number>2</number>
</property>
<item>
<widget class="QPushButton" name="pbQuarter">
<property name="maximumSize">
<size>
<width>63</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>1/4</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pbHalf">
<property name="maximumSize">
<size>
<width>62</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>1/2</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="2" column="1">
<widget class="QDoubleSpinBox" name="spinSlow">
<property name="suffix">
<string> FPS</string>
</property>
<property name="decimals">
<number>4</number>
</property>
<property name="minimum">
<double>0.000100000000000</double>
</property>
<property name="maximum">
<double>1000.000000000000000</double>
</property>
<property name="value">
<double>29.913000000000000</double>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QDoubleSpinBox" name="spinFFW">
<property name="suffix">
<string> FPS</string>
</property>
<property name="decimals">
<number>4</number>
</property>
<property name="minimum">
<double>0.000100000000000</double>
</property>
<property name="maximum">
<double>1000.000000000000000</double>
</property>
<property name="value">
<double>1000.000000000000000</double>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Slow-Mo</string>
</property>
</widget>
</item>
<item row="0" column="2">
<layout class="QHBoxLayout" name="horizontalLayout_10">
<property name="spacing">
<number>2</number>
</property>
<item>
<widget class="QPushButton" name="pbAccurate">
<property name="maximumSize">
<size>
<width>63</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Accurate</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pbClean">
<property name="maximumSize">
<size>
<width>62</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Clean</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="2">
<layout class="QHBoxLayout" name="horizontalLayout_9">
<property name="spacing">
<number>2</number>
</property>
<item>
<widget class="QPushButton" name="pb2x">
<property name="maximumSize">
<size>
<width>41</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>2x</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pb3x">
<property name="maximumSize">
<size>
<width>41</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>3x</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pbMAX">
<property name="maximumSize">
<size>
<width>41</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>MAX</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
@ -128,10 +308,10 @@
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
<set>QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok</set>
</property>
</widget>
</item>

View File

@ -1946,8 +1946,9 @@ void MainWindow::onOpenInterfaceSettings()
void MainWindow::onUpdateInterfaceSettings()
{
pauseOnLostFocus = globalCfg.GetBool("PauseLostFocus");
emuInstance->maxFPS = globalCfg.GetInt("MaxFPS");
emuInstance->targetFPS = 1.0 / globalCfg.GetDouble("TargetFPS");
emuInstance->fastForwardFPS = 1.0 / globalCfg.GetDouble("FastForwardFPS");
emuInstance->slowmoFPS = 1.0 / globalCfg.GetDouble("SlowmoFPS");
panel->setMouseHide(globalCfg.GetBool("MouseHide"),
globalCfg.GetInt("MouseHideSeconds")*1000);
}