Files
psxrecomp/runtime/include/psx_sdl.h
Shokunin f34f91cc25 Settings: Internal resolution presets (Native ... 8K, Match display)
Add a player-facing internal resolution on top of the raised GL ceiling.
A preset is a target height; the runtime renders at the integer scale
S = ceil(target / reference_lines), reference 240 by default: 720p 3x,
1080p 5x (area-resolved to 1080), 1440p 6x, 4K 9x, 5K 12x, 8K 18x. The
backend then clamps S to what it can allocate.

- runtime/include/internal_resolution.h: the preset table, stable ids,
  the value encoding shared with recomp-ui (0 unset, 1 native, -1 match
  display, N target lines) and the pure resolver.
- config: [video] internal_resolution (a preset id or a number of lines)
  and resolution_reference_lines in game.toml; internal_resolution in
  settings.toml, written as a stable id beside supersampling = min(S, 4)
  for older runtimes. Precedence: game default < player's legacy factor <
  player's preset. settings.toml window_width now accepts up to 7680.
- main.cpp: resolve the preset before gpu_init; Match display re-resolves
  from the game window's monitor (SDL3 mode height x pixel density) before
  the GL context allocates. The launcher gets the vocabulary (with a
  synthesized "Nx (L lines)" entry for a legacy factor no preset matches)
  behind RECOMP_LAUNCHER_HAS_INTERNAL_RESOLUTION, on first boot and on the
  netplay soft-return.
- The OpenGL game window gets SDL_WINDOW_HIGH_PIXEL_DENSITY (SDL2:
  ALLOW_HIGHDPI) only when the player chose more than native, so the
  drawable is in pixels on Retina; native keeps the window as it was.
- debug server: video_info (preset, requested and effective scale, GL
  limit and clamp, hr surface, window points vs pixels), screenshot_hires
  reads the GL hr FBO at full size, screenshot_wide_hires captures the
  native-wide band at internal resolution.
- docs: config_schema (keys, precedence), NETPLAY (a per-peer presentation
  setting), TCP_COMMANDS (index regenerated), ENHANCEMENTS IR1.

Default-off: with no internal_resolution anywhere the scale, the window
flags and the present path are unchanged.

Tests: internal_resolution_test (presets x reference 240/480 x backend
maxima, legacy mapping round trip), video_enhancement_settings_test (game
and settings keys, stable-id round trip, legacy key still written),
gl_internal_scale_guards (HiDPI only when opted in, launcher fields behind
the capability macro).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 20:52:23 -07:00

273 lines
8.1 KiB
C

