From 09b0a12b0aa4bc94d0291524cb5d4282e2e86d5d Mon Sep 17 00:00:00 2001 From: Matthew Coppola Date: Thu, 21 Nov 2024 02:54:23 -0500 Subject: [PATCH 01/18] Allow changing video resolution at runtime + more Fullscreen exclusive mode is togglable at runtime, as well, as a dropdown menu with "borderless" and "exclusive" options. All variables pertaining to video resolution, exclusive mode, and maximized state are now synchronized. For instance, Alt+Enter will affect the state of the fullscreen video option and maximizing the window via the title bar will update the maximize window option accordingly. --- port/fast3d/gfx_modes.h | 8 +++ port/fast3d/gfx_sdl2.cpp | 98 ++++++++++++++++++++++++++-- port/fast3d/gfx_window_manager_api.h | 7 ++ port/include/video.h | 10 +++ port/src/main.c | 1 + port/src/optionsmenu.c | 79 +++++++++++++++++++--- port/src/video.c | 85 ++++++++++++++++++++++++ 7 files changed, 273 insertions(+), 15 deletions(-) create mode 100644 port/fast3d/gfx_modes.h diff --git a/port/fast3d/gfx_modes.h b/port/fast3d/gfx_modes.h new file mode 100644 index 000000000..1fe895c41 --- /dev/null +++ b/port/fast3d/gfx_modes.h @@ -0,0 +1,8 @@ +#ifndef GFX_MODES_H +#define GFX_MODES_H + +struct GfxModes { + char mode[10]; +}; + +#endif diff --git a/port/fast3d/gfx_sdl2.cpp b/port/fast3d/gfx_sdl2.cpp index c6b67eebb..d838532b5 100644 --- a/port/fast3d/gfx_sdl2.cpp +++ b/port/fast3d/gfx_sdl2.cpp @@ -6,6 +6,7 @@ #include "platform.h" #include "system.h" +#include "gfx_modes.h" #include "gfx_window_manager_api.h" #include "gfx_screen_config.h" @@ -30,10 +31,30 @@ static uint64_t qpc_freq; #define FRAME_INTERVAL_US_NUMERATOR 1000000 #define FRAME_INTERVAL_US_DENOMINATOR (target_fps) -static void set_fullscreen(bool on, bool call_callback) { - if (fullscreen_state == on) { - return; +static int32_t gfx_sdl_get_maximized_state(void) { + return (int32_t)maximized_state; +} + +static int32_t gfx_sdl_get_fullscreen_state(void) { + return (int32_t)fullscreen_state; +} + +static int32_t gfx_sdl_get_fullscreen_flag_mode(void) { + return fullscreen_flag == SDL_WINDOW_FULLSCREEN_DESKTOP ? 0 : 1; +} + +static void gfx_sdl_set_fullscreen_flag(int32_t mode) { + switch (mode) { + case 0: { + fullscreen_flag = SDL_WINDOW_FULLSCREEN_DESKTOP; + } break; + case 1: { + fullscreen_flag = SDL_WINDOW_FULLSCREEN; + } break; } +} + +static void set_fullscreen(bool on, bool call_callback) { fullscreen_state = on; SDL_SetWindowFullscreen(wnd, on ? fullscreen_flag : 0); if (call_callback && on_fullscreen_changed_callback) { @@ -42,9 +63,6 @@ static void set_fullscreen(bool on, bool call_callback) { } static void set_maximize_window(bool on) { - if (maximized_state == on) { - return; - } maximized_state = on; if (on) { SDL_MaximizeWindow(wnd); @@ -217,6 +235,20 @@ static void gfx_sdl_set_cursor_visibility(bool visible) { } } +static void gfx_sdl_set_closest_resolution(int32_t width, int32_t height) { + const SDL_DisplayMode mode = {.w = width, .h = height}; + SDL_DisplayMode closest = {}; + if (SDL_GetClosestDisplayMode(0, &mode, &closest)) { + SDL_SetWindowDisplayMode(wnd, &closest); + SDL_SetWindowSize(wnd, closest.w, closest.h); + } +} + +static void gfx_sdl_set_dimensions(uint32_t width, uint32_t height, int32_t posX, int32_t posY) { + SDL_SetWindowSize(wnd, width, height); + SDL_SetWindowPosition(wnd, posX, posY); +} + static void gfx_sdl_get_dimensions(uint32_t* width, uint32_t* height, int32_t* posX, int32_t* posY) { SDL_GL_GetDrawableSize(wnd, static_cast((void*)width), static_cast((void*)height)); SDL_GetWindowPosition(wnd, static_cast(posX), static_cast(posY)); @@ -235,6 +267,9 @@ static void gfx_sdl_handle_events(void) { case SDL_WINDOWEVENT: if (event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) { SDL_GL_GetDrawableSize(wnd, &window_width, &window_height); + if (!fullscreen_state) { + maximized_state = SDL_GetWindowFlags(wnd) & SDL_WINDOW_MAXIMIZED ? true : false; + } } else if (event.window.event == SDL_WINDOWEVENT_CLOSE && event.window.windowID == SDL_GetWindowID(wnd)) { // We listen specifically for main window close because closing main window @@ -324,15 +359,63 @@ static bool gfx_sdl_set_swap_interval(int interval) { return success; } -struct GfxWindowManagerAPI gfx_sdl = { +static GfxModes *gfx_sdl_alloc_display_modes(GfxModes *modes, int *num_modes) { + static const char *alloc_failed_msg = "failed to allocate video display modes"; + const int disp_idx = SDL_GetWindowDisplayIndex(wnd); + const int display_modes = SDL_GetNumDisplayModes(disp_idx); + int modes_sz = sizeof *modes * 16; + + modes = (GfxModes *)malloc(modes_sz); + if (!modes) { + sysLogPrintf(LOG_ERROR, "malloc: %s", alloc_failed_msg); + } + + strncpy(modes[0].mode, "Custom", sizeof modes[0].mode); + modes[0].mode[sizeof modes[0].mode - 1] = '\0'; + + int j = 1; + for (int i = 0; i < display_modes; ++i) { + SDL_DisplayMode mode = {}; + if (SDL_GetDisplayMode(disp_idx, i, &mode) == 0) { + if (j == (int)(modes_sz / sizeof *modes)) { + modes = (GfxModes *)realloc(modes, modes_sz <<= 1); + if (!modes) { + sysLogPrintf(LOG_ERROR, "realloc: %s", alloc_failed_msg); + } + } + + snprintf(modes[j].mode, sizeof modes[0].mode, "%dx%d", mode.w, mode.h); + + if (j && strcmp(modes[j].mode, modes[j - 1].mode) != 0) { + ++j; + } + } + } + + *num_modes = j; + modes_sz = *num_modes * sizeof *modes; + modes = (GfxModes *)realloc(modes, modes_sz); + if (!modes) { + sysLogPrintf(LOG_ERROR, "realloc: %s", alloc_failed_msg); + } + return modes; +} + +struct GfxWindowManagerAPI gfx_sdl = { gfx_sdl_init, gfx_sdl_close, + gfx_sdl_get_fullscreen_state, gfx_sdl_set_fullscreen_changed_callback, gfx_sdl_set_fullscreen, gfx_sdl_set_fullscreen_exclusive, + gfx_sdl_set_fullscreen_flag, + gfx_sdl_get_fullscreen_flag_mode, + gfx_sdl_get_maximized_state, gfx_sdl_set_maximize_window, gfx_sdl_get_active_window_refresh_rate, gfx_sdl_set_cursor_visibility, + gfx_sdl_set_closest_resolution, + gfx_sdl_set_dimensions, gfx_sdl_get_dimensions, gfx_sdl_handle_events, gfx_sdl_start_frame, @@ -344,4 +427,5 @@ struct GfxWindowManagerAPI gfx_sdl = { gfx_sdl_get_window_handle, gfx_sdl_set_window_title, gfx_sdl_set_swap_interval, + gfx_sdl_alloc_display_modes, }; diff --git a/port/fast3d/gfx_window_manager_api.h b/port/fast3d/gfx_window_manager_api.h index 61b05315f..ba70a056b 100644 --- a/port/fast3d/gfx_window_manager_api.h +++ b/port/fast3d/gfx_window_manager_api.h @@ -19,12 +19,18 @@ struct GfxWindowInitSettings { struct GfxWindowManagerAPI { void (*init)(const struct GfxWindowInitSettings *settings); void (*close)(void); + int32_t (*get_fullscreen_state)(void); void (*set_fullscreen_changed_callback)(void (*on_fullscreen_changed)(bool is_now_fullscreen)); void (*set_fullscreen)(bool enable); void (*set_fullscreen_exclusive)(bool exc); + void (*set_fullscreen_flag)(int32_t mode); + int32_t (*get_fullscreen_flag_mode)(void); + int32_t (*get_maximized_state)(void); void (*set_maximize)(bool enable); void (*get_active_window_refresh_rate)(uint32_t* refresh_rate); void (*set_cursor_visibility)(bool visible); + void (*set_closest_resolution)(int32_t width, int32_t height); + void (*set_dimensions)(uint32_t width, uint32_t height, int32_t posX, int32_t posY); void (*get_dimensions)(uint32_t* width, uint32_t* height, int32_t* posX, int32_t* posY); void (*handle_events)(void); bool (*start_frame)(void); @@ -36,6 +42,7 @@ struct GfxWindowManagerAPI { void *(*get_window_handle)(void); void (*set_window_title)(const char *); bool (*set_swap_interval)(int); + struct GfxModes *(*alloc_display_modes)(struct GfxModes *, int *); }; #endif diff --git a/port/include/video.h b/port/include/video.h index d7a888886..ce0bc2caf 100644 --- a/port/include/video.h +++ b/port/include/video.h @@ -27,17 +27,25 @@ s32 videoGetWidth(void); s32 videoGetHeight(void); f32 videoGetAspect(void); s32 videoGetFullscreen(void); +s32 videoGetFullscreenMode(void); s32 videoGetMaximizeWindow(void); void videoSetMaximizeWindow(s32 fs); u32 videoGetTextureFilter(void); s32 videoGetTextureFilter2D(void); s32 videoGetDetailTextures(void); +s32 videoGetDisplayModeIndex(void); +struct GfxModes *videoGetDisplayModes(void); +s32 videoGetNumDisplayModes(void); + +void videoAllocDisplayModes(void); void videoSetWindowOffset(s32 x, s32 y); void videoSetFullscreen(s32 fs); +void videoSetFullscreenMode(s32 mode); void videoSetTextureFilter(u32 filter); void videoSetTextureFilter2D(s32 filter); void videoSetDetailTextures(s32 detail); +void videoSetDisplayMode(const s32 mode_idx); s32 videoCreateFramebuffer(u32 w, u32 h, s32 upscale, s32 autoresize); void videoSetFramebuffer(s32 target); @@ -49,4 +57,6 @@ s32 videoFramebuffersSupported(void); void videoResetTextureCache(void); void videoFreeCachedTexture(const void *texptr); +void videoShutdown(void); + #endif diff --git a/port/src/main.c b/port/src/main.c index 5e404426d..f8f6af13f 100644 --- a/port/src/main.c +++ b/port/src/main.c @@ -88,6 +88,7 @@ static void cleanup(void) sysLogPrintf(LOG_NOTE, "shutdown"); inputSaveBinds(); configSave(CONFIG_PATH); + videoShutdown(); crashShutdown(); // TODO: actually shut down all subsystems } diff --git a/port/src/optionsmenu.c b/port/src/optionsmenu.c index 2b845ebe0..d9e8f75a3 100644 --- a/port/src/optionsmenu.c +++ b/port/src/optionsmenu.c @@ -9,6 +9,7 @@ #include "game/mainmenu.h" #include "game/menu.h" #include "game/gamefile.h" +#include "../fast3d/gfx_modes.h" #include "video.h" #include "input.h" #include "config.h" @@ -687,6 +688,29 @@ static MenuItemHandlerResult menuhandlerFullScreen(s32 operation, struct menuite return 0; } +static MenuItemHandlerResult menuhandlerFullScreenMode(s32 operation, struct menuitem *item, union handlerdata *data) +{ + static const char *opts[] = { + "Borderless", + "Exclusive" + }; + + switch (operation) { + case MENUOP_GETOPTIONCOUNT: + data->dropdown.value = ARRAYCOUNT(opts); + break; + case MENUOP_GETOPTIONTEXT: + return (intptr_t)opts[data->dropdown.value]; + case MENUOP_SET: + videoSetFullscreenMode(data->dropdown.value); + break; + case MENUOP_GETSELECTEDINDEX: + data->dropdown.value = videoGetFullscreenMode(); + } + + return 0; +} + static MenuItemHandlerResult menuhandlerMaximizeWindow(s32 operation, struct menuitem *item, union handlerdata *data) { switch (operation) { @@ -700,6 +724,29 @@ static MenuItemHandlerResult menuhandlerMaximizeWindow(s32 operation, struct men return 0; } +static MenuItemHandlerResult menuhandlerResolution(s32 operation, struct menuitem *item, union handlerdata *data) +{ + switch (operation) { + case MENUOP_CHECKDISABLED: + if (videoGetFullscreen() && videoGetFullscreenMode() == 0) { + return true; + } + break; + case MENUOP_GETOPTIONCOUNT: + data->dropdown.value = videoGetNumDisplayModes(); + break; + case MENUOP_GETOPTIONTEXT: + return (intptr_t)videoGetDisplayModes()[data->dropdown.value].mode; + case MENUOP_SET: + videoSetDisplayMode(data->dropdown.value); + break; + case MENUOP_GETSELECTEDINDEX: + data->dropdown.value = videoGetDisplayModeIndex(); + } + + return 0; +} + static MenuItemHandlerResult menuhandlerTexFilter(s32 operation, struct menuitem *item, union handlerdata *data) { static const char *opts[] = { @@ -820,6 +867,30 @@ struct menuitem g_ExtendedVideoMenuItems[] = { 0, menuhandlerFullScreen, }, + { + MENUITEMTYPE_DROPDOWN, + 0, + MENUITEMFLAG_LITERAL_TEXT, + (uintptr_t)"Full Screen Mode", + 0, + menuhandlerFullScreenMode, + }, + { + MENUITEMTYPE_DROPDOWN, + 0, + MENUITEMFLAG_LITERAL_TEXT, + (uintptr_t)"Resolution", + 0, + menuhandlerResolution, + }, + { + MENUITEMTYPE_DROPDOWN, + 0, + MENUITEMFLAG_LITERAL_TEXT, + (uintptr_t)"Texture Filtering", + 0, + menuhandlerTexFilter, + }, { MENUITEMTYPE_CHECKBOX, 0, @@ -836,14 +907,6 @@ struct menuitem g_ExtendedVideoMenuItems[] = { 0, menuhandlerTexDetail, }, - { - MENUITEMTYPE_DROPDOWN, - 0, - MENUITEMFLAG_LITERAL_TEXT, - (uintptr_t)"Texture Filtering", - 0, - menuhandlerTexFilter, - }, { MENUITEMTYPE_CHECKBOX, 0, diff --git a/port/src/video.c b/port/src/video.c index 52eafc228..8acd6caca 100644 --- a/port/src/video.c +++ b/port/src/video.c @@ -6,6 +6,7 @@ #include #include "platform.h" #include "config.h" +#include "../fast3d/gfx_modes.h" #include "video.h" #include "../fast3d/gfx_api.h" @@ -39,6 +40,8 @@ static s32 vidAllowHiDpi = false; static s32 vidVsync = 1; static s32 vidMSAA = 1; static s32 vidFramerateLimit = 0; +static struct GfxModes *vidModes = NULL; // Allocated in videoAllocDisplayModes(). Freed in videoShutdown(). +static s32 vidNumModes = 0; static s32 texFilter = FILTER_LINEAR; static s32 texFilter2D = true; @@ -78,6 +81,7 @@ s32 videoInit(void) }; gfx_init(&set); + videoAllocDisplayModes(); if (!wmAPI->set_swap_interval(vidVsync)) { vidVsync = 0; @@ -177,11 +181,19 @@ s32 videoGetHeight(void) s32 videoGetFullscreen(void) { + vidFullscreen = wmAPI->get_fullscreen_state(); return vidFullscreen; } +s32 videoGetFullscreenMode(void) +{ + vidFullscreenExclusive = wmAPI->get_fullscreen_flag_mode(); + return vidFullscreenExclusive; +} + s32 videoGetMaximizeWindow(void) { + vidMaximize = wmAPI->get_maximized_state(); return vidMaximize; } @@ -190,6 +202,59 @@ f32 videoGetAspect(void) return gfx_current_dimensions.aspect_ratio; } +s32 videoGetDisplayModeIndex(void) +{ + for (int i = 1; i < vidNumModes; ++i) { + char tmp[10] = {0}; + snprintf(tmp, sizeof tmp, "%dx%d", gfx_current_dimensions.width, gfx_current_dimensions.height); + if (strcmp(tmp, vidModes[i].mode) == 0) { + return i; + } + } + + // Current dimensions don't match any known mode, so return index 0, "Custom". + return 0; +} + +void videoAllocDisplayModes(void) +{ + vidModes = wmAPI->alloc_display_modes(vidModes, &vidNumModes); +} + +struct GfxModes *videoGetDisplayModes(void) +{ + return vidModes; +} + +s32 videoGetNumDisplayModes(void) +{ + return vidNumModes; +} + +void videoSetDisplayMode(const s32 mode_idx) +{ + const char *cur_mode = vidModes[mode_idx].mode; + char *x_pos = NULL; + + if (mode_idx == 0) { + // "Custom" video mode. + return; + } + + vidWidth = strtol(cur_mode, &x_pos, 10); + cur_mode = x_pos + 1; + vidHeight = strtol(cur_mode, NULL, 10); + + if (vidFullscreen) { + wmAPI->set_closest_resolution(vidWidth, vidHeight); + } else { + if (vidMaximize) { + videoSetMaximizeWindow(false); + } + wmAPI->set_dimensions(vidWidth, vidHeight, 100, 100); + } +} + s32 videoGetTextureFilter2D(void) { return texFilter2D; @@ -215,7 +280,22 @@ void videoSetFullscreen(s32 fs) { if (fs != vidFullscreen) { vidFullscreen = !!fs; + wmAPI->set_closest_resolution(vidWidth, vidHeight); wmAPI->set_fullscreen(vidFullscreen); + if (!vidFullscreen && vidMaximize) { + wmAPI->set_maximize(false); + wmAPI->set_maximize(true); + } + } +} + +void videoSetFullscreenMode(s32 mode) +{ + vidFullscreenExclusive = mode; + wmAPI->set_fullscreen_flag(mode); + if (vidFullscreen) { + wmAPI->set_fullscreen(false); + wmAPI->set_fullscreen(true); } } @@ -287,6 +367,11 @@ void videoFreeCachedTexture(const void *texptr) gfx_texture_cache_delete(texptr); } +void videoShutdown(void) +{ + free(vidModes); +} + PD_CONSTRUCTOR static void videoConfigInit(void) { configRegisterInt("Video.DefaultFullscreen", &vidFullscreen, 0, 1); From 1be0e3680b41b726b533d939a96848b09fa8e503 Mon Sep 17 00:00:00 2001 From: Matthew Coppola Date: Thu, 21 Nov 2024 07:23:54 -0500 Subject: [PATCH 02/18] Add video option to center the window --- port/fast3d/gfx_sdl2.cpp | 35 +++++++++++++++++++++++-- port/fast3d/gfx_window_manager_api.h | 4 ++- port/include/video.h | 2 ++ port/src/optionsmenu.c | 29 ++++++++++++++++++--- port/src/video.c | 38 +++++++++++++++++++++++++--- 5 files changed, 98 insertions(+), 10 deletions(-) diff --git a/port/fast3d/gfx_sdl2.cpp b/port/fast3d/gfx_sdl2.cpp index d838532b5..e69615953 100644 --- a/port/fast3d/gfx_sdl2.cpp +++ b/port/fast3d/gfx_sdl2.cpp @@ -114,6 +114,13 @@ static void gfx_sdl_init(const struct GfxWindowInitSettings *set) { posY = 100; } + if (set->centered) { + SDL_DisplayMode mode = {}; + SDL_GetCurrentDisplayMode(0, &mode); + posX = mode.w / 2 - window_width / 2; + posY = mode.h / 2 - window_height / 2; + } + if (set->fullscreen_is_exclusive) { fullscreen_flag = SDL_WINDOW_FULLSCREEN; } @@ -235,12 +242,35 @@ static void gfx_sdl_set_cursor_visibility(bool visible) { } } -static void gfx_sdl_set_closest_resolution(int32_t width, int32_t height) { +static void get_centered_positions_native(int32_t width, int32_t height, int32_t *posX, int32_t *posY) { + const int disp_idx = SDL_GetWindowDisplayIndex(wnd); + SDL_DisplayMode mode = {}; + SDL_GetDesktopDisplayMode(disp_idx, &mode); + *posX = mode.w / 2 - width / 2; + *posY = mode.h / 2 - height / 2; +} + +static void gfx_sdl_get_centered_positions(int32_t width, int32_t height, int32_t *posX, int32_t *posY) { + const int disp_idx = SDL_GetWindowDisplayIndex(wnd); + SDL_DisplayMode mode = {}; + SDL_GetCurrentDisplayMode(disp_idx, &mode); + *posX = mode.w / 2 - width / 2; + *posY = mode.h / 2 - height / 2; +} + +static void gfx_sdl_set_closest_resolution(int32_t width, int32_t height, bool should_center) { const SDL_DisplayMode mode = {.w = width, .h = height}; + const int disp_idx = SDL_GetWindowDisplayIndex(wnd); SDL_DisplayMode closest = {}; - if (SDL_GetClosestDisplayMode(0, &mode, &closest)) { + if (SDL_GetClosestDisplayMode(disp_idx, &mode, &closest)) { SDL_SetWindowDisplayMode(wnd, &closest); SDL_SetWindowSize(wnd, closest.w, closest.h); + if (should_center) { + int32_t posX = 0; + int32_t posY = 0; + get_centered_positions_native(closest.w, closest.h, &posX, &posY); + SDL_SetWindowPosition(wnd, posX, posY); + } } } @@ -417,6 +447,7 @@ struct GfxWindowManagerAPI gfx_sdl = { gfx_sdl_set_closest_resolution, gfx_sdl_set_dimensions, gfx_sdl_get_dimensions, + gfx_sdl_get_centered_positions, gfx_sdl_handle_events, gfx_sdl_start_frame, gfx_sdl_swap_buffers_begin, diff --git a/port/fast3d/gfx_window_manager_api.h b/port/fast3d/gfx_window_manager_api.h index ba70a056b..0dc2ddd9a 100644 --- a/port/fast3d/gfx_window_manager_api.h +++ b/port/fast3d/gfx_window_manager_api.h @@ -13,6 +13,7 @@ struct GfxWindowInitSettings { bool fullscreen; bool fullscreen_is_exclusive; bool maximized; + bool centered; bool allow_hidpi; }; @@ -29,9 +30,10 @@ struct GfxWindowManagerAPI { void (*set_maximize)(bool enable); void (*get_active_window_refresh_rate)(uint32_t* refresh_rate); void (*set_cursor_visibility)(bool visible); - void (*set_closest_resolution)(int32_t width, int32_t height); + void (*set_closest_resolution)(int32_t width, int32_t height, bool should_center); void (*set_dimensions)(uint32_t width, uint32_t height, int32_t posX, int32_t posY); void (*get_dimensions)(uint32_t* width, uint32_t* height, int32_t* posX, int32_t* posY); + void (*get_centered_positions)(int32_t width, int32_t height, int32_t *posX, int32_t *posY); void (*handle_events)(void); bool (*start_frame)(void); void (*swap_buffers_begin)(void); diff --git a/port/include/video.h b/port/include/video.h index ce0bc2caf..5c22f466e 100644 --- a/port/include/video.h +++ b/port/include/video.h @@ -30,6 +30,8 @@ s32 videoGetFullscreen(void); s32 videoGetFullscreenMode(void); s32 videoGetMaximizeWindow(void); void videoSetMaximizeWindow(s32 fs); +s32 videoGetCenterWindow(void); +void videoSetCenterWindow(s32 center); u32 videoGetTextureFilter(void); s32 videoGetTextureFilter2D(void); s32 videoGetDetailTextures(void); diff --git a/port/src/optionsmenu.c b/port/src/optionsmenu.c index d9e8f75a3..12e068ea1 100644 --- a/port/src/optionsmenu.c +++ b/port/src/optionsmenu.c @@ -724,14 +724,27 @@ static MenuItemHandlerResult menuhandlerMaximizeWindow(s32 operation, struct men return 0; } +static MenuItemHandlerResult menuhandlerCenterWindow(s32 operation, struct menuitem *item, union handlerdata *data) +{ + switch (operation) { + case MENUOP_GET: + return videoGetCenterWindow(); + case MENUOP_SET: + videoSetCenterWindow(data->checkbox.value); + break; + } + + return 0; +} + static MenuItemHandlerResult menuhandlerResolution(s32 operation, struct menuitem *item, union handlerdata *data) { switch (operation) { case MENUOP_CHECKDISABLED: - if (videoGetFullscreen() && videoGetFullscreenMode() == 0) { - return true; - } - break; + if (videoGetFullscreen() && videoGetFullscreenMode() == 0) { + return true; + } + break; case MENUOP_GETOPTIONCOUNT: data->dropdown.value = videoGetNumDisplayModes(); break; @@ -899,6 +912,14 @@ struct menuitem g_ExtendedVideoMenuItems[] = { 0, menuhandlerMaximizeWindow, }, + { + MENUITEMTYPE_CHECKBOX, + 0, + MENUITEMFLAG_LITERAL_TEXT, + (uintptr_t)"Center Window", + 0, + menuhandlerCenterWindow, + }, { MENUITEMTYPE_CHECKBOX, 0, diff --git a/port/src/video.c b/port/src/video.c index 8acd6caca..238fcc386 100644 --- a/port/src/video.c +++ b/port/src/video.c @@ -36,6 +36,7 @@ static s32 vidFramebuffers = true; static s32 vidFullscreen = DEFAULT_VID_FULLSCREEN; static s32 vidFullscreenExclusive = DEFAULT_VID_FULLSCREEN_EXCLUSIVE; static s32 vidMaximize = false; +static s32 vidCenter = false; static s32 vidAllowHiDpi = false; static s32 vidVsync = 1; static s32 vidMSAA = 1; @@ -76,6 +77,7 @@ s32 videoInit(void) .fullscreen = vidFullscreen, .fullscreen_is_exclusive = vidFullscreenExclusive, .maximized = vidMaximize, + .centered = vidCenter, .allow_hidpi = vidAllowHiDpi } }; @@ -197,6 +199,11 @@ s32 videoGetMaximizeWindow(void) return vidMaximize; } +s32 videoGetCenterWindow(void) +{ + return vidCenter; +} + f32 videoGetAspect(void) { return gfx_current_dimensions.aspect_ratio; @@ -245,13 +252,20 @@ void videoSetDisplayMode(const s32 mode_idx) cur_mode = x_pos + 1; vidHeight = strtol(cur_mode, NULL, 10); + s32 posX = 100; + s32 posY = 100; + if (vidCenter) { + wmAPI->get_centered_positions(vidWidth, vidHeight, &posX, &posY); + } + if (vidFullscreen) { - wmAPI->set_closest_resolution(vidWidth, vidHeight); + wmAPI->set_closest_resolution(vidWidth, vidHeight, vidCenter); } else { if (vidMaximize) { videoSetMaximizeWindow(false); + } else { + wmAPI->set_dimensions(vidWidth, vidHeight, posX, posY); } - wmAPI->set_dimensions(vidWidth, vidHeight, 100, 100); } } @@ -280,7 +294,7 @@ void videoSetFullscreen(s32 fs) { if (fs != vidFullscreen) { vidFullscreen = !!fs; - wmAPI->set_closest_resolution(vidWidth, vidHeight); + wmAPI->set_closest_resolution(vidWidth, vidHeight, vidCenter); wmAPI->set_fullscreen(vidFullscreen); if (!vidFullscreen && vidMaximize) { wmAPI->set_maximize(false); @@ -304,6 +318,23 @@ void videoSetMaximizeWindow(s32 fs) if (fs != vidMaximize) { vidMaximize = !!fs; wmAPI->set_maximize(vidMaximize); + if (vidCenter && !vidMaximize) { + s32 posX = 0; + s32 posY = 0; + wmAPI->get_centered_positions(vidWidth, vidHeight, &posX, &posY); + wmAPI->set_dimensions(vidWidth, vidHeight, posX, posY); + } + } +} + +void videoSetCenterWindow(s32 center) +{ + vidCenter = center; + if (vidCenter && !vidMaximize) { + s32 posX = 0; + s32 posY = 0; + wmAPI->get_centered_positions(vidWidth, vidHeight, &posX, &posY); + wmAPI->set_dimensions(vidWidth, vidHeight, posX, posY); } } @@ -379,6 +410,7 @@ PD_CONSTRUCTOR static void videoConfigInit(void) configRegisterInt("Video.DefaultWidth", &vidWidth, 0, 32767); configRegisterInt("Video.DefaultHeight", &vidHeight, 0, 32767); configRegisterInt("Video.ExclusiveFullscreen", &vidFullscreenExclusive, 0, 1); + configRegisterInt("Video.CenterWindow", &vidCenter, 0, 1); configRegisterInt("Video.AllowHiDpi", &vidAllowHiDpi, 0, 1); configRegisterInt("Video.VSync", &vidVsync, -1, 10); configRegisterInt("Video.FramebufferEffects", &vidFramebuffers, 0, 1); From 5aa1cc611734818573886b1ecb8e83517082e13a Mon Sep 17 00:00:00 2001 From: Matthew Coppola Date: Sat, 23 Nov 2024 02:19:57 -0500 Subject: [PATCH 03/18] Cache ints instead of strings for video modes --- port/fast3d/gfx_modes.h | 8 --- port/fast3d/gfx_sdl2.cpp | 62 ++++++++------------ port/fast3d/gfx_window_manager_api.h | 4 +- port/include/video.h | 11 ++-- port/src/optionsmenu.c | 13 ++++- port/src/video.c | 87 +++++++++++++++++++++------- 6 files changed, 112 insertions(+), 73 deletions(-) delete mode 100644 port/fast3d/gfx_modes.h diff --git a/port/fast3d/gfx_modes.h b/port/fast3d/gfx_modes.h deleted file mode 100644 index 1fe895c41..000000000 --- a/port/fast3d/gfx_modes.h +++ /dev/null @@ -1,8 +0,0 @@ -#ifndef GFX_MODES_H -#define GFX_MODES_H - -struct GfxModes { - char mode[10]; -}; - -#endif diff --git a/port/fast3d/gfx_sdl2.cpp b/port/fast3d/gfx_sdl2.cpp index e69615953..5ddacd407 100644 --- a/port/fast3d/gfx_sdl2.cpp +++ b/port/fast3d/gfx_sdl2.cpp @@ -6,7 +6,6 @@ #include "platform.h" #include "system.h" -#include "gfx_modes.h" #include "gfx_window_manager_api.h" #include "gfx_screen_config.h" @@ -389,51 +388,39 @@ static bool gfx_sdl_set_swap_interval(int interval) { return success; } -static GfxModes *gfx_sdl_alloc_display_modes(GfxModes *modes, int *num_modes) { - static const char *alloc_failed_msg = "failed to allocate video display modes"; - const int disp_idx = SDL_GetWindowDisplayIndex(wnd); - const int display_modes = SDL_GetNumDisplayModes(disp_idx); - int modes_sz = sizeof *modes * 16; - - modes = (GfxModes *)malloc(modes_sz); - if (!modes) { - sysLogPrintf(LOG_ERROR, "malloc: %s", alloc_failed_msg); +int gfx_sdl_get_display_mode(int modenum, int *out_w, int *out_h) { + const int display_in_use = SDL_GetWindowDisplayIndex(wnd); + SDL_DisplayMode sdlmode; + if (SDL_GetDisplayMode(display_in_use, modenum, &sdlmode) == 0) { + *out_w = sdlmode.w; + *out_h = sdlmode.h; + return 1; } + return 0; +} - strncpy(modes[0].mode, "Custom", sizeof modes[0].mode); - modes[0].mode[sizeof modes[0].mode - 1] = '\0'; - - int j = 1; - for (int i = 0; i < display_modes; ++i) { - SDL_DisplayMode mode = {}; - if (SDL_GetDisplayMode(disp_idx, i, &mode) == 0) { - if (j == (int)(modes_sz / sizeof *modes)) { - modes = (GfxModes *)realloc(modes, modes_sz <<= 1); - if (!modes) { - sysLogPrintf(LOG_ERROR, "realloc: %s", alloc_failed_msg); - } - } - - snprintf(modes[j].mode, sizeof modes[0].mode, "%dx%d", mode.w, mode.h); - - if (j && strcmp(modes[j].mode, modes[j - 1].mode) != 0) { - ++j; - } - } +int gfx_sdl_get_current_display_mode(int *out_w, int *out_h) { + const int display_in_use = SDL_GetWindowDisplayIndex(wnd); + SDL_DisplayMode sdlmode; + if (SDL_GetCurrentDisplayMode(display_in_use, &sdlmode) == 0) { + *out_w = sdlmode.w; + *out_h = sdlmode.h; + return 1; } + return 0; +} - *num_modes = j; - modes_sz = *num_modes * sizeof *modes; - modes = (GfxModes *)realloc(modes, modes_sz); - if (!modes) { - sysLogPrintf(LOG_ERROR, "realloc: %s", alloc_failed_msg); - } - return modes; +int gfx_sdl_get_num_display_modes(void) { + const int display_in_use = SDL_GetWindowDisplayIndex(wnd); + return SDL_GetNumDisplayModes(display_in_use); } struct GfxWindowManagerAPI gfx_sdl = { gfx_sdl_init, gfx_sdl_close, + gfx_sdl_get_display_mode, + gfx_sdl_get_current_display_mode, + gfx_sdl_get_num_display_modes, gfx_sdl_get_fullscreen_state, gfx_sdl_set_fullscreen_changed_callback, gfx_sdl_set_fullscreen, @@ -458,5 +445,4 @@ struct GfxWindowManagerAPI gfx_sdl = { gfx_sdl_get_window_handle, gfx_sdl_set_window_title, gfx_sdl_set_swap_interval, - gfx_sdl_alloc_display_modes, }; diff --git a/port/fast3d/gfx_window_manager_api.h b/port/fast3d/gfx_window_manager_api.h index 0dc2ddd9a..4883a79cc 100644 --- a/port/fast3d/gfx_window_manager_api.h +++ b/port/fast3d/gfx_window_manager_api.h @@ -20,6 +20,9 @@ struct GfxWindowInitSettings { struct GfxWindowManagerAPI { void (*init)(const struct GfxWindowInitSettings *settings); void (*close)(void); + int (*get_display_mode)(int modenum, int *out_w, int *out_h); + int (*get_current_display_mode)(int *out_w, int *out_h); + int (*get_num_display_modes)(void); int32_t (*get_fullscreen_state)(void); void (*set_fullscreen_changed_callback)(void (*on_fullscreen_changed)(bool is_now_fullscreen)); void (*set_fullscreen)(bool enable); @@ -44,7 +47,6 @@ struct GfxWindowManagerAPI { void *(*get_window_handle)(void); void (*set_window_title)(const char *); bool (*set_swap_interval)(int); - struct GfxModes *(*alloc_display_modes)(struct GfxModes *, int *); }; #endif diff --git a/port/include/video.h b/port/include/video.h index 5c22f466e..97f9e4082 100644 --- a/port/include/video.h +++ b/port/include/video.h @@ -11,6 +11,11 @@ #define VIDEO_MAX_FPS 240 #endif +typedef struct { + s32 width; + s32 height; +} displaymode; + s32 videoInit(void); void videoStartFrame(void); void videoSubmitCommands(Gfx *cmds); @@ -36,18 +41,16 @@ u32 videoGetTextureFilter(void); s32 videoGetTextureFilter2D(void); s32 videoGetDetailTextures(void); s32 videoGetDisplayModeIndex(void); -struct GfxModes *videoGetDisplayModes(void); +s32 videoGetDisplayMode(displaymode *out, const s32 index); s32 videoGetNumDisplayModes(void); -void videoAllocDisplayModes(void); - void videoSetWindowOffset(s32 x, s32 y); void videoSetFullscreen(s32 fs); void videoSetFullscreenMode(s32 mode); void videoSetTextureFilter(u32 filter); void videoSetTextureFilter2D(s32 filter); void videoSetDetailTextures(s32 detail); -void videoSetDisplayMode(const s32 mode_idx); +void videoSetDisplayMode(const s32 index); s32 videoCreateFramebuffer(u32 w, u32 h, s32 upscale, s32 autoresize); void videoSetFramebuffer(s32 target); diff --git a/port/src/optionsmenu.c b/port/src/optionsmenu.c index 12e068ea1..b44355a75 100644 --- a/port/src/optionsmenu.c +++ b/port/src/optionsmenu.c @@ -9,7 +9,6 @@ #include "game/mainmenu.h" #include "game/menu.h" #include "game/gamefile.h" -#include "../fast3d/gfx_modes.h" #include "video.h" #include "input.h" #include "config.h" @@ -739,6 +738,10 @@ static MenuItemHandlerResult menuhandlerCenterWindow(s32 operation, struct menui static MenuItemHandlerResult menuhandlerResolution(s32 operation, struct menuitem *item, union handlerdata *data) { + static char resstring[32]; + static const char *rescustom = "Custom"; + displaymode mode; + switch (operation) { case MENUOP_CHECKDISABLED: if (videoGetFullscreen() && videoGetFullscreenMode() == 0) { @@ -749,7 +752,13 @@ static MenuItemHandlerResult menuhandlerResolution(s32 operation, struct menuite data->dropdown.value = videoGetNumDisplayModes(); break; case MENUOP_GETOPTIONTEXT: - return (intptr_t)videoGetDisplayModes()[data->dropdown.value].mode; + videoGetDisplayMode(&mode, data->dropdown.value); + if (mode.width == 0 && mode.height == 0) { + return (intptr_t)rescustom; + } else { + snprintf(resstring, sizeof(resstring), "%dx%d", mode.width, mode.height); + } + return (intptr_t)resstring; case MENUOP_SET: videoSetDisplayMode(data->dropdown.value); break; diff --git a/port/src/video.c b/port/src/video.c index 238fcc386..390ab662e 100644 --- a/port/src/video.c +++ b/port/src/video.c @@ -6,7 +6,7 @@ #include #include "platform.h" #include "config.h" -#include "../fast3d/gfx_modes.h" +#include "system.h" #include "video.h" #include "../fast3d/gfx_api.h" @@ -41,8 +41,9 @@ static s32 vidAllowHiDpi = false; static s32 vidVsync = 1; static s32 vidMSAA = 1; static s32 vidFramerateLimit = 0; -static struct GfxModes *vidModes = NULL; // Allocated in videoAllocDisplayModes(). Freed in videoShutdown(). -static s32 vidNumModes = 0; +static displaymode vidModeDefault; +static s32 vidNumModes = 1; +static displaymode *vidModes = &vidModeDefault; static s32 texFilter = FILTER_LINEAR; static s32 texFilter2D = true; @@ -53,6 +54,8 @@ static u32 frames = 0; static u32 framesPerSec = 0; static f64 startTime, endTime, fpsTime; +static s32 videoInitDisplayModes(void); + s32 videoInit(void) { wmAPI = &gfx_sdl; @@ -83,7 +86,7 @@ s32 videoInit(void) }; gfx_init(&set); - videoAllocDisplayModes(); + videoInitDisplayModes(); if (!wmAPI->set_swap_interval(vidVsync)) { vidVsync = 0; @@ -211,26 +214,72 @@ f32 videoGetAspect(void) s32 videoGetDisplayModeIndex(void) { - for (int i = 1; i < vidNumModes; ++i) { - char tmp[10] = {0}; - snprintf(tmp, sizeof tmp, "%dx%d", gfx_current_dimensions.width, gfx_current_dimensions.height); - if (strcmp(tmp, vidModes[i].mode) == 0) { + for (s32 i = 1; i < vidNumModes; ++i) { + if (vidModes[i].width == gfx_current_dimensions.width && + vidModes[i].height == gfx_current_dimensions.height) { return i; } } - // Current dimensions don't match any known mode, so return index 0, "Custom". return 0; } -void videoAllocDisplayModes(void) +static s32 videoInitDisplayModes(void) { - vidModes = wmAPI->alloc_display_modes(vidModes, &vidNumModes); + if (!wmAPI->get_current_display_mode(&vidModeDefault.width, &vidModeDefault.height)) { + vidModeDefault.width = 640; + vidModeDefault.height = 480; + return false; + } + + const s32 numBaseModes = wmAPI->get_num_display_modes(); + if (!numBaseModes) { + return false; + } + + const s32 numCustomModes = 1; + displaymode *modeList = sysMemZeroAlloc((numBaseModes + numCustomModes) * sizeof(displaymode)); + if (!modeList) { + return false; + } + + modeList[0].width = 0; + modeList[0].height = 0; + + s32 numModes = 1; + s32 w = -1, h = w, neww = w, newh = w; + + // SDL modes are guaranteed to be sorted high to low + for (s32 i = 0; i < numBaseModes; ++i) { + wmAPI->get_display_mode(i, &neww, &newh); + + if (neww != w || newh != h) { + w = neww; + h = newh; + modeList[numModes].width = w; + modeList[numModes].height = h; + ++numModes; + } + } + + modeList = sysMemRealloc(modeList, numModes * sizeof(displaymode)); + if (!modeList) { + return false; + } + + vidModes = modeList; + vidNumModes = numModes; + + return true; } -struct GfxModes *videoGetDisplayModes(void) +s32 videoGetDisplayMode(displaymode *out, const s32 index) { - return vidModes; + if (index >= 0 && index < vidNumModes) { + *out = vidModes[index]; + return true; + } + return false; } s32 videoGetNumDisplayModes(void) @@ -238,19 +287,17 @@ s32 videoGetNumDisplayModes(void) return vidNumModes; } -void videoSetDisplayMode(const s32 mode_idx) +void videoSetDisplayMode(const s32 index) { - const char *cur_mode = vidModes[mode_idx].mode; - char *x_pos = NULL; + const displaymode dm = vidModes[index]; - if (mode_idx == 0) { + if (index == 0) { // "Custom" video mode. return; } - vidWidth = strtol(cur_mode, &x_pos, 10); - cur_mode = x_pos + 1; - vidHeight = strtol(cur_mode, NULL, 10); + vidWidth = dm.width; + vidHeight = dm.height; s32 posX = 100; s32 posY = 100; From 1d14877bec682e10f4cb82e68c727ff8310af13e Mon Sep 17 00:00:00 2001 From: Matthew Coppola Date: Tue, 26 Nov 2024 05:43:41 -0500 Subject: [PATCH 04/18] Allow changing vsync and framerate limit at runtime Also implements a new menu item flag: MENUITEMFLAG_SLIDER_DEFERRED, which only calls the MENUOP_GETSLIDER callback on entering dimmed mode, storing its value for later ticks, and only calls the MENUOP_SET callback on leaving dimmed mode. This was necessary to prevent the framerate limit slider from immediately affecting the framerate, leading to an annoying slow down as the value was decremented to 0. --- port/fast3d/gfx_sdl2.cpp | 10 ++++ port/fast3d/gfx_window_manager_api.h | 2 + port/include/video.h | 4 ++ port/src/optionsmenu.c | 70 ++++++++++++++++++++++++++++ port/src/video.c | 41 ++++++++++++---- src/game/menuitem.c | 44 +++++++++++++---- src/include/constants.h | 3 +- 7 files changed, 155 insertions(+), 19 deletions(-) diff --git a/port/fast3d/gfx_sdl2.cpp b/port/fast3d/gfx_sdl2.cpp index 5ddacd407..c1ec8ce6e 100644 --- a/port/fast3d/gfx_sdl2.cpp +++ b/port/fast3d/gfx_sdl2.cpp @@ -363,6 +363,10 @@ static double gfx_sdl_get_time(void) { return SDL_GetPerformanceCounter() / (double)qpc_freq; } +static int32_t gfx_sdl_get_target_fps(void) { + return target_fps; +} + static void gfx_sdl_set_target_fps(int fps) { target_fps = fps; } @@ -379,6 +383,10 @@ static void gfx_sdl_set_window_title(const char *title) { SDL_SetWindowTitle(wnd, title); } +static int gfx_sdl_get_swap_interval(void) { + return SDL_GL_GetSwapInterval(); +} + static bool gfx_sdl_set_swap_interval(int interval) { const bool success = SDL_GL_SetSwapInterval(interval) >= 0; vsync_enabled = success && (interval != 0); @@ -440,9 +448,11 @@ struct GfxWindowManagerAPI gfx_sdl = { gfx_sdl_swap_buffers_begin, gfx_sdl_swap_buffers_end, gfx_sdl_get_time, + gfx_sdl_get_target_fps, gfx_sdl_set_target_fps, gfx_sdl_can_disable_vsync, gfx_sdl_get_window_handle, gfx_sdl_set_window_title, + gfx_sdl_get_swap_interval, gfx_sdl_set_swap_interval, }; diff --git a/port/fast3d/gfx_window_manager_api.h b/port/fast3d/gfx_window_manager_api.h index 4883a79cc..d10fc45cf 100644 --- a/port/fast3d/gfx_window_manager_api.h +++ b/port/fast3d/gfx_window_manager_api.h @@ -42,10 +42,12 @@ struct GfxWindowManagerAPI { void (*swap_buffers_begin)(void); void (*swap_buffers_end)(void); double (*get_time)(void); // For debug + int32_t (*get_target_fps)(void); void (*set_target_fps)(int fps); bool (*can_disable_vsync)(void); void *(*get_window_handle)(void); void (*set_window_title)(const char *); + int (*get_swap_interval)(void); bool (*set_swap_interval)(int); }; diff --git a/port/include/video.h b/port/include/video.h index 97f9e4082..c99655110 100644 --- a/port/include/video.h +++ b/port/include/video.h @@ -43,6 +43,8 @@ s32 videoGetDetailTextures(void); s32 videoGetDisplayModeIndex(void); s32 videoGetDisplayMode(displaymode *out, const s32 index); s32 videoGetNumDisplayModes(void); +s32 videoGetVsync(void); +s32 videoGetFramerateLimit(void); void videoSetWindowOffset(s32 x, s32 y); void videoSetFullscreen(s32 fs); @@ -51,6 +53,8 @@ void videoSetTextureFilter(u32 filter); void videoSetTextureFilter2D(s32 filter); void videoSetDetailTextures(s32 detail); void videoSetDisplayMode(const s32 index); +void videoSetVsync(const s32 vsync); +void videoSetFramerateLimit(const s32 limit); s32 videoCreateFramebuffer(u32 w, u32 h, s32 upscale, s32 autoresize); void videoSetFramebuffer(s32 target); diff --git a/port/src/optionsmenu.c b/port/src/optionsmenu.c index b44355a75..022e1e802 100644 --- a/port/src/optionsmenu.c +++ b/port/src/optionsmenu.c @@ -736,6 +736,60 @@ static MenuItemHandlerResult menuhandlerCenterWindow(s32 operation, struct menui return 0; } +static MenuItemHandlerResult menuhandlerVsync(s32 operation, struct menuitem *item, union handlerdata *data) +{ + static const char *opts[] = { + "Adaptive", + "Off", + "On (Sync Every Frame)", + "On (Sync Every 2 Frames)", + "On (Sync Every 3 Frames)", + "On (Sync Every 4 Frames)", + "On (Sync Every 5 Frames)", + "On (Sync Every 6 Frames)", + "On (Sync Every 7 Frames)", + "On (Sync Every 8 Frames)", + "On (Sync Every 9 Frames)", + "On (Sync Every 10 Frames)" + }; + + switch (operation) { + case MENUOP_GETOPTIONCOUNT: + data->dropdown.value = ARRAYCOUNT(opts); + break; + case MENUOP_GETOPTIONTEXT: + return (intptr_t)opts[data->dropdown.value]; + case MENUOP_SET: + videoSetVsync(data->dropdown.value - 1); + case MENUOP_GETSELECTEDINDEX: + data->dropdown.value = videoGetVsync() + 1; + } + + return 0; +} + +static MenuItemHandlerResult menuhandlerFramerateLimit(s32 operation, struct menuitem *item, union handlerdata *data) +{ + switch (operation) { + case MENUOP_GETSLIDER: + data->slider.value = videoGetFramerateLimit(); + break; + case MENUOP_SET: + if (g_TickRateDiv < 2) { + g_TickRateDiv = (data->slider.value == 0 || data->slider.value > 60) ? 0 : 1; + } + videoSetFramerateLimit(data->slider.value); + break; + case MENUOP_GETSLIDERLABEL: + if (data->slider.value == 0) { + sprintf(data->slider.label, "Off"); + } + break; + } + + return 0; +} + static MenuItemHandlerResult menuhandlerResolution(s32 operation, struct menuitem *item, union handlerdata *data) { static char resstring[32]; @@ -929,6 +983,22 @@ struct menuitem g_ExtendedVideoMenuItems[] = { 0, menuhandlerCenterWindow, }, + { + MENUITEMTYPE_DROPDOWN, + 0, + MENUITEMFLAG_LITERAL_TEXT, + (uintptr_t)"Vsync", + 0, + menuhandlerVsync, + }, + { + MENUITEMTYPE_SLIDER, + 0, + MENUITEMFLAG_LITERAL_TEXT | MENUITEMFLAG_SLIDER_WIDE | MENUITEMFLAG_SLIDER_DEFERRED, + (uintptr_t)"Framerate Limit", + VIDEO_MAX_FPS, + menuhandlerFramerateLimit, + }, { MENUITEMTYPE_CHECKBOX, 0, diff --git a/port/src/video.c b/port/src/video.c index 390ab662e..cd1f6288d 100644 --- a/port/src/video.c +++ b/port/src/video.c @@ -86,18 +86,11 @@ s32 videoInit(void) }; gfx_init(&set); + videoInitDisplayModes(); + videoSetVsync(vidVsync); + videoSetFramerateLimit(vidFramerateLimit); - if (!wmAPI->set_swap_interval(vidVsync)) { - vidVsync = 0; - } - - if (vidVsync == 0 && vidFramerateLimit == 0) { - // cap FPS if there's no vsync to prevent the game from exploding - vidFramerateLimit = VIDEO_MAX_FPS; - } - - wmAPI->set_target_fps(vidFramerateLimit); // disabled because vsync is on gfx_set_texture_filter((enum FilteringMode)texFilter); initDone = true; @@ -224,6 +217,18 @@ s32 videoGetDisplayModeIndex(void) return 0; } +s32 videoGetVsync(void) +{ + vidVsync = wmAPI->get_swap_interval(); + return vidVsync; +} + +s32 videoGetFramerateLimit(void) +{ + vidFramerateLimit = wmAPI->get_target_fps(); + return vidFramerateLimit; +} + static s32 videoInitDisplayModes(void) { if (!wmAPI->get_current_display_mode(&vidModeDefault.width, &vidModeDefault.height)) { @@ -409,6 +414,22 @@ s32 videoCreateFramebuffer(u32 w, u32 h, s32 upscale, s32 autoresize) return gfx_create_framebuffer(w, h, upscale, autoresize); } +void videoSetVsync(const s32 vsync) +{ + vidVsync = wmAPI->set_swap_interval(vsync) ? vsync : 0; + + if (vidVsync == 0 && vidFramerateLimit == 0) { + // cap FPS if there's no vsync to prevent the game from exploding + videoSetFramerateLimit(VIDEO_MAX_FPS); + } +} + +void videoSetFramerateLimit(const s32 limit) +{ + vidFramerateLimit = (vidVsync == 0 && limit == 0) ? VIDEO_MAX_FPS : limit; + wmAPI->set_target_fps(vidFramerateLimit); +} + void videoSetFramebuffer(s32 target) { return gfx_set_framebuffer(target, 1.f); diff --git a/src/game/menuitem.c b/src/game/menuitem.c index c95acb89e..9a3625030 100644 --- a/src/game/menuitem.c +++ b/src/game/menuitem.c @@ -44,6 +44,8 @@ u8 g_KeyboardKeys[5][10] = { { '1','2','1','2','1','2','3','1','2','3' }, }; +static s32 deferredindex = -1; + s32 func0f0e5ce0(s32 value) { if (value < var800711a4) { @@ -2171,7 +2173,7 @@ Gfx* menuitemColorBoxRender(Gfx *gdl, struct menurendercontext *context) return gdl; } -#endif +#endif Gfx *menuitemSelectableRender(Gfx *gdl, struct menurendercontext *context) { @@ -2332,8 +2334,15 @@ Gfx *menuitemSliderRender(Gfx *gdl, struct menurendercontext *context) extray = 0; if (context->item->handler != NULL) { - context->item->handler(MENUOP_GETSLIDER, context->item, &data); - slidervalue = (s16) data.slider.value; + if ((context->item->flags & MENUITEMFLAG_SLIDER_DEFERRED) && + deferredindex != -1 && + context->dialog->dimmed && + context->focused) { + slidervalue = (s16) deferredindex; + } else { + context->item->handler(MENUOP_GETSLIDER, context->item, &data); + slidervalue = (s16) data.slider.value; + } } else { slidervalue = 0; } @@ -2484,8 +2493,17 @@ bool menuitemSliderTick(struct menuitem *item, struct menudialog *dialog, struct if (tickflags & MENUTICKFLAG_DIALOGISDIMMED) { if (item->handler) { - item->handler(MENUOP_GETSLIDER, item, &handlerdata); - index = (s16) handlerdata.slider.value; + if (item->flags & MENUITEMFLAG_SLIDER_DEFERRED) { + if (deferredindex == -1) { + item->handler(MENUOP_GETSLIDER, item, &handlerdata); + deferredindex = (s16) handlerdata.slider.value; + } + index = deferredindex; + } else { + item->handler(MENUOP_GETSLIDER, item, &handlerdata); + index = (s16) handlerdata.slider.value; + } + } else { index = 0; } @@ -2565,11 +2583,21 @@ bool menuitemSliderTick(struct menuitem *item, struct menudialog *dialog, struct inputs->leftright = 0; handlerdata.slider.value = index; - if (item->handler) { - item->handler(MENUOP_SET, item, &handlerdata); + if (item->flags & MENUITEMFLAG_SLIDER_DEFERRED) { + deferredindex = index; + } else { + if (item->handler) { + item->handler(MENUOP_SET, item, &handlerdata); + } } if (inputs->select) { + if (item->flags & MENUITEMFLAG_SLIDER_DEFERRED) { + deferredindex = -1; + if (item->handler) { + item->handler(MENUOP_SET, item, &handlerdata); + } + } dialog->dimmed = false; } } else { @@ -2609,7 +2637,7 @@ Gfx *menuitemCarouselRender(Gfx *gdl, struct menurendercontext *context) #ifdef PLATFORM_N64 s16 chevronOffset = 0; -#else +#else s16 chevronOffset = 3; #endif diff --git a/src/include/constants.h b/src/include/constants.h index 777fbed56..110e88f5b 100644 --- a/src/include/constants.h +++ b/src/include/constants.h @@ -1666,6 +1666,7 @@ #define MENUITEMFLAG_CAROUSEL_04000000 0x04000000 #define MENUITEMFLAG_LITERAL_TEXT 0x08000000 #define MENUITEMFLAG_SLIDER_WIDE 0x10000000 +#define MENUITEMFLAG_SLIDER_DEFERRED 0x20000000 #define MENUITEMTYPE_LABEL 0x01 #define MENUITEMTYPE_LIST 0x02 @@ -1696,7 +1697,7 @@ #ifndef PLATFORM_N64 #define MENUITEMTYPE_COLORBOX 0x1b -#endif +#endif #define MENUMODELFLAG_HASSCALE 0x01 #define MENUMODELFLAG_HASPOSITION 0x02 From d43c4c0cbad3c821494f65b2ae9aaf1468aabae6 Mon Sep 17 00:00:00 2001 From: Matthew Coppola Date: Wed, 27 Nov 2024 04:35:21 -0500 Subject: [PATCH 05/18] Allow changing MSAA at runtime --- port/include/video.h | 2 ++ port/src/optionsmenu.c | 46 ++++++++++++++++++++++++++++++++++++++++++ port/src/video.c | 12 +++++++++++ 3 files changed, 60 insertions(+) diff --git a/port/include/video.h b/port/include/video.h index c99655110..0e4b242e2 100644 --- a/port/include/video.h +++ b/port/include/video.h @@ -45,6 +45,7 @@ s32 videoGetDisplayMode(displaymode *out, const s32 index); s32 videoGetNumDisplayModes(void); s32 videoGetVsync(void); s32 videoGetFramerateLimit(void); +s32 videoGetMSAA(void); void videoSetWindowOffset(s32 x, s32 y); void videoSetFullscreen(s32 fs); @@ -55,6 +56,7 @@ void videoSetDetailTextures(s32 detail); void videoSetDisplayMode(const s32 index); void videoSetVsync(const s32 vsync); void videoSetFramerateLimit(const s32 limit); +void videoSetMSAA(const s32 msaa); s32 videoCreateFramebuffer(u32 w, u32 h, s32 upscale, s32 autoresize); void videoSetFramebuffer(s32 target); diff --git a/port/src/optionsmenu.c b/port/src/optionsmenu.c index 022e1e802..dfc12c97b 100644 --- a/port/src/optionsmenu.c +++ b/port/src/optionsmenu.c @@ -790,6 +790,44 @@ static MenuItemHandlerResult menuhandlerFramerateLimit(s32 operation, struct men return 0; } +static MenuItemHandlerResult menuhandlerMSAA(s32 operation, struct menuitem *item, union handlerdata *data) +{ + s32 msaa; + static const char *opts[] = { + "Off", + "2x", + "4x", + "8x", + "16x" + }; + + switch (operation) { + case MENUOP_GETOPTIONCOUNT: + data->dropdown.value = ARRAYCOUNT(opts); + break; + case MENUOP_GETOPTIONTEXT: + return (intptr_t)opts[data->dropdown.value]; + case MENUOP_SET: + videoSetMSAA(1 << data->dropdown.value); + break; + case MENUOP_GETSELECTEDINDEX: + msaa = videoGetMSAA(); + if (msaa < 2) { + data->dropdown.value = 0; + } else if (msaa < 4) { + data->dropdown.value = 1; + } else if (msaa < 8) { + data->dropdown.value = 2; + } else if (msaa < 16) { + data->dropdown.value = 3; + } else { + data->dropdown.value = 4; + } + } + + return 0; +} + static MenuItemHandlerResult menuhandlerResolution(s32 operation, struct menuitem *item, union handlerdata *data) { static char resstring[32]; @@ -999,6 +1037,14 @@ struct menuitem g_ExtendedVideoMenuItems[] = { VIDEO_MAX_FPS, menuhandlerFramerateLimit, }, + { + MENUITEMTYPE_DROPDOWN, + 0, + MENUITEMFLAG_LITERAL_TEXT, + (uintptr_t)"Anti-aliasing (MSAA)", + 0, + menuhandlerMSAA, + }, { MENUITEMTYPE_CHECKBOX, 0, diff --git a/port/src/video.c b/port/src/video.c index cd1f6288d..0a84df49b 100644 --- a/port/src/video.c +++ b/port/src/video.c @@ -217,6 +217,12 @@ s32 videoGetDisplayModeIndex(void) return 0; } +s32 videoGetMSAA(void) +{ + vidMSAA = (s32)gfx_msaa_level; + return vidMSAA; +} + s32 videoGetVsync(void) { vidVsync = wmAPI->get_swap_interval(); @@ -414,6 +420,12 @@ s32 videoCreateFramebuffer(u32 w, u32 h, s32 upscale, s32 autoresize) return gfx_create_framebuffer(w, h, upscale, autoresize); } +void videoSetMSAA(const s32 msaa) +{ + vidMSAA = msaa; + gfx_msaa_level = (u32)vidMSAA; +} + void videoSetVsync(const s32 vsync) { vidVsync = wmAPI->set_swap_interval(vsync) ? vsync : 0; From 6de98cfcd45687375429dc745450ebc8ce139d29 Mon Sep 17 00:00:00 2001 From: Matthew Coppola Date: Wed, 27 Nov 2024 11:53:40 -0500 Subject: [PATCH 06/18] Update GUI texture filtering setting immediately --- port/src/optionsmenu.c | 1 + 1 file changed, 1 insertion(+) diff --git a/port/src/optionsmenu.c b/port/src/optionsmenu.c index dfc12c97b..9e23290af 100644 --- a/port/src/optionsmenu.c +++ b/port/src/optionsmenu.c @@ -905,6 +905,7 @@ static MenuItemHandlerResult menuhandlerTexFilter2D(s32 operation, struct menuit return videoGetTextureFilter2D(); case MENUOP_SET: videoSetTextureFilter2D(data->checkbox.value); + g_TexFilter2D = videoGetTextureFilter2D() ? G_TF_BILERP : G_TF_POINT; break; } From 210b299bd40719cfd88055df6693141f8a45ad31 Mon Sep 17 00:00:00 2001 From: Matthew Coppola Date: Sat, 30 Nov 2024 11:58:14 -0500 Subject: [PATCH 07/18] Add Display FPS options to Extended Video Options Also changed how the window title FPS works, which is now derived from the difference in time between subsequent video frames. --- port/include/video.h | 5 ++++ port/src/optionsmenu.c | 67 ++++++++++++++++++++++++++++++++++++++++++ port/src/video.c | 49 ++++++++++++++++++++++++------ src/game/lv.c | 37 +++++++++++++++++++++++ 4 files changed, 149 insertions(+), 9 deletions(-) diff --git a/port/include/video.h b/port/include/video.h index 0e4b242e2..0c468dcbb 100644 --- a/port/include/video.h +++ b/port/include/video.h @@ -21,6 +21,7 @@ void videoStartFrame(void); void videoSubmitCommands(Gfx *cmds); void videoClearScreen(void); void videoEndFrame(void); +f64 videoGetDiffTime(void); void *videoGetWindowHandle(void); @@ -45,6 +46,8 @@ s32 videoGetDisplayMode(displaymode *out, const s32 index); s32 videoGetNumDisplayModes(void); s32 videoGetVsync(void); s32 videoGetFramerateLimit(void); +s32 videoGetDisplayFPS(void); +s32 videoGetDisplayFPSDivisor(void); s32 videoGetMSAA(void); void videoSetWindowOffset(s32 x, s32 y); @@ -56,6 +59,8 @@ void videoSetDetailTextures(s32 detail); void videoSetDisplayMode(const s32 index); void videoSetVsync(const s32 vsync); void videoSetFramerateLimit(const s32 limit); +void videoSetDisplayFPS(const s32 displayfps); +void videoSetDisplayFPSDivisor(const s32 divisor); void videoSetMSAA(const s32 msaa); s32 videoCreateFramebuffer(u32 w, u32 h, s32 upscale, s32 autoresize); diff --git a/port/src/optionsmenu.c b/port/src/optionsmenu.c index 9e23290af..5835ee921 100644 --- a/port/src/optionsmenu.c +++ b/port/src/optionsmenu.c @@ -912,6 +912,57 @@ static MenuItemHandlerResult menuhandlerTexFilter2D(s32 operation, struct menuit return 0; } +static MenuItemHandlerResult menuhandlerDisplayFPS(s32 operation, struct menuitem *item, union handlerdata *data) +{ + switch (operation) { + case MENUOP_GET: + return videoGetDisplayFPS(); + case MENUOP_SET: + videoSetDisplayFPS(data->checkbox.value); + break; + } + + return 0; +} + +static MenuItemHandlerResult menuhandlerDisplayFPSInterval(s32 operation, struct menuitem *item, union handlerdata *data) +{ + s32 divisor; + static const char *opts[] = { + "1 Sec", + "1/2 Secs", + "1/4 Secs", + "1/8 Secs", + "1/16 Secs", + }; + + switch (operation) { + case MENUOP_GETOPTIONCOUNT: + data->dropdown.value = ARRAYCOUNT(opts); + break; + case MENUOP_GETOPTIONTEXT: + return (intptr_t)opts[data->dropdown.value]; + case MENUOP_SET: + videoSetDisplayFPSDivisor(1 << data->dropdown.value); + break; + case MENUOP_GETSELECTEDINDEX: + divisor = videoGetDisplayFPSDivisor(); + if (divisor < 2) { + data->dropdown.value = 0; + } else if (divisor < 4) { + data->dropdown.value = 1; + } else if (divisor < 8) { + data->dropdown.value = 2; + } else if (divisor < 16) { + data->dropdown.value = 3; + } else { + data->dropdown.value = 4; + } + } + + return 0; +} + static MenuItemHandlerResult menuhandlerGeMuzzleFlashes(s32 operation, struct menuitem *item, union handlerdata *data) { switch (operation) { @@ -1038,6 +1089,22 @@ struct menuitem g_ExtendedVideoMenuItems[] = { VIDEO_MAX_FPS, menuhandlerFramerateLimit, }, + { + MENUITEMTYPE_CHECKBOX, + 0, + MENUITEMFLAG_LITERAL_TEXT, + (uintptr_t)"Display FPS", + 0, + menuhandlerDisplayFPS, + }, + { + MENUITEMTYPE_DROPDOWN, + 0, + MENUITEMFLAG_LITERAL_TEXT, + (uintptr_t)"Display FPS Interval", + 0, + menuhandlerDisplayFPSInterval, + }, { MENUITEMTYPE_DROPDOWN, 0, diff --git a/port/src/video.c b/port/src/video.c index 0a84df49b..726096de4 100644 --- a/port/src/video.c +++ b/port/src/video.c @@ -41,9 +41,12 @@ static s32 vidAllowHiDpi = false; static s32 vidVsync = 1; static s32 vidMSAA = 1; static s32 vidFramerateLimit = 0; +static s32 vidDisplayFPS = 0; +static s32 vidDisplayFPSDivisor = 8; static displaymode vidModeDefault; static s32 vidNumModes = 1; static displaymode *vidModes = &vidModeDefault; +static f64 vidDiffTime; static s32 texFilter = FILTER_LINEAR; static s32 texFilter2D = true; @@ -51,8 +54,7 @@ static s32 texDetail = true; static u32 dlcount = 0; static u32 frames = 0; -static u32 framesPerSec = 0; -static f64 startTime, endTime, fpsTime; +static f64 startTime, endTime; static s32 videoInitDisplayModes(void); @@ -115,26 +117,33 @@ void videoSubmitCommands(Gfx *cmds) void videoEndFrame(void) { + static f64 accumTime = 0.0; + if (!initDone) { return; } gfx_end_frame(); - endTime = wmAPI->get_time(); - ++frames; - ++framesPerSec; - if (endTime >= fpsTime) { + endTime = wmAPI->get_time(); + accumTime += endTime - startTime; + + if (accumTime >= 1.0 / (f64)vidDisplayFPSDivisor) { char tmp[128]; - snprintf(tmp, sizeof(tmp), "fps %3u frt %lf frm %u", framesPerSec, endTime - startTime, frames); + vidDiffTime = endTime - startTime; + accumTime = 0.0; + snprintf(tmp, sizeof(tmp), "fps %3u frt %lf frm %u", (u32)(1.0 / vidDiffTime), vidDiffTime, frames); wmAPI->set_window_title(tmp); - framesPerSec = 0; - fpsTime = endTime + 1.0; } } +f64 videoGetDiffTime(void) +{ + return vidDiffTime; +} + void videoClearScreen(void) { videoStartFrame(); @@ -235,6 +244,16 @@ s32 videoGetFramerateLimit(void) return vidFramerateLimit; } +s32 videoGetDisplayFPS(void) +{ + return vidDisplayFPS; +} + +s32 videoGetDisplayFPSDivisor(void) +{ + return vidDisplayFPSDivisor; +} + static s32 videoInitDisplayModes(void) { if (!wmAPI->get_current_display_mode(&vidModeDefault.width, &vidModeDefault.height)) { @@ -442,6 +461,16 @@ void videoSetFramerateLimit(const s32 limit) wmAPI->set_target_fps(vidFramerateLimit); } +void videoSetDisplayFPS(const s32 displayfps) +{ + vidDisplayFPS = displayfps; +} + +void videoSetDisplayFPSDivisor(const s32 divisor) +{ + vidDisplayFPSDivisor = divisor; +} + void videoSetFramebuffer(s32 target) { return gfx_set_framebuffer(target, 1.f); @@ -495,6 +524,8 @@ PD_CONSTRUCTOR static void videoConfigInit(void) configRegisterInt("Video.VSync", &vidVsync, -1, 10); configRegisterInt("Video.FramebufferEffects", &vidFramebuffers, 0, 1); configRegisterInt("Video.FramerateLimit", &vidFramerateLimit, 0, VIDEO_MAX_FPS); + configRegisterInt("Video.DisplayFPS", &vidDisplayFPS, 0, 1); + configRegisterInt("Video.DisplayFPSDivisor", &vidDisplayFPSDivisor, 1, 16); configRegisterInt("Video.MSAA", &vidMSAA, 1, 16); configRegisterInt("Video.TextureFilter", &texFilter, 0, 2); configRegisterInt("Video.TextureFilter2D", &texFilter2D, 0, 1); diff --git a/src/game/lv.c b/src/game/lv.c index 22f3993a8..06bff3b84 100644 --- a/src/game/lv.c +++ b/src/game/lv.c @@ -96,6 +96,9 @@ #include "lib/vars.h" #include "lib/vi.h" #include "types.h" +#ifndef PLATFORM_N64 +#include "video.h" +#endif struct sndstate *g_MiscSfxAudioHandles[3]; u32 var800aa5bc; @@ -951,6 +954,34 @@ void lvFindThreats(void) } } +#ifndef PLATFORM_N64 +Gfx *lvRenderFPS(Gfx *gdl) +{ + s32 x = 27, y = 13; + f32 fps = 1.0f / videoGetDiffTime(); + s32 ifps = (s32)fps; + u8 r = (ifps <= 30) ? 255 : (ifps <= 60) ? roundf(255 * (1.0f - (ifps - 30.0f) / 30.0f)) : 0; + u8 g = (ifps <= 30) ? roundf(255 * (ifps - 1.0f) / 29.0f) : 255; + u8 b = (ifps > 60 && ifps <= 90) ? roundf(255 * (ifps - 60.0f) / 30.0f) : (ifps > 90) ? 255 : 0; + u8 a = 160; + u32 color = r << 24 | g << 16 | b << 8 | a; + char buffer[16]; + + if (g_CharsNumeric && g_FontNumeric) { + snprintf(buffer, sizeof buffer, "%.2f", fps); + + gSPSetExtraGeometryModeEXT(gdl++, g_HudAlignModeL); + + gdl = text0f153628(gdl); + gdl = textRender(gdl, &x, &y, buffer, g_CharsNumeric, g_FontNumeric, color, 0x000000a0, viGetWidth(), viGetHeight(), 0, 0); + + gSPClearExtraGeometryModeEXT(gdl++, g_HudAlignModeL); + } + + return gdl; +} +#endif + /** * Renders a complete frame for all players, and also does some other game logic * that really doesn't belong here. @@ -1745,6 +1776,12 @@ Gfx *lvRender(Gfx *gdl) gDPSetScissor(gdl++, G_SC_NON_INTERLACE, 0, 0, viGetWidth(), viGetHeight()); +#ifndef PLATFORM_N64 + if (videoGetDisplayFPS()) { + gdl = lvRenderFPS(gdl); + } +#endif + #if VERSION < VERSION_NTSC_1_0 if ((uintptr_t)gdl < (uintptr_t)g_GfxBuffers[g_GfxActiveBufferIndex] || (uintptr_t)gdl > (uintptr_t)g_GfxBuffers[g_GfxActiveBufferIndex + 1]) { From 1e2f6dcb91131eb0da4271d0eb08e8e7ad20b9a3 Mon Sep 17 00:00:00 2001 From: Matthew Coppola Date: Sat, 30 Nov 2024 13:58:33 -0500 Subject: [PATCH 08/18] Fix mouse input not updating deferred slider while in dimmed mode --- src/game/menuitem.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/game/menuitem.c b/src/game/menuitem.c index 9a3625030..59eec1160 100644 --- a/src/game/menuitem.c +++ b/src/game/menuitem.c @@ -2481,9 +2481,14 @@ bool menuitemSliderTick(struct menuitem *item, struct menudialog *dialog, struct index = item->param3; } if (item->handler) { - item->handler(MENUOP_GET, item, &handlerdata); - handlerdata.slider.value = index; - item->handler(MENUOP_SET, item, &handlerdata); + if ((item->flags & MENUITEMFLAG_SLIDER_DEFERRED) && + (tickflags & MENUTICKFLAG_DIALOGISDIMMED)) { + deferredindex = index; + } else { + item->handler(MENUOP_GET, item, &handlerdata); + handlerdata.slider.value = index; + item->handler(MENUOP_SET, item, &handlerdata); + } } return true; } From b34bb012409293f1f6f7552ed85a2a5701c9d9a8 Mon Sep 17 00:00:00 2001 From: Matthew Coppola Date: Sat, 30 Nov 2024 17:30:59 -0500 Subject: [PATCH 09/18] Actually sync fullscreen and maximized states with the backend --- port/src/video.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/port/src/video.c b/port/src/video.c index 726096de4..9a121421d 100644 --- a/port/src/video.c +++ b/port/src/video.c @@ -105,6 +105,10 @@ void videoStartFrame(void) startTime = wmAPI->get_time(); gfx_start_frame(); } + + // Synchronize with their backend counterparts. + vidFullscreen = videoGetFullscreen(); + vidMaximize = videoGetMaximizeWindow(); } void videoSubmitCommands(Gfx *cmds) From 0b8a333e2b8a3350dca22cc31e4108caba236dfa Mon Sep 17 00:00:00 2001 From: Matthew Coppola Date: Sat, 30 Nov 2024 19:45:30 -0500 Subject: [PATCH 10/18] Rearrange the menu, clean up, and change some things --- port/src/optionsmenu.c | 148 ++++++++++++++++++++++------------------- port/src/video.c | 4 +- 2 files changed, 80 insertions(+), 72 deletions(-) diff --git a/port/src/optionsmenu.c b/port/src/optionsmenu.c index 5835ee921..e26c8efcd 100644 --- a/port/src/optionsmenu.c +++ b/port/src/optionsmenu.c @@ -741,16 +741,16 @@ static MenuItemHandlerResult menuhandlerVsync(s32 operation, struct menuitem *it static const char *opts[] = { "Adaptive", "Off", - "On (Sync Every Frame)", - "On (Sync Every 2 Frames)", - "On (Sync Every 3 Frames)", - "On (Sync Every 4 Frames)", - "On (Sync Every 5 Frames)", - "On (Sync Every 6 Frames)", - "On (Sync Every 7 Frames)", - "On (Sync Every 8 Frames)", - "On (Sync Every 9 Frames)", - "On (Sync Every 10 Frames)" + "On (Every Frame)", + "On (Every 2 Frames)", + "On (Every 3 Frames)", + "On (Every 4 Frames)", + "On (Every 5 Frames)", + "On (Every 6 Frames)", + "On (Every 7 Frames)", + "On (Every 8 Frames)", + "On (Every 9 Frames)", + "On (Every 10 Frames)" }; switch (operation) { @@ -781,10 +781,12 @@ static MenuItemHandlerResult menuhandlerFramerateLimit(s32 operation, struct men videoSetFramerateLimit(data->slider.value); break; case MENUOP_GETSLIDERLABEL: + // NOTE: data->slider.label length must not exceed 15. if (data->slider.value == 0) { - sprintf(data->slider.label, "Off"); + strcpy(data->slider.label, "Off"); + } else { + sprintf(data->slider.label, "%d FPS", data->slider.value); } - break; } return 0; @@ -795,10 +797,10 @@ static MenuItemHandlerResult menuhandlerMSAA(s32 operation, struct menuitem *ite s32 msaa; static const char *opts[] = { "Off", - "2x", - "4x", - "8x", - "16x" + "2x (MSAA)", + "4x (MSAA)", + "8x (MSAA)", + "16x (MSAA)" }; switch (operation) { @@ -919,7 +921,6 @@ static MenuItemHandlerResult menuhandlerDisplayFPS(s32 operation, struct menuite return videoGetDisplayFPS(); case MENUOP_SET: videoSetDisplayFPS(data->checkbox.value); - break; } return 0; @@ -927,36 +928,19 @@ static MenuItemHandlerResult menuhandlerDisplayFPS(s32 operation, struct menuite static MenuItemHandlerResult menuhandlerDisplayFPSInterval(s32 operation, struct menuitem *item, union handlerdata *data) { - s32 divisor; - static const char *opts[] = { - "1 Sec", - "1/2 Secs", - "1/4 Secs", - "1/8 Secs", - "1/16 Secs", - }; - switch (operation) { - case MENUOP_GETOPTIONCOUNT: - data->dropdown.value = ARRAYCOUNT(opts); + case MENUOP_GETSLIDER: + data->slider.value = videoGetDisplayFPSDivisor() - 1; break; - case MENUOP_GETOPTIONTEXT: - return (intptr_t)opts[data->dropdown.value]; case MENUOP_SET: - videoSetDisplayFPSDivisor(1 << data->dropdown.value); + videoSetDisplayFPSDivisor(data->dropdown.value + 1); break; - case MENUOP_GETSELECTEDINDEX: - divisor = videoGetDisplayFPSDivisor(); - if (divisor < 2) { - data->dropdown.value = 0; - } else if (divisor < 4) { - data->dropdown.value = 1; - } else if (divisor < 8) { - data->dropdown.value = 2; - } else if (divisor < 16) { - data->dropdown.value = 3; + case MENUOP_GETSLIDERLABEL: + // NOTE: data->slider.label length must not exceed 15. + if (data->slider.value == 0) { + strcpy(data->slider.label, "1 Sec"); } else { - data->dropdown.value = 4; + sprintf(data->slider.label, "1/%d Secs", data->slider.value + 1); } } @@ -1049,14 +1033,6 @@ struct menuitem g_ExtendedVideoMenuItems[] = { 0, menuhandlerResolution, }, - { - MENUITEMTYPE_DROPDOWN, - 0, - MENUITEMFLAG_LITERAL_TEXT, - (uintptr_t)"Texture Filtering", - 0, - menuhandlerTexFilter, - }, { MENUITEMTYPE_CHECKBOX, 0, @@ -1073,6 +1049,22 @@ struct menuitem g_ExtendedVideoMenuItems[] = { 0, menuhandlerCenterWindow, }, + { + MENUITEMTYPE_DROPDOWN, + 0, + MENUITEMFLAG_LITERAL_TEXT, + (uintptr_t)"HUD Centering", + 0, + menuhandlerCenterHUD, + }, + { + MENUITEMTYPE_SEPARATOR, + 0, + 0, + 0, + 0, + NULL, + }, { MENUITEMTYPE_DROPDOWN, 0, @@ -1098,21 +1090,53 @@ struct menuitem g_ExtendedVideoMenuItems[] = { menuhandlerDisplayFPS, }, { - MENUITEMTYPE_DROPDOWN, + MENUITEMTYPE_SLIDER, 0, - MENUITEMFLAG_LITERAL_TEXT, + MENUITEMFLAG_LITERAL_TEXT | MENUITEMFLAG_SLIDER_WIDE, (uintptr_t)"Display FPS Interval", - 0, + 31, menuhandlerDisplayFPSInterval, }, + { + MENUITEMTYPE_SEPARATOR, + 0, + 0, + 0, + 0, + NULL, + }, { MENUITEMTYPE_DROPDOWN, 0, MENUITEMFLAG_LITERAL_TEXT, - (uintptr_t)"Anti-aliasing (MSAA)", + (uintptr_t)"Anti-aliasing", 0, menuhandlerMSAA, }, + { + MENUITEMTYPE_DROPDOWN, + 0, + MENUITEMFLAG_LITERAL_TEXT, + (uintptr_t)"Texture Filtering", + 0, + menuhandlerTexFilter, + }, + { + MENUITEMTYPE_CHECKBOX, + 0, + MENUITEMFLAG_LITERAL_TEXT, + (uintptr_t)"GUI Texture Filtering", + 0, + menuhandlerTexFilter2D, + }, + { + MENUITEMTYPE_SEPARATOR, + 0, + 0, + 0, + 0, + NULL, + }, { MENUITEMTYPE_CHECKBOX, 0, @@ -1121,14 +1145,6 @@ struct menuitem g_ExtendedVideoMenuItems[] = { 0, menuhandlerTexDetail, }, - { - MENUITEMTYPE_CHECKBOX, - 0, - MENUITEMFLAG_LITERAL_TEXT, - (uintptr_t)"GUI Texture Filtering", - 0, - menuhandlerTexFilter2D, - }, { MENUITEMTYPE_CHECKBOX, 0, @@ -1137,14 +1153,6 @@ struct menuitem g_ExtendedVideoMenuItems[] = { 0, menuhandlerGeMuzzleFlashes, }, - { - MENUITEMTYPE_DROPDOWN, - 0, - MENUITEMFLAG_LITERAL_TEXT, - (uintptr_t)"HUD Centering", - 0, - menuhandlerCenterHUD, - }, { MENUITEMTYPE_SLIDER, 0, diff --git a/port/src/video.c b/port/src/video.c index 9a121421d..7430204e8 100644 --- a/port/src/video.c +++ b/port/src/video.c @@ -42,7 +42,7 @@ static s32 vidVsync = 1; static s32 vidMSAA = 1; static s32 vidFramerateLimit = 0; static s32 vidDisplayFPS = 0; -static s32 vidDisplayFPSDivisor = 8; +static s32 vidDisplayFPSDivisor = 16; static displaymode vidModeDefault; static s32 vidNumModes = 1; static displaymode *vidModes = &vidModeDefault; @@ -529,7 +529,7 @@ PD_CONSTRUCTOR static void videoConfigInit(void) configRegisterInt("Video.FramebufferEffects", &vidFramebuffers, 0, 1); configRegisterInt("Video.FramerateLimit", &vidFramerateLimit, 0, VIDEO_MAX_FPS); configRegisterInt("Video.DisplayFPS", &vidDisplayFPS, 0, 1); - configRegisterInt("Video.DisplayFPSDivisor", &vidDisplayFPSDivisor, 1, 16); + configRegisterInt("Video.DisplayFPSDivisor", &vidDisplayFPSDivisor, 1, 32); configRegisterInt("Video.MSAA", &vidMSAA, 1, 16); configRegisterInt("Video.TextureFilter", &texFilter, 0, 2); configRegisterInt("Video.TextureFilter2D", &texFilter2D, 0, 1); From 687b966c5eb736ae1311d9a6e1bcd97ed160a9dd Mon Sep 17 00:00:00 2001 From: Matthew Coppola Date: Sat, 30 Nov 2024 20:29:11 -0500 Subject: [PATCH 11/18] Allow overriding g_TickRateDiv at runtime --- port/src/main.c | 2 ++ port/src/optionsmenu.c | 69 +++++++++++++++++++++++++++++++++++++++++- src/include/data.h | 1 + 3 files changed, 71 insertions(+), 1 deletion(-) diff --git a/port/src/main.c b/port/src/main.c index f8f6af13f..c745c0efb 100644 --- a/port/src/main.c +++ b/port/src/main.c @@ -36,6 +36,7 @@ u32 g_VmNumPageReplaces = 0; u8 g_VmShowStats = 0; s32 g_TickRateDiv = 1; +s32 g_TickRateDivOverride = 0; s32 g_TickExtraSleep = true; s32 g_SkipIntro = false; @@ -165,6 +166,7 @@ PD_CONSTRUCTOR static void gameConfigInit(void) configRegisterInt("Game.MenuMouseControl", &g_MenuMouseControl, 0, 1); configRegisterFloat("Game.ScreenShakeIntensity", &g_ViShakeIntensityMult, 0.f, 10.f); configRegisterInt("Game.TickRateDivisor", &g_TickRateDiv, 0, 10); + configRegisterInt("Game.TickRateDivisorOverride", &g_TickRateDivOverride, 0, 1); configRegisterInt("Game.ExtraSleep", &g_TickExtraSleep, 0, 1); configRegisterInt("Game.SkipIntro", &g_SkipIntro, 0, 1); configRegisterInt("Game.DisableMpDeathMusic", &g_MusicDisableMpDeath, 0, 1); diff --git a/port/src/optionsmenu.c b/port/src/optionsmenu.c index e26c8efcd..1d03bc169 100644 --- a/port/src/optionsmenu.c +++ b/port/src/optionsmenu.c @@ -775,7 +775,7 @@ static MenuItemHandlerResult menuhandlerFramerateLimit(s32 operation, struct men data->slider.value = videoGetFramerateLimit(); break; case MENUOP_SET: - if (g_TickRateDiv < 2) { + if (!g_TickRateDivOverride) { g_TickRateDiv = (data->slider.value == 0 || data->slider.value > 60) ? 0 : 1; } videoSetFramerateLimit(data->slider.value); @@ -960,6 +960,49 @@ static MenuItemHandlerResult menuhandlerGeMuzzleFlashes(s32 operation, struct me return 0; } +static MenuItemHandlerResult menuhandlerTickrateDivisorOverride(s32 operation, struct menuitem *item, union handlerdata *data) +{ + s32 framerateLimit; + + switch (operation) { + case MENUOP_GET: + return g_TickRateDivOverride; + case MENUOP_SET: + g_TickRateDivOverride = data->checkbox.value; + if (!g_TickRateDivOverride) { + framerateLimit = videoGetFramerateLimit(); + g_TickRateDiv = (framerateLimit == 0 || framerateLimit > 60) ? 0 : 1; + } + } + + return 0; +} + +static MenuItemHandlerResult menuhandlerTickrateDivisor(s32 operation, struct menuitem *item, union handlerdata *data) +{ + s32 framerateLimit; + + switch (operation) { + case MENUOP_CHECKHIDDEN: + if (!g_TickRateDivOverride) { + return true; + } + break; + case MENUOP_GETSLIDER: + data->slider.value = g_TickRateDiv; + break; + case MENUOP_SET: + if (g_TickRateDivOverride) { + g_TickRateDiv = data->slider.value; + } else { + framerateLimit = videoGetFramerateLimit(); + g_TickRateDiv = (framerateLimit == 0 || framerateLimit > 60) ? 0 : 1; + } + } + + return 0; +} + static MenuItemHandlerResult menuhandlerCenterHUD(s32 operation, struct menuitem *item, union handlerdata *data) { static const char *opts[] = { @@ -1169,6 +1212,30 @@ struct menuitem g_ExtendedVideoMenuItems[] = { 0, NULL, }, + { + MENUITEMTYPE_CHECKBOX, + 0, + MENUITEMFLAG_LITERAL_TEXT, + (uintptr_t)"Override Tickrate Divisor", + 0, + menuhandlerTickrateDivisorOverride, + }, + { + MENUITEMTYPE_SLIDER, + 0, + MENUITEMFLAG_LITERAL_TEXT | MENUITEMFLAG_SLIDER_WIDE, + (uintptr_t)"Tickrate Divisor", + 10, + menuhandlerTickrateDivisor, + }, + { + MENUITEMTYPE_SEPARATOR, + 0, + 0, + 0, + 0, + NULL, + }, { MENUITEMTYPE_SELECTABLE, 0, diff --git a/src/include/data.h b/src/include/data.h index 24c106292..6a5148ca0 100644 --- a/src/include/data.h +++ b/src/include/data.h @@ -550,6 +550,7 @@ extern s32 g_BlurFb; extern s32 g_BlurFbCapTimer; extern bool g_BlurFbDirty; extern s32 g_TickRateDiv; +extern s32 g_TickRateDivOverride; extern s32 g_TickExtraSleep; extern s32 g_MusicDisableMpDeath; extern s32 g_BgunGeMuzzleFlashes; From e2e5bc352d56cbfb4438edf459d8e547b2299f06 Mon Sep 17 00:00:00 2001 From: Matthew Coppola Date: Fri, 6 Dec 2024 16:53:38 -0500 Subject: [PATCH 12/18] Call text_end (text0f153780) for correctness sake --- src/game/lv.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/game/lv.c b/src/game/lv.c index 06bff3b84..bd2a37c2f 100644 --- a/src/game/lv.c +++ b/src/game/lv.c @@ -974,6 +974,7 @@ Gfx *lvRenderFPS(Gfx *gdl) gdl = text0f153628(gdl); gdl = textRender(gdl, &x, &y, buffer, g_CharsNumeric, g_FontNumeric, color, 0x000000a0, viGetWidth(), viGetHeight(), 0, 0); + gdl = text0f153780(gdl); gSPClearExtraGeometryModeEXT(gdl++, g_HudAlignModeL); } From ac4f317879a7f983b946aaae8682a5ccbda57b2b Mon Sep 17 00:00:00 2001 From: fgsfds Date: Sat, 22 Feb 2025 13:22:00 +0100 Subject: [PATCH 13/18] port: changes to the video settings PR: - change FPS calculation to use average FPS, use real time instead of render time for interval timing - remove Maximize Window (you already have a maximize button and it's saved to the INI) - remove Display FPS Interval from settings (the average user won't care and whoever cares can change it in the INI) - remove the tickrate override flag and implicit tickrate adjustment in the framerate limit option, instead add one checkbox to uncap tickrate (mostly because I'd like the game to default to the og 60 FPS cap since high FPS is still unstable) - rearrange some options a bit --- port/include/video.h | 6 +- port/src/main.c | 2 - port/src/optionsmenu.c | 147 +++++++---------------------------------- port/src/video.c | 54 ++++++++------- src/game/lv.c | 24 +++++-- src/include/data.h | 1 - 6 files changed, 73 insertions(+), 161 deletions(-) diff --git a/port/include/video.h b/port/include/video.h index 0c468dcbb..288c9b284 100644 --- a/port/include/video.h +++ b/port/include/video.h @@ -21,7 +21,6 @@ void videoStartFrame(void); void videoSubmitCommands(Gfx *cmds); void videoClearScreen(void); void videoEndFrame(void); -f64 videoGetDiffTime(void); void *videoGetWindowHandle(void); @@ -47,9 +46,11 @@ s32 videoGetNumDisplayModes(void); s32 videoGetVsync(void); s32 videoGetFramerateLimit(void); s32 videoGetDisplayFPS(void); -s32 videoGetDisplayFPSDivisor(void); s32 videoGetMSAA(void); +f32 videoGetAverageFPS(void); +f64 videoGetLastRenderTime(void); + void videoSetWindowOffset(s32 x, s32 y); void videoSetFullscreen(s32 fs); void videoSetFullscreenMode(s32 mode); @@ -60,7 +61,6 @@ void videoSetDisplayMode(const s32 index); void videoSetVsync(const s32 vsync); void videoSetFramerateLimit(const s32 limit); void videoSetDisplayFPS(const s32 displayfps); -void videoSetDisplayFPSDivisor(const s32 divisor); void videoSetMSAA(const s32 msaa); s32 videoCreateFramebuffer(u32 w, u32 h, s32 upscale, s32 autoresize); diff --git a/port/src/main.c b/port/src/main.c index 0b7e7e6c2..a622bc078 100644 --- a/port/src/main.c +++ b/port/src/main.c @@ -36,7 +36,6 @@ u32 g_VmNumPageReplaces = 0; u8 g_VmShowStats = 0; s32 g_TickRateDiv = 1; -s32 g_TickRateDivOverride = 0; s32 g_TickExtraSleep = true; s32 g_SkipIntro = false; @@ -166,7 +165,6 @@ PD_CONSTRUCTOR static void gameConfigInit(void) configRegisterInt("Game.MenuMouseControl", &g_MenuMouseControl, 0, 1); configRegisterFloat("Game.ScreenShakeIntensity", &g_ViShakeIntensityMult, 0.f, 10.f); configRegisterInt("Game.TickRateDivisor", &g_TickRateDiv, 0, 10); - configRegisterInt("Game.TickRateDivisorOverride", &g_TickRateDivOverride, 0, 1); configRegisterInt("Game.ExtraSleep", &g_TickExtraSleep, 0, 1); configRegisterInt("Game.SkipIntro", &g_SkipIntro, 0, 1); configRegisterInt("Game.DisableMpDeathMusic", &g_MusicDisableMpDeath, 0, 1); diff --git a/port/src/optionsmenu.c b/port/src/optionsmenu.c index 8f0eaa2c1..7629d9f8d 100644 --- a/port/src/optionsmenu.c +++ b/port/src/optionsmenu.c @@ -721,19 +721,6 @@ static MenuItemHandlerResult menuhandlerFullScreenMode(s32 operation, struct men return 0; } -static MenuItemHandlerResult menuhandlerMaximizeWindow(s32 operation, struct menuitem *item, union handlerdata *data) -{ - switch (operation) { - case MENUOP_GET: - return videoGetMaximizeWindow(); - case MENUOP_SET: - videoSetMaximizeWindow(data->checkbox.value); - break; - } - - return 0; -} - static MenuItemHandlerResult menuhandlerCenterWindow(s32 operation, struct menuitem *item, union handlerdata *data) { switch (operation) { @@ -786,9 +773,6 @@ static MenuItemHandlerResult menuhandlerFramerateLimit(s32 operation, struct men data->slider.value = videoGetFramerateLimit(); break; case MENUOP_SET: - if (!g_TickRateDivOverride) { - g_TickRateDiv = (data->slider.value == 0 || data->slider.value > 60) ? 0 : 1; - } videoSetFramerateLimit(data->slider.value); break; case MENUOP_GETSLIDERLABEL: @@ -937,27 +921,6 @@ static MenuItemHandlerResult menuhandlerDisplayFPS(s32 operation, struct menuite return 0; } -static MenuItemHandlerResult menuhandlerDisplayFPSInterval(s32 operation, struct menuitem *item, union handlerdata *data) -{ - switch (operation) { - case MENUOP_GETSLIDER: - data->slider.value = videoGetDisplayFPSDivisor() - 1; - break; - case MENUOP_SET: - videoSetDisplayFPSDivisor(data->dropdown.value + 1); - break; - case MENUOP_GETSLIDERLABEL: - // NOTE: data->slider.label length must not exceed 15. - if (data->slider.value == 0) { - strcpy(data->slider.label, "1 Sec"); - } else { - sprintf(data->slider.label, "1/%d Secs", data->slider.value + 1); - } - } - - return 0; -} - static MenuItemHandlerResult menuhandlerGeMuzzleFlashes(s32 operation, struct menuitem *item, union handlerdata *data) { switch (operation) { @@ -971,44 +934,14 @@ static MenuItemHandlerResult menuhandlerGeMuzzleFlashes(s32 operation, struct me return 0; } -static MenuItemHandlerResult menuhandlerTickrateDivisorOverride(s32 operation, struct menuitem *item, union handlerdata *data) +static MenuItemHandlerResult menuhandlerUncapTickrate(s32 operation, struct menuitem *item, union handlerdata *data) { - s32 framerateLimit; - switch (operation) { case MENUOP_GET: - return g_TickRateDivOverride; + return (g_TickRateDiv == 0); case MENUOP_SET: - g_TickRateDivOverride = data->checkbox.value; - if (!g_TickRateDivOverride) { - framerateLimit = videoGetFramerateLimit(); - g_TickRateDiv = (framerateLimit == 0 || framerateLimit > 60) ? 0 : 1; - } - } - - return 0; -} - -static MenuItemHandlerResult menuhandlerTickrateDivisor(s32 operation, struct menuitem *item, union handlerdata *data) -{ - s32 framerateLimit; - - switch (operation) { - case MENUOP_CHECKHIDDEN: - if (!g_TickRateDivOverride) { - return true; - } + g_TickRateDiv = !data->checkbox.value; break; - case MENUOP_GETSLIDER: - data->slider.value = g_TickRateDiv; - break; - case MENUOP_SET: - if (g_TickRateDivOverride) { - g_TickRateDiv = data->slider.value; - } else { - framerateLimit = videoGetFramerateLimit(); - g_TickRateDiv = (framerateLimit == 0 || framerateLimit > 60) ? 0 : 1; - } } return 0; @@ -1087,14 +1020,6 @@ struct menuitem g_ExtendedVideoMenuItems[] = { 0, menuhandlerResolution, }, - { - MENUITEMTYPE_CHECKBOX, - 0, - MENUITEMFLAG_LITERAL_TEXT, - (uintptr_t)"Maximize Window", - 0, - menuhandlerMaximizeWindow, - }, { MENUITEMTYPE_CHECKBOX, 0, @@ -1107,9 +1032,9 @@ struct menuitem g_ExtendedVideoMenuItems[] = { MENUITEMTYPE_DROPDOWN, 0, MENUITEMFLAG_LITERAL_TEXT, - (uintptr_t)"HUD Centering", + (uintptr_t)"Anti-aliasing", 0, - menuhandlerCenterHUD, + menuhandlerMSAA, }, { MENUITEMTYPE_SEPARATOR, @@ -1135,6 +1060,14 @@ struct menuitem g_ExtendedVideoMenuItems[] = { VIDEO_MAX_FPS, menuhandlerFramerateLimit, }, + { + MENUITEMTYPE_CHECKBOX, + 0, + MENUITEMFLAG_LITERAL_TEXT, + (uintptr_t)"Uncap Tickrate", + 0, + menuhandlerUncapTickrate, + }, { MENUITEMTYPE_CHECKBOX, 0, @@ -1143,14 +1076,6 @@ struct menuitem g_ExtendedVideoMenuItems[] = { 0, menuhandlerDisplayFPS, }, - { - MENUITEMTYPE_SLIDER, - 0, - MENUITEMFLAG_LITERAL_TEXT | MENUITEMFLAG_SLIDER_WIDE, - (uintptr_t)"Display FPS Interval", - 31, - menuhandlerDisplayFPSInterval, - }, { MENUITEMTYPE_SEPARATOR, 0, @@ -1159,14 +1084,6 @@ struct menuitem g_ExtendedVideoMenuItems[] = { 0, NULL, }, - { - MENUITEMTYPE_DROPDOWN, - 0, - MENUITEMFLAG_LITERAL_TEXT, - (uintptr_t)"Anti-aliasing", - 0, - menuhandlerMSAA, - }, { MENUITEMTYPE_DROPDOWN, 0, @@ -1183,6 +1100,14 @@ struct menuitem g_ExtendedVideoMenuItems[] = { 0, menuhandlerTexFilter2D, }, + { + MENUITEMTYPE_CHECKBOX, + 0, + MENUITEMFLAG_LITERAL_TEXT, + (uintptr_t)"Detail Textures", + 0, + menuhandlerTexDetail, + }, { MENUITEMTYPE_SEPARATOR, 0, @@ -1192,12 +1117,12 @@ struct menuitem g_ExtendedVideoMenuItems[] = { NULL, }, { - MENUITEMTYPE_CHECKBOX, + MENUITEMTYPE_DROPDOWN, 0, MENUITEMFLAG_LITERAL_TEXT, - (uintptr_t)"Detail Textures", + (uintptr_t)"HUD Centering", 0, - menuhandlerTexDetail, + menuhandlerCenterHUD, }, { MENUITEMTYPE_CHECKBOX, @@ -1223,30 +1148,6 @@ struct menuitem g_ExtendedVideoMenuItems[] = { 0, NULL, }, - { - MENUITEMTYPE_CHECKBOX, - 0, - MENUITEMFLAG_LITERAL_TEXT, - (uintptr_t)"Override Tickrate Divisor", - 0, - menuhandlerTickrateDivisorOverride, - }, - { - MENUITEMTYPE_SLIDER, - 0, - MENUITEMFLAG_LITERAL_TEXT | MENUITEMFLAG_SLIDER_WIDE, - (uintptr_t)"Tickrate Divisor", - 10, - menuhandlerTickrateDivisor, - }, - { - MENUITEMTYPE_SEPARATOR, - 0, - 0, - 0, - 0, - NULL, - }, { MENUITEMTYPE_SELECTABLE, 0, diff --git a/port/src/video.c b/port/src/video.c index 7430204e8..296a71063 100644 --- a/port/src/video.c +++ b/port/src/video.c @@ -41,20 +41,26 @@ static s32 vidAllowHiDpi = false; static s32 vidVsync = 1; static s32 vidMSAA = 1; static s32 vidFramerateLimit = 0; + static s32 vidDisplayFPS = 0; -static s32 vidDisplayFPSDivisor = 16; -static displaymode vidModeDefault; +static f32 vidDisplayFPSInterval = 1.f; +static f32 vidAvgFPS = 0; +static f64 vidLastRenderTime; + static s32 vidNumModes = 1; +static displaymode vidModeDefault; static displaymode *vidModes = &vidModeDefault; -static f64 vidDiffTime; static s32 texFilter = FILTER_LINEAR; static s32 texFilter2D = true; -static s32 texDetail = true; +static s32 texDetail = false; static u32 dlcount = 0; static u32 frames = 0; static f64 startTime, endTime; +static f64 accumDelta = 0.0; +static f64 fpsTime = 0.0; +static s32 fpsNumFrames = 0; static s32 videoInitDisplayModes(void); @@ -121,8 +127,6 @@ void videoSubmitCommands(Gfx *cmds) void videoEndFrame(void) { - static f64 accumTime = 0.0; - if (!initDone) { return; } @@ -130,22 +134,32 @@ void videoEndFrame(void) gfx_end_frame(); ++frames; + ++fpsNumFrames; - endTime = wmAPI->get_time(); - accumTime += endTime - startTime; + const f64 flipTime = wmAPI->get_time(); + accumDelta += flipTime - endTime; + endTime = flipTime; + vidLastRenderTime = endTime - startTime; - if (accumTime >= 1.0 / (f64)vidDisplayFPSDivisor) { + if (endTime >= fpsTime) { char tmp[128]; - vidDiffTime = endTime - startTime; - accumTime = 0.0; - snprintf(tmp, sizeof(tmp), "fps %3u frt %lf frm %u", (u32)(1.0 / vidDiffTime), vidDiffTime, frames); + vidAvgFPS = fpsNumFrames ? ((f64)fpsNumFrames / accumDelta) : 0.f; + fpsNumFrames = 0; + accumDelta = 0.0; + snprintf(tmp, sizeof(tmp), "fps %4.1f frt %lf frm %u", vidAvgFPS, vidLastRenderTime, frames); wmAPI->set_window_title(tmp); + fpsTime = endTime + vidDisplayFPSInterval; } } -f64 videoGetDiffTime(void) +f64 videoGetLastRenderTime(void) { - return vidDiffTime; + return vidLastRenderTime; +} + +f32 videoGetAverageFPS(void) +{ + return vidAvgFPS; } void videoClearScreen(void) @@ -253,11 +267,6 @@ s32 videoGetDisplayFPS(void) return vidDisplayFPS; } -s32 videoGetDisplayFPSDivisor(void) -{ - return vidDisplayFPSDivisor; -} - static s32 videoInitDisplayModes(void) { if (!wmAPI->get_current_display_mode(&vidModeDefault.width, &vidModeDefault.height)) { @@ -470,11 +479,6 @@ void videoSetDisplayFPS(const s32 displayfps) vidDisplayFPS = displayfps; } -void videoSetDisplayFPSDivisor(const s32 divisor) -{ - vidDisplayFPSDivisor = divisor; -} - void videoSetFramebuffer(s32 target) { return gfx_set_framebuffer(target, 1.f); @@ -529,7 +533,7 @@ PD_CONSTRUCTOR static void videoConfigInit(void) configRegisterInt("Video.FramebufferEffects", &vidFramebuffers, 0, 1); configRegisterInt("Video.FramerateLimit", &vidFramerateLimit, 0, VIDEO_MAX_FPS); configRegisterInt("Video.DisplayFPS", &vidDisplayFPS, 0, 1); - configRegisterInt("Video.DisplayFPSDivisor", &vidDisplayFPSDivisor, 1, 32); + configRegisterFloat("Video.DisplayFPSInterval", &vidDisplayFPSInterval, 0.01f, 32.f); configRegisterInt("Video.MSAA", &vidMSAA, 1, 16); configRegisterInt("Video.TextureFilter", &texFilter, 0, 2); configRegisterInt("Video.TextureFilter2D", &texFilter2D, 0, 1); diff --git a/src/game/lv.c b/src/game/lv.c index 3ce268583..56d9e9ab3 100644 --- a/src/game/lv.c +++ b/src/game/lv.c @@ -957,16 +957,26 @@ void lvFindThreats(void) #ifndef PLATFORM_N64 Gfx *lvRenderFPS(Gfx *gdl) { + const f32 fps = videoGetAverageFPS(); + const u8 a = 160; s32 x = 27, y = 13; - f32 fps = 1.0f / videoGetDiffTime(); - s32 ifps = (s32)fps; - u8 r = (ifps <= 30) ? 255 : (ifps <= 60) ? roundf(255 * (1.0f - (ifps - 30.0f) / 30.0f)) : 0; - u8 g = (ifps <= 30) ? roundf(255 * (ifps - 1.0f) / 29.0f) : 255; - u8 b = (ifps > 60 && ifps <= 90) ? roundf(255 * (ifps - 60.0f) / 30.0f) : (ifps > 90) ? 255 : 0; - u8 a = 160; - u32 color = r << 24 | g << 16 | b << 8 | a; + u32 color; char buffer[16]; + if (fps <= 30.f) { + // red -> yellow + color = 0xff000000 | a | ((u32)((fps / 30.f) * 255.f) << 16); + } else if (fps <= 60.f) { + // yellow -> green + color = 0x00ff0000 | a | ((u32)((1.f - (fps - 30.f) / 30.f) * 255.f) << 24); + } else if (fps <= 90.f) { + // green -> cyan + color = 0x00ff0000 | a | ((u32)(((fps - 60.f) / 30.f) * 255.f) << 8); + } else { + // cyan + color = 0x00ffff00 | a; + } + if (g_CharsNumeric && g_FontNumeric) { snprintf(buffer, sizeof buffer, "%.2f", fps); diff --git a/src/include/data.h b/src/include/data.h index 787cdfb27..d1bf537ca 100644 --- a/src/include/data.h +++ b/src/include/data.h @@ -551,7 +551,6 @@ extern s32 g_BlurFb; extern s32 g_BlurFbCapTimer; extern bool g_BlurFbDirty; extern s32 g_TickRateDiv; -extern s32 g_TickRateDivOverride; extern s32 g_TickExtraSleep; extern s32 g_MusicDisableMpDeath; extern s32 g_BgunGeMuzzleFlashes; From 26251c086577a11d47d691caf6b36c0904fc6008 Mon Sep 17 00:00:00 2001 From: fgsfds Date: Sat, 22 Feb 2025 13:28:23 +0100 Subject: [PATCH 14/18] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 00485eebb..53022f2bd 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ There are minor graphics- and gameplay-related issues, and possibly occasional c * basic mod support, currently enough to load a few custom levels; * slightly expanded memory heap size; * experimental high framerate support (up to 240 FPS): - * set `Game.TickRateDivisor` to `0` in `pd.ini` to activate; + * enable `Uncap Tickrate` in `Extended Video Options` to activate; * in practice the game will have issues running faster than ~165 FPS, so use VSync or `Video.FramerateLimit` to cap it. * emulate the Transfer Pak functionality the game has on the Nintendo 64 to unlock some cheats automatically. From f4f1470e1e0e57aed988ac20d99156a848e7d031 Mon Sep 17 00:00:00 2001 From: fgsfds Date: Sat, 22 Feb 2025 13:48:25 +0100 Subject: [PATCH 15/18] port: use a dynamic string for the vsync options --- port/src/optionsmenu.c | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/port/src/optionsmenu.c b/port/src/optionsmenu.c index 7629d9f8d..4771fc28b 100644 --- a/port/src/optionsmenu.c +++ b/port/src/optionsmenu.c @@ -736,27 +736,25 @@ static MenuItemHandlerResult menuhandlerCenterWindow(s32 operation, struct menui static MenuItemHandlerResult menuhandlerVsync(s32 operation, struct menuitem *item, union handlerdata *data) { - static const char *opts[] = { + static const s32 numOpts = 10; + static const char *constOpts[] = { "Adaptive", "Off", - "On (Every Frame)", - "On (Every 2 Frames)", - "On (Every 3 Frames)", - "On (Every 4 Frames)", - "On (Every 5 Frames)", - "On (Every 6 Frames)", - "On (Every 7 Frames)", - "On (Every 8 Frames)", - "On (Every 9 Frames)", - "On (Every 10 Frames)" + "On" }; + static char dynOpt[20]; + s32 vblanks; switch (operation) { case MENUOP_GETOPTIONCOUNT: - data->dropdown.value = ARRAYCOUNT(opts); + data->dropdown.value = numOpts; break; case MENUOP_GETOPTIONTEXT: - return (intptr_t)opts[data->dropdown.value]; + if (data->dropdown.value < ARRAYCOUNT(constOpts)) + return (intptr_t)constOpts[data->dropdown.value]; + vblanks = (s32)data->dropdown.value - 1; + snprintf(dynOpt, sizeof(dynOpt), "On (%d frames)", vblanks); + return (intptr_t)dynOpt; case MENUOP_SET: videoSetVsync(data->dropdown.value - 1); case MENUOP_GETSELECTEDINDEX: From 045c4c371af51d3fc6c8751607c817e848e8cc0c Mon Sep 17 00:00:00 2001 From: fgsfds Date: Sat, 22 Feb 2025 13:50:49 +0100 Subject: [PATCH 16/18] Revert "ci: temporarily disable flatpak autobuilds" This reverts commit daade36e192174839c1f0a575f78b5a8ea56d885. --- .github/workflows/c-cpp.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/c-cpp.yml b/.github/workflows/c-cpp.yml index 56b0fff4c..6ae34ca94 100644 --- a/.github/workflows/c-cpp.yml +++ b/.github/workflows/c-cpp.yml @@ -282,7 +282,7 @@ jobs: bundle: io.github.fgsfdsfgs.perfect_dark.flatpak manifest-path: dist/linux/io.github.fgsfdsfgs.perfect_dark.yaml cache-key: flatpak-builder-${{ github.sha }} - upload-artifact: true + upload-artifact: false # - name: Build Flatpak bundle # run: | @@ -302,7 +302,7 @@ jobs: publish-latest-build: runs-on: ubuntu-latest if: github.ref == 'refs/heads/port' - needs: [build-i686-windows, build-i686-linux, build-x86_64-windows, build-x86_64-linux, build-arm64-nswitch, build-x86_64-osx, build-arm64-osx] + needs: [build-i686-windows, build-i686-linux, build-x86_64-windows, build-x86_64-linux, build-arm64-nswitch, build-x86_64-osx, build-arm64-osx, build-flatpak-bundle] permissions: contents: write steps: @@ -323,6 +323,7 @@ jobs: zip -r ../ci-release/pd-i686-windows.zip pd-i686-windows zip -r ../ci-release/pd-x86_64-windows.zip pd-x86_64-windows zip -r ../ci-release/pd-arm64-nswitch.zip pd-arm64-nswitch + mv io.github.fgsfdsfgs.perfect_dark.flatpak/io.github.fgsfdsfgs.perfect_dark.flatpak ../ci-release/ popd git tag --force ci-dev-build port git push --force origin ci-dev-build From 8b0b75d4addd8e3db980b3ec22384b42fc74cbae Mon Sep 17 00:00:00 2001 From: fgsfds Date: Mon, 24 Feb 2025 20:26:38 +0100 Subject: [PATCH 17/18] port: increase CONFIG_MAX_SETTINGS --- port/src/config.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/port/src/config.c b/port/src/config.c index 6be6f0d9f..ec011625c 100644 --- a/port/src/config.c +++ b/port/src/config.c @@ -10,7 +10,7 @@ #define CONFIG_MAX_SECNAME 128 #define CONFIG_MAX_KEYNAME 256 -#define CONFIG_MAX_SETTINGS 256 +#define CONFIG_MAX_SETTINGS 300 typedef enum { CFG_NONE, From 60b8e3b49eb5d124bf5923f84eb2068e237c26d8 Mon Sep 17 00:00:00 2001 From: SilentException Date: Mon, 24 Feb 2025 21:35:02 +0100 Subject: [PATCH 18/18] Log warning if configuration max settings is exceeded + snprintf instead of strncpy (null termination) --- port/src/config.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/port/src/config.c b/port/src/config.c index ec011625c..596cf3a5a 100644 --- a/port/src/config.c +++ b/port/src/config.c @@ -34,6 +34,7 @@ struct configentry { } settings[CONFIG_MAX_SETTINGS]; static s32 numSettings = 0; +static u8 configMaxWarningLogged = 0; static inline s32 configClampInt(s32 val, s32 min, s32 max) { @@ -64,11 +65,15 @@ static inline struct configentry *configAddEntry(const char *key) { if (numSettings < CONFIG_MAX_SETTINGS) { struct configentry *cfg = &settings[numSettings++]; - strncpy(cfg->key, key, CONFIG_MAX_KEYNAME); + snprintf(cfg->key, CONFIG_MAX_KEYNAME, "%s", key); const char *delim = strrchr(cfg->key, '.'); cfg->seclen = delim ? (delim - cfg->key) : 0; return cfg; } + if (!configMaxWarningLogged) { + sysLogPrintf(LOG_WARNING, "Maximum number of configuration entries exceeded: %d", CONFIG_MAX_SETTINGS); + configMaxWarningLogged = 1; + } return NULL; }