Files
psxrecomp/runtime/tests/test_internal_resolution.cpp
T
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

84 lines
3.8 KiB
C++

// internal_resolution.h: preset ids, the value encoding shared with the
// launcher, and the integer scale each preset asks for. Presets x reference
// 240/480 x backend maxima (the GL clamp at 16384 is 16x; 18x needs the
// windowed high-resolution surface or a 32768-limit GPU).
#include "internal_resolution.h"
#include <cstdio>
#include <cstring>
static int g_failures = 0;
static void expect(const char* what, long long got, long long want) {
if (got != want) {
std::printf("FAIL %s: got %lld want %lld\n", what, got, want);
++g_failures;
}
}
int main() {
// Presets at the NTSC 240-line reference (R4 races are 320x240).
struct { const char* id; int s240; int s480; } table[] = {
{ "native", 1, 1 }, { "720p", 3, 2 }, { "1080p", 5, 3 }, { "1440p", 6, 3 },
{ "4k", 9, 5 }, { "5k", 12, 6 }, { "8k", 18, 9 },
};
for (const auto& t : table) {
int v = 0;
expect(t.id, psx_ir_parse(t.id, &v), 1);
expect(t.id, psx_resolve_internal_scale(v, 240, 0, 32), t.s240);
expect(t.id, psx_resolve_internal_scale(v, 480, 0, 32), t.s480);
expect("id round trip", std::strcmp(psx_ir_id_for(v), t.id), 0);
}
// Backend maxima: software/Vulkan 4, full-VRAM GL at a 16384 limit 16.
int v8k = 0;
psx_ir_parse("8K", &v8k);
expect("8k sw", psx_resolve_internal_scale(v8k, 240, 0, 4), 4);
expect("8k gl16", psx_resolve_internal_scale(v8k, 240, 0, 16), 16);
expect("8k lines", v8k, 4320);
// Match display: the monitor's pixel height, 1 until it is known.
int vd = 0;
expect("display parse", psx_ir_parse("display", &vd), 1);
expect("display value", vd, PSX_IR_DISPLAY);
expect("display unknown", psx_resolve_internal_scale(vd, 240, 0, 32), 1);
expect("display 1080", psx_resolve_internal_scale(vd, 240, 1080, 32), 5);
expect("display 1964 (14in MBP)", psx_resolve_internal_scale(vd, 240, 1964, 32), 9);
expect("display 2160", psx_resolve_internal_scale(vd, 240, 2160, 32), 9);
expect("display 2880 (5K)", psx_resolve_internal_scale(vd, 240, 2880, 32), 12);
expect("display 4320 (8K)", psx_resolve_internal_scale(vd, 240, 4320, 32), 18);
expect("display id", std::strcmp(psx_ir_id_for(vd), "display"), 0);
// Integer line counts and rejects.
int v = 0;
expect("lines parse", psx_ir_parse("1600", &v), 1);
expect("lines value", v, 1600);
expect("lines scale", psx_resolve_internal_scale(v, 240, 0, 32), 7);
expect("custom has no id", psx_ir_id_for(1600) == nullptr, 1);
v = 77;
expect("reject junk", psx_ir_parse("4kk", &v), 0);
expect("reject empty", psx_ir_parse("", &v), 0);
expect("reject 1", psx_ir_parse("1", &v), 0);
expect("reject huge", psx_ir_parse("99999", &v), 0);
expect("untouched on reject", v, 77);
expect("unset scale", psx_resolve_internal_scale(PSX_IR_UNSET, 240, 0, 32), 1);
expect("bad reference defaults to 240", psx_resolve_internal_scale(2160, 0, 0, 32), 9);
// Legacy supersampling shown as a preset when one resolves to it.
expect("legacy 1", psx_ir_from_supersampling(1, 240), PSX_IR_NATIVE);
expect("legacy 2", psx_ir_from_supersampling(2, 240), 480);
expect("legacy 3", psx_ir_from_supersampling(3, 240), 720);
expect("legacy 4", psx_ir_from_supersampling(4, 240), 960);
expect("legacy 5", psx_ir_from_supersampling(5, 240), 1080);
expect("legacy 9", psx_ir_from_supersampling(9, 240), 2160);
expect("legacy 18", psx_ir_from_supersampling(18, 240), 4320);
// And the legacy value resolves back to the same scale.
for (int n = 1; n <= 32; n++)
expect("legacy round trip",
psx_resolve_internal_scale(psx_ir_from_supersampling(n, 240), 240, 0, 32), n);
if (g_failures) return 1;
std::printf("internal_resolution: all checks passed\n");
return 0;
}