#pragma once
/*
* Runtime-facing SDL compatibility seam.
*
* PSXRecomp uses SDL3 by default, while PSX_SDL_BACKEND=SDL2 keeps the
* previous implementation available for comparison and older platforms.
*/
#if defined(PSX_SDL3)
#define SDL_ENABLE_OLD_NAMES
#define SDL_FUNCTION_POINTER_IS_VOID_POINTER
#include <SDL3/SDL.h>
#ifndef SDL_WINDOW_SHOWN
#define SDL_WINDOW_SHOWN 0
#endif
#ifndef SDL_WINDOW_FULLSCREEN_DESKTOP
#define SDL_WINDOW_FULLSCREEN_DESKTOP SDL_WINDOW_FULLSCREEN
#endif
#ifndef SDL_RENDERER_ACCELERATED
#define SDL_RENDERER_ACCELERATED 0x00000001u
#endif
#ifndef SDL_RENDERER_PRESENTVSYNC
#define SDL_RENDERER_PRESENTVSYNC 0x00000002u
#endif
#ifndef SDL_RENDERER_SOFTWARE
#define SDL_RENDERER_SOFTWARE 0x00000004u
#endif
#ifndef SDL_MUTEX_TIMEDOUT
#define SDL_MUTEX_TIMEDOUT 1
#endif
#ifndef SDL_HINT_RENDER_SCALE_QUALITY
#define SDL_HINT_RENDER_SCALE_QUALITY "SDL_RENDER_SCALE_QUALITY"
#endif
static inline int psx_sdl_init(SDL_InitFlags flags)
{
return SDL_Init(flags) ? 0 : -1;
}
static inline int psx_sdl_init_subsystem(SDL_InitFlags flags)
{
return SDL_InitSubSystem(flags) ? 0 : -1;
}
static inline SDL_Window *psx_sdl_create_window(
const char *title, int x, int y, int w, int h, Uint64 flags)
{
(void)x;
(void)y;
return SDL_CreateWindow(title, w, h, (SDL_WindowFlags)flags);
}
static inline SDL_Renderer *psx_sdl_create_renderer(
SDL_Window *window, int driver_index, Uint32 flags)
{
(void)driver_index;
SDL_Renderer *renderer = SDL_CreateRenderer(window, NULL);
if (renderer && (flags & SDL_RENDERER_PRESENTVSYNC)) {
(void)SDL_SetRenderVSync(renderer, 1);
}
return renderer;
}
static inline int psx_sdl_render_set_logical_size(
SDL_Renderer *renderer, int w, int h)
{
return SDL_SetRenderLogicalPresentation(
renderer, w, h, SDL_LOGICAL_PRESENTATION_LETTERBOX)
? 0
: -1;
}
static inline int psx_sdl_render_copy(
SDL_Renderer *renderer, SDL_Texture *texture,
const SDL_Rect *src, const SDL_Rect *dst)
{
SDL_FRect srcf;
SDL_FRect dstf;
const SDL_FRect *srcp = NULL;
const SDL_FRect *dstp = NULL;
if (src) {
srcf.x = (float)src->x;
srcf.y = (float)src->y;
srcf.w = (float)src->w;
srcf.h = (float)src->h;
srcp = &srcf;
}
if (dst) {
dstf.x = (float)dst->x;
dstf.y = (float)dst->y;
dstf.w = (float)dst->w;
dstf.h = (float)dst->h;
dstp = &dstf;
}
return SDL_RenderTexture(renderer, texture, srcp, dstp) ? 0 : -1;
}
static inline int psx_sdl_render_set_vsync(
SDL_Renderer *renderer, int enabled)
{
return SDL_SetRenderVSync(renderer, enabled) ? 0 : -1;
}
static inline int psx_sdl_gl_make_current(
SDL_Window *window, SDL_GLContext context)
{
return SDL_GL_MakeCurrent(window, context) ? 0 : -1;
}
static inline int psx_sdl_gl_set_swap_interval(int interval)
{
return SDL_GL_SetSwapInterval(interval) ? 0 : -1;
}
static inline void psx_sdl_gl_get_drawable_size(
SDL_Window *window, int *w, int *h)
{
(void)SDL_GetWindowSizeInPixels(window, w, h);
}
static inline int psx_sdl_get_display_usable_bounds(
SDL_DisplayID display, SDL_Rect *bounds)
{
if (!display) display = SDL_GetPrimaryDisplay();
return SDL_GetDisplayUsableBounds(display, bounds) ? 0 : -1;
}
static inline int psx_sdl_get_current_display_mode(
SDL_DisplayID display, SDL_DisplayMode *mode)
{
if (!display) display = SDL_GetPrimaryDisplay();
const SDL_DisplayMode *current = SDL_GetCurrentDisplayMode(display);
if (!current) return -1;
*mode = *current;
return 0;
}
/* Opt-in high-pixel-density game window. Without it, SDL3 on a Retina Mac
* gives the GL context a drawable in POINTS (half the panel resolution) and
* the compositor stretches it, so any internal resolution above that is
* averaged away. Only set when the player chose an internal resolution. */
#define PSX_SDL_WINDOW_HIGH_DENSITY SDL_WINDOW_HIGH_PIXEL_DENSITY
/* Pixel height of the display a window is on (the primary display when the
* window is NULL), for "Match display". 0 when unknown. */
static inline int psx_sdl_display_pixel_height(SDL_Window *window)
{
SDL_DisplayID display = window ? SDL_GetDisplayForWindow(window) : 0;
if (!display) display = SDL_GetPrimaryDisplay();
if (!display) return 0;
const SDL_DisplayMode *mode = SDL_GetCurrentDisplayMode(display);
if (!mode || mode->h <= 0) return 0;
float density = mode->pixel_density > 0.0f ? mode->pixel_density : 1.0f;
return (int)((float)mode->h * density + 0.5f);
}
static inline const Uint8 *psx_sdl_get_keyboard_state(int *count)
{
return (const Uint8 *)(const void *)SDL_GetKeyboardState(count);
}
static inline SDL_JoystickID psx_sdl_joystick_id_for_index(int index)
{
int count = 0;
SDL_JoystickID result = 0;
SDL_JoystickID *ids = SDL_GetJoysticks(&count);
if (ids && index >= 0 && index < count) result = ids[index];
SDL_free(ids);
return result;
}
static inline int psx_sdl_num_joysticks(void)
{
int count = 0;
SDL_JoystickID *ids = SDL_GetJoysticks(&count);
SDL_free(ids);
return count;
}
static inline bool psx_sdl_is_game_controller(int index)
{
SDL_JoystickID id = psx_sdl_joystick_id_for_index(index);
return id && SDL_IsGamepad(id);
}
static inline SDL_Gamepad *psx_sdl_game_controller_open(int index)
{
SDL_JoystickID id = psx_sdl_joystick_id_for_index(index);
return id ? SDL_OpenGamepad(id) : NULL;
}
static inline SDL_GUID psx_sdl_joystick_get_device_guid(int index)
{
return SDL_GetJoystickGUIDForID(psx_sdl_joystick_id_for_index(index));
}
static inline SDL_JoystickID psx_sdl_joystick_get_device_instance_id(int index)
{
return psx_sdl_joystick_id_for_index(index);
}
static inline void psx_sdl_joystick_get_guid_string(
SDL_GUID guid, char *buffer, int buffer_size)
{
SDL_GUIDToString(guid, buffer, buffer_size);
}
static inline int psx_sdl_cond_wait_timeout(
SDL_Condition *condition, SDL_Mutex *mutex, Sint32 timeout_ms)
{
return SDL_WaitConditionTimeout(condition, mutex, timeout_ms)
? 0
: SDL_MUTEX_TIMEDOUT;
}
#undef SDL_Init
#define SDL_Init psx_sdl_init
#undef SDL_InitSubSystem
#define SDL_InitSubSystem psx_sdl_init_subsystem
#undef SDL_CreateWindow
#define SDL_CreateWindow psx_sdl_create_window
#undef SDL_CreateRenderer
#define SDL_CreateRenderer psx_sdl_create_renderer
#undef SDL_RenderSetLogicalSize
#define SDL_RenderSetLogicalSize psx_sdl_render_set_logical_size
#undef SDL_RenderCopy
#define SDL_RenderCopy psx_sdl_render_copy
#undef SDL_RenderSetVSync
#define SDL_RenderSetVSync psx_sdl_render_set_vsync
#undef SDL_GL_MakeCurrent
#define SDL_GL_MakeCurrent psx_sdl_gl_make_current
#undef SDL_GL_SetSwapInterval
#define SDL_GL_SetSwapInterval psx_sdl_gl_set_swap_interval
#define SDL_GL_GetDrawableSize psx_sdl_gl_get_drawable_size
#undef SDL_GetDisplayUsableBounds
#define SDL_GetDisplayUsableBounds psx_sdl_get_display_usable_bounds
#undef SDL_GetCurrentDisplayMode
#define SDL_GetCurrentDisplayMode psx_sdl_get_current_display_mode
#undef SDL_GetKeyboardState
#define SDL_GetKeyboardState psx_sdl_get_keyboard_state
#define SDL_NumJoysticks psx_sdl_num_joysticks
#undef SDL_IsGameController
#define SDL_IsGameController psx_sdl_is_game_controller
#undef SDL_GameControllerOpen
#define SDL_GameControllerOpen psx_sdl_game_controller_open
#undef SDL_JoystickGetDeviceGUID
#define SDL_JoystickGetDeviceGUID psx_sdl_joystick_get_device_guid
#undef SDL_JoystickGetDeviceInstanceID
#define SDL_JoystickGetDeviceInstanceID psx_sdl_joystick_get_device_instance_id
#undef SDL_JoystickGetGUIDString
#define SDL_JoystickGetGUIDString psx_sdl_joystick_get_guid_string
#undef SDL_CondWaitTimeout
#define SDL_CondWaitTimeout psx_sdl_cond_wait_timeout
#else
#include <SDL.h>
#define PSX_SDL_WINDOW_HIGH_DENSITY SDL_WINDOW_ALLOW_HIGHDPI
static inline int psx_sdl_display_pixel_height(SDL_Window *window)
{
int index = window ? SDL_GetWindowDisplayIndex(window) : 0;
SDL_DisplayMode mode;
if (index < 0) index = 0;
if (SDL_GetCurrentDisplayMode(index, &mode) != 0 || mode.h <= 0) return 0;
return mode.h;
}
#endif