Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/core/Frontend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5681,6 +5681,7 @@ CMenuManager::ProcessUserInput(uint8 goDown, uint8 goUp, uint8 optionSelected, u
m_PrefsShowLegends = true;
m_PrefsVsyncDisp = true;
m_PrefsFrameLimiter = true;
RsGlobal.maxFPS = 30;
m_PrefsRadarMode = 0;
m_PrefsShowHud = true;
m_nDisplayVideoMode = m_nPrefsVideoMode;
Expand Down
71 changes: 59 additions & 12 deletions src/core/MenuScreensCustom.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include "Text.h"
#include "crossplatform.h"
#include "custompipes.h"
#include "framepacing.h"
#include "main.h"
#include "platform.h"
#include "postfx.h"
Expand All @@ -38,6 +39,9 @@

#ifdef CUSTOM_FRONTEND_OPTIONS

#define FRAME_LIMIT_SELECTOR \
MENUACTION_CFO_DYNAMIC, "FEM_FRM", {new CCFODynamic(nil, nil, nil, FrameLimitDraw, FrameLimitButtonPress)}, 0, 0, MENUALIGN_LEFT,

#if defined(IMPROVED_VIDEOMODE) && !defined(GTA_HANDHELD)
#ifdef UNLOCK_MENU_OPTIONS_INGAME
#define VIDEOMODE_SELECTOR \
Expand Down Expand Up @@ -146,6 +150,58 @@

const char *filterNames[] = { "FEM_NON", "FEM_SIM", "FEM_NRM", "FEM_MOB" };
const char *off_on[] = { "FEM_OFF", "FEM_ON" };

static const int frameLimits[] = {30, 40, 50, 60, 75, 90, 100, 120, 144, 165, 180, 200, 240};

static wchar *
FrameLimitDraw(bool *disabled, bool userHovering)
{
(void)userHovering;
*disabled = false;
if(!FrontEndMenuManager.m_PrefsFrameLimiter) return TheText.Get("FEM_OFF");

static wchar text[16];
char value[16];
snprintf(value, sizeof(value), "%d FPS", RsFramePacer::SanitizeLimit(RsGlobal.maxFPS));
AsciiToUnicode(value, text);
return text;
}

static void
FrameLimitButtonPress(int8 action)
{
if(action != FEOPTION_ACTION_LEFT && action != FEOPTION_ACTION_RIGHT && action != FEOPTION_ACTION_SELECT) return;

const bool increase = action != FEOPTION_ACTION_LEFT;
if(!FrontEndMenuManager.m_PrefsFrameLimiter) {
RsGlobal.maxFPS = increase ? frameLimits[0] : frameLimits[ARRAY_SIZE(frameLimits) - 1];
FrontEndMenuManager.m_PrefsFrameLimiter = true;
} else {
const int current = RsFramePacer::SanitizeLimit(RsGlobal.maxFPS);
int next = 0;
if(increase) {
for(int i = 0; i < ARRAY_SIZE(frameLimits); i++) {
if(frameLimits[i] > current) {
next = frameLimits[i];
break;
}
}
} else {
for(int i = ARRAY_SIZE(frameLimits) - 1; i >= 0; i--) {
if(frameLimits[i] < current) {
next = frameLimits[i];
break;
}
}
}

// Keep the last valid limit while disabled; zero is never a frame interval.
if(next) RsGlobal.maxFPS = next;
else FrontEndMenuManager.m_PrefsFrameLimiter = false;
}
FrontEndMenuManager.SaveSettings();
}

#ifdef RW_WGPU
// WebGPU backend choices. Index maps to rw::wgpu::BACKEND_* in the startup
// plumbing (glfw.cpp). D3D12 is Windows-only; OpenGL is the desktop-GL path.
Expand All @@ -161,6 +217,7 @@ RestoreDefGraphics(int8 action)
{
if(action != FEOPTION_ACTION_SELECT) return;

RsGlobal.maxFPS = 30;
#ifdef PS2_ALPHA_TEST
gPS2alphaTest = false;
#endif
Expand Down Expand Up @@ -748,12 +805,7 @@ CMenuScreenCustom aScreens[] = {
0,
MENUALIGN_LEFT,
#endif
MENUACTION_FRAMELIMIT,
"FEM_FRM",
{nil, SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS},
0,
0,
MENUALIGN_LEFT,
FRAME_LIMIT_SELECTOR
#if defined LEGACY_MENU_OPTIONS && !defined EXTENDED_COLOURFILTER
MENUACTION_TRAILS,
"FED_TRA",
Expand Down Expand Up @@ -1953,12 +2005,7 @@ CMenuScreenCustom aScreens[] = {
0,
MENUALIGN_LEFT,
#endif
MENUACTION_FRAMELIMIT,
"FEM_FRM",
{nil, SAVESLOT_NONE, MENUPAGE_GRAPHICS_SETTINGS},
0,
0,
MENUALIGN_LEFT,
FRAME_LIMIT_SELECTOR
MULTISAMPLING_SELECTOR ISLAND_LOADING_SELECTOR DUALPASS_SELECTOR
WGPU_BACKEND_SELECTOR
#ifdef EXTENDED_COLOURFILTER
Expand Down
2 changes: 2 additions & 0 deletions src/core/re3.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
#include "Zones.h"

#include "crossplatform.h"
#include "framepacing.h"

#ifndef _WIN32
#include "assert.h"
Expand Down Expand Up @@ -560,6 +561,7 @@ bool LoadINISettings()
ReadIniIfExists("Graphics", "AspectRatio", &FrontEndMenuManager.m_PrefsUseWideScreen);
ReadIniIfExists("Graphics", "FrameLimiter", &FrontEndMenuManager.m_PrefsFrameLimiter);
ReadIniIfExists("Graphics", "MaxFPS", &RsGlobal.maxFPS);
RsGlobal.maxFPS = RsFramePacer::SanitizeLimit(RsGlobal.maxFPS);
#ifdef LEGACY_MENU_OPTIONS
ReadIniIfExists("Graphics", "VSync", &FrontEndMenuManager.m_PrefsVsyncDisp);
ReadIniIfExists("Graphics", "Trails", &CMBlur::BlurOn);
Expand Down
71 changes: 71 additions & 0 deletions src/skel/framepacing.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
#ifndef RS_FRAMEPACING_H
#define RS_FRAMEPACING_H

#include <chrono>
#include <cmath>

// One policy and sub-millisecond clock for every desktop rendering backend.
// The platform performs the interruptible wait, then checks input/focus again.
class RsFramePacer
{
bool m_started;
int m_targetFPS;
double m_lastFrame;

public:
RsFramePacer() { Reset(); }

static int SanitizeLimit(int fps)
{
return fps <= 0 ? 30 : (fps > 1000 ? 1000 : fps);
}

static int GetTargetFPS(bool limiterEnabled, int maxFPS, bool menuActive)
{
if (!limiterEnabled)
return menuActive ? 60 : 0;
const int limit = SanitizeLimit(maxFPS);
return menuActive && limit > 60 ? 60 : limit;
}

static double CurrentTimeSeconds()
{
return std::chrono::duration<double>(std::chrono::steady_clock::now().time_since_epoch()).count();
}

void Reset()
{
m_started = false;
m_targetFPS = 0;
m_lastFrame = 0.0;
}

// A zero result permits and records this frame. Never accumulate overdue
// deadlines: slow rendering, loading or focus loss must not cause a burst.
double RemainingSeconds(double now, int targetFPS)
{
if (!std::isfinite(now)) {
Reset();
return 0.0;
}

targetFPS = targetFPS > 0 ? SanitizeLimit(targetFPS) : 0;
if (!m_started || targetFPS == 0 || targetFPS != m_targetFPS || now < m_lastFrame) {
m_started = true;
m_targetFPS = targetFPS;
m_lastFrame = now;
return 0.0;
}

const double remaining = 1.0 / (double)targetFPS - (now - m_lastFrame);
// Arithmetic at an exact deadline can differ by a fraction of a
// nanosecond. Do not dispatch another OS wait for that rounding noise.
if (remaining <= 1.0e-9) {
m_lastFrame = now;
return 0.0;
}
return remaining;
}
};

#endif
Loading