From 09b0a12b0aa4bc94d0291524cb5d4282e2e86d5d Mon Sep 17 00:00:00 2001 From: Matthew Coppola Date: Thu, 21 Nov 2024 02:54:23 -0500 Subject: [PATCH] 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);