Skip to content

Commit 0b35741

Browse files
committed
ui: Revised FPS cap configurability
1 parent b29f784 commit 0b35741

File tree

7 files changed

+126
-55
lines changed

7 files changed

+126
-55
lines changed

config_spec.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,13 @@ display:
165165
vsync:
166166
type: bool
167167
default: true
168+
fps_cap:
169+
type: enum
170+
values: [none, 15_fps, 30_fps, 60_fps, 120_fps, 144_fps, custom]
171+
default: 60_fps
172+
custom_fps_cap:
173+
type: number
174+
default: 60.0
168175
ui:
169176
show_menubar:
170177
type: bool

ui/xemu.c

Lines changed: 27 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,7 @@ static bool alt_grab;
106106
static bool ctrl_grab;
107107
static int gui_saved_grab;
108108
static int gui_fullscreen;
109-
static int fps_selection = 3;
110-
static float frame_deadline = (NANOSECONDS_PER_SECOND / 60);
109+
static float min_frame_duration = (NANOSECONDS_PER_SECOND / 60);
111110
static int gui_grab_code = KMOD_LALT | KMOD_LCTRL;
112111
static SDL_Cursor *sdl_cursor_normal;
113112
static SDL_Cursor *sdl_cursor_hidden;
@@ -134,24 +133,30 @@ void xemu_toggle_fullscreen(void)
134133
toggle_full_screen(&sdl2_console[0]);
135134
}
136135

137-
int xemu_get_frame_rate_cap(void)
138-
{
139-
return fps_selection;
140-
}
141136

142-
void xemu_set_frame_rate_cap(int selection)
137+
void xemu_update_frame_rate_cap(void)
143138
{
144-
// FIXME: Remove redundent selection
145-
fps_selection = selection;
139+
int selection = g_config.display.window.fps_cap;
146140

147141
// No framerate cap
148142
if (!selection) {
149-
frame_deadline = 0;
143+
min_frame_duration = 0;
144+
return;
145+
}
146+
147+
// NOTE: Should we log in someway if the selection escaped its valid range?
148+
// For now we default to custom.
149+
float frame_rate;
150+
const float frame_rates[5] = { 15, 30, 60, 120, 144 };
151+
if (selection < CONFIG_DISPLAY_WINDOW_FPS_CAP_CUSTOM) {
152+
frame_rate = frame_rates[selection - 1];
153+
} else {
154+
frame_rate = g_config.display.window.custom_fps_cap;
150155
}
151156

152-
const int framerates[6] = { 60, 15, 30, 60, 120, 144 };
153-
int frame_rate = framerates[selection];
154-
frame_deadline = (float)NANOSECONDS_PER_SECOND / frame_rate;
157+
// Calculate the minimum time allowed between frames based on
158+
// the desired frame rate
159+
min_frame_duration = (float)NANOSECONDS_PER_SECOND / frame_rate;
155160
}
156161

157162
#define SDL2_REFRESH_INTERVAL_BUSY 16
@@ -1026,29 +1031,13 @@ void sdl2_gl_switch(DisplayChangeListener *dcl,
10261031
}
10271032
}
10281033

1029-
float fps = 1.0;
1030-
1031-
static void update_fps(void)
1032-
{
1033-
static int64_t last_update = 0;
1034-
const float r = 0.5;//0.1;
1035-
static float avg = 1.0;
1036-
int64_t now = qemu_clock_get_ns(QEMU_CLOCK_REALTIME);
1037-
float ms = ((float)(now-last_update)/1000000.0);
1038-
last_update = now;
1039-
if (fabs(avg-ms) > 0.25*avg) avg = ms;
1040-
else avg = avg*(1.0-r)+ms*r;
1041-
fps = 1000.0/avg;
1042-
}
1043-
10441034
void sdl2_gl_refresh(DisplayChangeListener *dcl)
10451035
{
10461036
struct sdl2_console *scon = container_of(dcl, struct sdl2_console, dcl);
10471037
assert(scon->opengl);
10481038
bool flip_required = false;
10491039

10501040
SDL_GL_MakeCurrent(scon->real_window, scon->winctx);
1051-
update_fps();
10521041

10531042
/* XXX: Note that this bypasses the usual VGA path in order to quickly
10541043
* get the surface. This is simple and fast, at the cost of accuracy.
@@ -1103,10 +1092,10 @@ void sdl2_gl_refresh(DisplayChangeListener *dcl)
11031092
qemu_mutex_unlock_main_loop();
11041093

11051094
/*
1106-
* Throttle to make sure swaps happen at 60Hz
1095+
* Throttle to make sure swaps happen at the desired frame rate cap
11071096
*/
1108-
static int64_t last_update = 0;
1109-
int64_t deadline = last_update + frame_deadline;
1097+
static int64_t last_frame_update = 0;
1098+
int64_t frame_expiration = last_frame_update + min_frame_duration;
11101099

11111100
#ifdef DEBUG_XEMU_C
11121101
int64_t sleep_acc = 0;
@@ -1121,8 +1110,8 @@ void sdl2_gl_refresh(DisplayChangeListener *dcl)
11211110

11221111
while (1) {
11231112
int64_t now = qemu_clock_get_ns(QEMU_CLOCK_REALTIME);
1124-
int64_t time_remaining = deadline - now;
1125-
if (now < deadline) {
1113+
int64_t time_remaining = frame_expiration - now;
1114+
if (now < frame_expiration) {
11261115
if (time_remaining > sleep_threshold) {
11271116
// Try to sleep until the until reaching the sleep threshold.
11281117
sleep_ns(time_remaining - sleep_threshold);
@@ -1139,11 +1128,10 @@ void sdl2_gl_refresh(DisplayChangeListener *dcl)
11391128
}
11401129
} else {
11411130
DPRINTF("zzZz %g %ld\n", (double)sleep_acc/1000000.0, spin_acc);
1142-
last_update = now;
1131+
last_frame_update = now;
11431132
break;
11441133
}
11451134
}
1146-
11471135
}
11481136

11491137
void sdl2_gl_redraw(struct sdl2_console *scon)
@@ -1413,6 +1401,9 @@ int main(int argc, char **argv)
14131401
set_full_screen(&sdl2_console[0], gui_fullscreen);
14141402
}
14151403

