Merge remote-tracking branch 'origin/feat/supersample-display-policy' into local/base-fixes

This commit is contained in:
Shokunin
2026-10-22 12:45:53 -07:00
8 changed files with 79 additions and 14 deletions
+8 -1
View File
@@ -702,16 +702,23 @@ player's `settings.toml`, and a game may ship a default the same way:
[video]
internal_resolution = "4k" # native | 720p | 1080p | 1440p | 4k | 5k | 8k | display, or a number of lines
resolution_reference_lines = 240 # game.toml only: the title's usual display height (120..1024)
supersample_disable_at_lines = 0 # game.toml only: supersample 1.0 at/above this display height (0 = off, 240..8640)
match_display_max_lines = 0 # game.toml only: cap on the display height Match display uses (0 = none, 240..8640)
```
`match_display_max_lines` caps the monitor height that Match display, its
dynamic-resolution floor (`display`) and graphics-preset detection resolve
against. With no cap, an 8K panel asks 18x (27x with `supersample = 1.5`), which is
against. It also caps the final Match display target after supersampling,
so a larger saved factor cannot exceed it. Fixed presets keep their own target.
With no cap, an 8K panel asks 18x (27x with `supersample = 1.5`), which is
past the full-VRAM surface. The high-resolution window then engages, and
dynamic resolution cannot step there. A title whose GPU budget is set for
4K lines sets 2160.
`supersample_disable_at_lines` uses the physical monitor height before the cap.
At or above its threshold the effective factor is 1.0; the saved factor is
retained for smaller monitors. Zero preserves the default behaviour.
A preset is a target height. The runtime renders at the integer scale
`S = ceil(target / resolution_reference_lines)`: at the default 240 lines,
720p is 3x, 1080p is 5x (1200 lines, area-resolved to 1080), 1440p 6x, 4K 9x,
+1 -1
View File
@@ -1 +1 @@
75a8d9834efb1c994169a939fbe043073b1b4b133bfe7b0235b80338af7f2f08
61bf4f6d29680623d215fbe407958408cab0a1e08dbda3c62baa25e607d63b7b
+7
View File
@@ -780,6 +780,13 @@ static RuntimeConfig parse_runtime_block(const toml::value& cfg, const fs::path&
}
rt.video_match_display_max_lines = static_cast<int>(n);
}
if (video.contains("supersample_disable_at_lines")) {
const auto n = toml::find<int64_t>(video, "supersample_disable_at_lines");
if (n != 0 && (n < 240 || n > 8640))
throw std::runtime_error(fmt::format(
"[video] supersample_disable_at_lines must be 0 or 240..8640: {}", n));
rt.video_supersample_disable_at_lines = static_cast<int>(n);
}
if (video.contains("window_width")) {
const auto n = toml::find<int64_t>(video, "window_width");
if (n < 640 || n > 7680) {
+2
View File
@@ -472,6 +472,8 @@ struct RuntimeConfig {
/* [video] match_display_max_lines: cap on the display height Match
* display (and its dynamic-resolution floor) resolves against; 0 = none. */
int video_match_display_max_lines = 0;
/* Disable target supersampling on displays at least this tall (0 = off). */
int video_supersample_disable_at_lines = 0;
// Optional initial window width declared by the title profile. Zero keeps
// the historical fit-to-display behavior; player settings may override it.
@@ -226,14 +226,17 @@ static void test_internal_resolution_game_toml() {
"resolution_reference_lines defaults to 240");
check(gc.runtime.video_match_display_max_lines == 0,
"match_display_max_lines defaults to 0 (no cap)");
check(gc.runtime.video_supersample_disable_at_lines == 0, "display SS policy defaults off");
fs::remove(p);
p = write_game_toml("psxrecomp_ir_match_cap.toml",
"[video]\n"
"internal_resolution = \"display\"\n"
"match_display_max_lines = 2160\n");
"match_display_max_lines = 2160\n"
"supersample_disable_at_lines = 2160\n");
gc = PSXRecompV4::load_game_config(p);
check(gc.runtime.video_match_display_max_lines == 2160, "match_display_max_lines parses");
check(gc.runtime.video_supersample_disable_at_lines == 2160, "display SS policy parses");
fs::remove(p);
p = write_game_toml("psxrecomp_ir_4k.toml",
@@ -257,6 +260,7 @@ static void test_internal_resolution_game_toml() {
"internal_resolution = 1\n",
"resolution_reference_lines = 50\n",
"match_display_max_lines = 100\n",
"supersample_disable_at_lines = 100\n",
"supersampling = 33\n" }) {
p = write_game_toml("psxrecomp_ir_bad.toml", std::string("[video]\n") + bad);
bool rejected = false;
+19
View File
@@ -143,6 +143,13 @@ static inline int psx_ss_parse_milli(const char *s) {
return (int)m;
}
/* Opt-in display policy; keep the saved factor intact for smaller displays. */
static inline int psx_ir_display_supersample(int ss_milli, int display_px_h,
int disable_at_lines) {
return disable_at_lines > 0 && display_px_h >= disable_at_lines
? PSX_SS_MILLI_MIN : ss_milli;
}
/* psx_resolve_internal_scale with the supersample factor on the target. */
static inline int psx_resolve_internal_scale_ss(int value, int ref_lines,
int display_px_h, int s_max,
@@ -160,6 +167,18 @@ static inline int psx_resolve_internal_scale_ss(int value, int ref_lines,
return psx_resolve_internal_scale(value, ref_lines, display_px_h, s_max);
}
/* A Match display cap bounds the final target, including supersampling.
* Fixed presets and the legacy integer scale retain their own ceilings. */
static inline int psx_resolve_internal_scale_ss_cap(int value, int ref_lines,
int display_px_h, int s_max, int ss_milli, int max_display_lines) {
int s = psx_resolve_internal_scale_ss(value, ref_lines, display_px_h, s_max, ss_milli);
if (value == PSX_IR_DISPLAY && max_display_lines > 0) {
int cap = psx_resolve_internal_scale(max_display_lines, ref_lines, 0, s_max);
if (s > cap) s = cap;
}
return s;
}
/* A legacy [video] supersampling factor as a value the launcher can show:
* the preset that resolves to the same scale (1 -> Native, 3 -> 720p,
* 5 -> 1080p, 6 -> 1440p, 9 -> 4K, 12 -> 5K, 18 -> 8K at 240 lines),
+22 -11
View File
@@ -1272,6 +1272,11 @@ static int g_video_ss_milli = 1000; /* [video] supersample / PSX_SUPER
* full-VRAM surface, where the high-resolution window engages and dynamic
* resolution cannot step; a title can cap it at, say, 4K lines. */
static int g_video_match_max_lines = 0;
static int g_video_ss_disable_at_lines = 0;
static int display_supersample(SDL_Window *window) {
return psx_ir_display_supersample(g_video_ss_milli,
psx_sdl_display_pixel_height(window), g_video_ss_disable_at_lines);
}
static int match_display_px_h(SDL_Window *window) {
const int h = psx_sdl_display_pixel_height(window);
return (g_video_match_max_lines > 0 && h > g_video_match_max_lines)
@@ -1454,9 +1459,8 @@ static int effective_internal_resolution(void) {
static void apply_internal_resolution(int display_px_h) {
const int preset = effective_internal_resolution();
if (preset == PSX_IR_UNSET) return;
g_video_scale = psx_resolve_internal_scale_ss(preset, g_video_ref_lines,
display_px_h, video_scale_ceiling(),
g_video_ss_milli);
g_video_scale = psx_resolve_internal_scale_ss_cap(preset, g_video_ref_lines,
display_px_h, video_scale_ceiling(), display_supersample(nullptr), g_video_match_max_lines);
}
/* The value the launcher row starts on: the preset, or the legacy factor shown
@@ -17860,6 +17864,7 @@ int main(int argc, char** argv) {
g_video_internal_res = gc.runtime.video_internal_resolution;
g_video_ref_lines = gc.runtime.video_resolution_reference_lines;
g_video_match_max_lines = gc.runtime.video_match_display_max_lines;
g_video_ss_disable_at_lines = gc.runtime.video_supersample_disable_at_lines;
g_video_dynres = gc.runtime.video_dynamic_resolution ? 1 : 0;
g_video_dynres_min = gc.runtime.video_dynamic_resolution_min;
if (gc.runtime.video_window_width > 0) {
@@ -20353,9 +20358,10 @@ session_reboot:
else if (!std::strcmp(e, "fxaa_hq")) g_video_post_aa = GL_POST_AA_FXAA_HQ;
else std::fprintf(stdout, "psxrecomp: PSX_AA_MODE=%s not understood (off, fxaa, fxaa_hq)\n", e);
}
if (g_video_ss_milli != 1000)
const int effective_ss = display_supersample(nullptr);
if (!g_video_ss_disable_at_lines && effective_ss != 1000)
std::fprintf(stdout, "psxrecomp: supersample %.3gx on the internal-resolution target\n",
g_video_ss_milli / 1000.0);
effective_ss / 1000.0);
apply_internal_resolution(match_display_px_h(nullptr));
if (g_video_scale < 1) g_video_scale = 1;
if (const char* e = std::getenv("PSX_DYNRES"))
@@ -20996,17 +21002,22 @@ session_reboot:
/* Match display: the monitor's pixel height, now that the window says
* which monitor. glb_set_scale only records the request; the hr
* surface is allocated at context init below. */
if (effective_internal_resolution() == PSX_IR_DISPLAY && g_video_scale_applies) {
const int ir = effective_internal_resolution();
if (g_video_scale_applies && ir != PSX_IR_UNSET &&
(ir == PSX_IR_DISPLAY || g_video_ss_disable_at_lines > 0)) {
const int dh = match_display_px_h(sdl_window);
const int s = psx_resolve_internal_scale_ss(PSX_IR_DISPLAY, g_video_ref_lines,
dh, GL_MAX_INTERNAL_SCALE,
g_video_ss_milli);
const int ss = display_supersample(sdl_window);
const int s = psx_resolve_internal_scale_ss_cap(ir, g_video_ref_lines,
dh, GL_MAX_INTERNAL_SCALE, ss, g_video_match_max_lines);
gr_set_scale(s);
g_video_requested_scale = s;
std::fprintf(stdout, "psxrecomp: internal resolution Match display: "
"%d px -> %dx requested\n", dh, s);
const char *label = psx_ir_label_for(ir);
std::fprintf(stdout, "psxrecomp: internal resolution %s: "
"%d px -> %dx requested (supersample %.3gx)\n",
label ? label : "custom", dh, s, ss / 1000.0);
}
gl_renderer_set_swap_interval(present_effective_swap_interval()); /* applied at context init */
/* Dynamic resolution: the surfaces are allocated at the scale above
* (the ceiling) and the level steps under it (dynres_setup). */
#ifndef PSX_NO_DEBUG_TOOLS
@@ -222,6 +222,21 @@ int main() {
expect("ss clamps to the ceiling", psx_resolve_internal_scale_ss(4320, 240, 0, 32, 4000), 32);
expect("ss unknown display stays 1", psx_resolve_internal_scale_ss(PSX_IR_DISPLAY, 240, 0, 32, 2000), 1);
expect("ss policy opt-out", psx_ir_display_supersample(1500, 4320, 0), 1500);
expect("ss below threshold", psx_ir_display_supersample(1500, 2159, 2160), 1500);
expect("ss at threshold", psx_ir_display_supersample(1500, 2160, 2160), 1000);
expect("ss on 8K", psx_ir_display_supersample(1500, 4320, 2160), 1000);
expect("ss unknown display", psx_ir_display_supersample(1500, 0, 2160), 1500);
expect("cap includes SS below threshold", psx_resolve_internal_scale_ss_cap(
PSX_IR_DISPLAY, 240, 1440, 32, 4000, 4320), 18);
expect("cap below 8K", psx_resolve_internal_scale_ss_cap(
PSX_IR_DISPLAY, 240, 1080, 32, 1500, 4320), 7);
expect("8K capped after SS", psx_resolve_internal_scale_ss_cap(
PSX_IR_DISPLAY, 240, 8640, 32, 4000, 4320), 18);
expect("fixed preset keeps own ceiling", psx_resolve_internal_scale_ss_cap(
4320, 240, 8640, 32, 4000, 4320), 32);
expect("cap opt-out", psx_resolve_internal_scale_ss_cap(
PSX_IR_DISPLAY, 240, 4320, 32, 1500, 0), 27);
launcher_round_trips();
if (g_failures) return 1;