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.
This commit is contained in:
Matthew Coppola committed 2024-12-06 17:20:15 -05:00
1 parent 05e462b680
commit 09b0a12b0a
7 files changed
+273 -15

No files matched your search

+8
View File
@@ -0,0 +1,8 @@
#ifndef GFX_MODES_H
#define GFX_MODES_H
struct GfxModes {
char mode[10];
};
#endif
+91 -7
View File
@@ -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<int*>((void*)width), static_cast<int*>((void*)height));
SDL_GetWindowPosition(wnd, static_cast<int*>(posX), static_cast<int*>(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,
};
+7
View File
@@ -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
+10
View File
@@ -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
+1
View File
@@ -88,6 +88,7 @@ static void cleanup(void)
sysLogPrintf(LOG_NOTE, "shutdown");
inputSaveBinds();
configSave(CONFIG_PATH);
videoShutdown();
crashShutdown();
// TODO: actually shut down all subsystems
}
+71 -8
View File
@@ -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,
+85
View File
@@ -6,6 +6,7 @@
#include <PR/gbi.h>
#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);