1404+
// Check settings and update the frame rate cap
1405+
xemu_update_frame_rate_cap();
1406+
14161407
/*
14171408
* FIXME: May want to create a callback mechanism for main QEMU thread
14181409
* to just run functions to avoid TLS bugs and locking issues.

ui/xui/main-menu.cc

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -554,9 +554,34 @@ void MainMenuDisplayView::Draw()
554554
"3840x2160\0",
555555
"Select preferred startup window size")) {
556556
}
557+
557558
Toggle("Vertical refresh sync", &g_config.display.window.vsync,
558559
"Sync to screen vertical refresh to reduce tearing artifacts");
559560

561+
if (ChevronCombo("Frame rate cap", &g_config.display.window.fps_cap,
562+
"None\0"
563+
"15fps\0"
564+
"30fps\0"
565+
"60fps\0"
566+
"120fps\0"
567+
"144fps\0"
568+
"Custom\0",
569+
"Limit the maximum frames per second")) {
570+
xemu_update_frame_rate_cap();
571+
}
572+
573+
char custom_refresh_rate[32];
574+
snprintf(custom_refresh_rate, sizeof(custom_refresh_rate),
575+
"Limit refresh rate to %dfps\0",
576+
(int)g_config.display.window.custom_fps_cap);
577+
if (g_config.display.window.fps_cap ==
578+
CONFIG_DISPLAY_WINDOW_FPS_CAP_CUSTOM) {
579+
if (Slider("Custom FPS Limit", &g_config.display.window.custom_fps_cap,
580+
10, 300, custom_refresh_rate)) {
581+
xemu_update_frame_rate_cap();
582+
}
583+
}
584+
560585
SectionTitle("Interface");
561586
Toggle("Show main menu bar", &g_config.display.ui.show_menubar,
562587
"Show main menu bar when mouse is activated");

ui/xui/menubar.cc

Lines changed: 45 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -194,16 +194,51 @@ void ShowMainMenu()
194194
nv2a_set_surface_scale_factor(rendering_scale + 1);
195195
}
196196

197-
int max_fps = xemu_get_frame_rate_cap();
198-
if(ImGui::Combo("Frame Rate Cap", &max_fps,
199-
"none\0"
200-
"15fps\0"
201-
"30fps\0"
202-
"60fps\0"
203-
"120fps\0"
204-
"144fps\0")) {
205-
xemu_set_frame_rate_cap(max_fps);
206-
}
197+
198+
// Define the string array used for fps selection in the menu bar
199+
// Split the string into a set and custom region, since we will have
200+
// to format a section of the string, but can't format the whole string
201+
// due to the null terminations.
202+
//
203+
// We need to both allocate a string beginning with the base options
204+
// and track the substring size, so just use a macro.
205+
#ifndef FPS_BASE_OPTIONS_STR
206+
#define FPS_BASE_OPTIONS_STR \
207+
"None\0" \
208+
"15fps\0" \
209+
"30fps\0" \
210+
"60fps\0" \
211+
"120fps\0" \
212+
"144fps"
213+
#endif
214+
215+
// Allocate the fps options buffer
216+
char fps_options[64] = FPS_BASE_OPTIONS_STR;
217+
// Split into regions
218+
const size_t fps_options_set_region_size =
219+
sizeof(FPS_BASE_OPTIONS_STR);
220+
char *fps_options_custom_region =
221+
fps_options + fps_options_set_region_size;
222+
size_t fps_options_custom_region_size =
223+
sizeof(fps_options) - fps_options_set_region_size;
224+
// Clean up the macro (kinda gross we that we need one, but better than a magic
225+
// number)
226+
#undef FPS_BASE_OPTIONS_STR
227+
228+
// Format the custom region to specifc the custom FPS.
229+
// Clear the custom region first! If the custom FPS jumps by over a
230+
// digit and the custom region is not cleared, an additional
231+
// selection option may be created.
232+
memset(fps_options_custom_region, '\0',
233+
fps_options_custom_region_size);
234+
snprintf(fps_options_custom_region, fps_options_custom_region_size,
235+
"Custom (%dfps)",
236+
(int)g_config.display.window.custom_fps_cap);
237+
238+
if (ImGui::Combo("Frame Rate Cap", &g_config.display.window.fps_cap,
239+
fps_options)) {
240+
xemu_update_frame_rate_cap();
241+
}
207242

208243
ImGui::Combo("Display Mode", &g_config.display.ui.fit,
209244
"Center\0Scale\0Stretch\0");

ui/xui/widgets.cc

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -222,8 +222,17 @@ bool Toggle(const char *str_id, bool *v, const char *description)
222222
return status;
223223
}
224224

225-
void Slider(const char *str_id, float *v, const char *description)
225+
bool Slider(const char *str_id, float *v, const char *description)
226226
{
227+
return Slider(str_id, v, 0, 1, description);
228+
}
229+
230+
bool Slider(const char *str_id, float *v, float min, float max,
231+
const char *description)
232+
{
233+
float initial = *v;
234+
float pos = (initial - min) / (max - min);
235+
227236
ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32_BLACK_TRANS);
228237

229238
ImGuiStyle &style = ImGui::GetStyle();
@@ -261,13 +270,13 @@ void Slider(const char *str_id, float *v, const char *description)
261270
ImGui::IsKeyPressed(ImGuiKey_GamepadDpadLeft) ||
262271
ImGui::IsKeyPressed(ImGuiKey_GamepadLStickLeft) ||
263272
ImGui::IsKeyPressed(ImGuiKey_GamepadRStickLeft)) {
264-
*v -= 0.05;
273+
pos -= 0.05;
265274
}
266275
if (ImGui::IsKeyPressed(ImGuiKey_RightArrow) ||
267276
ImGui::IsKeyPressed(ImGuiKey_GamepadDpadRight) ||
268277
ImGui::IsKeyPressed(ImGuiKey_GamepadLStickRight) ||
269278
ImGui::IsKeyPressed(ImGuiKey_GamepadRStickRight)) {
270-
*v += 0.05;
279+
pos += 0.05;
271280
}
272281

273282
if (
@@ -286,16 +295,19 @@ void Slider(const char *str_id, float *v, const char *description)
286295

287296
if (ImGui::IsItemActive()) {
288297
ImVec2 mouse = ImGui::GetMousePos();
289-
*v = GetSliderValueForMousePos(mouse, slider_pos, slider_size);
298+
pos = GetSliderValueForMousePos(mouse, slider_pos, slider_size);
290299
}
291-
*v = fmax(0, fmin(*v, 1));
292-
DrawSlider(*v, ImGui::IsItemHovered() || ImGui::IsItemActive(), slider_pos,
300+
pos = fmax(0, fmin(pos, 1));
301+
DrawSlider(pos, ImGui::IsItemHovered() || ImGui::IsItemActive(), slider_pos,
293302
slider_size);
294303

295304
ImVec2 slider_max = ImVec2(slider_pos.x + slider_size.x, slider_pos.y + slider_size.y);
296305
ImGui::RenderNavHighlight(ImRect(slider_pos, slider_max), window->GetID("###slider"));
297306

298307
ImGui::PopStyleColor();
308+
309+
*v = (pos * (max - min)) + min;
310+
return *v != initial;
299311
}
300312

301313
bool FilePicker(const char *str_id, const char **buf, const char *filters,

ui/xui/widgets.hh

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@ float GetSliderValueForMousePos(ImVec2 mouse, ImVec2 pos, ImVec2 size);
3434
void DrawSlider(float v, bool hovered, ImVec2 pos, ImVec2 size);
3535
void DrawToggle(bool enabled, bool hovered, ImVec2 pos, ImVec2 size);
3636
bool Toggle(const char *str_id, bool *v, const char *description = nullptr);
37-
void Slider(const char *str_id, float *v, const char *description = nullptr);
37+
bool Slider(const char *str_id, float *v, const char *description = nullptr);
38+
bool Slider(const char *str_id, float *v, float min, float max,
39+
const char *description = nullptr);
3840
bool FilePicker(const char *str_id, const char **buf, const char *filters,
3941
bool dir = false);
4042
void DrawComboChevron();

ui/xui/xemu-hud.h

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,7 @@ extern "C" {
3333
// Implemented in xemu.c
3434
int xemu_is_fullscreen(void);
3535
void xemu_toggle_fullscreen(void);
36-
int xemu_get_frame_rate_cap(void);
37-
void xemu_set_frame_rate_cap(int selection);
36+
void xemu_update_frame_rate_cap(void);
3837
void xemu_eject_disc(Error **errp);
3938
void xemu_load_disc(const char *path, Error **errp);
4039

0 commit comments

Comments
 (0)