From 7d06942eb0e1cb0a6d3bc4f18f22decaca271402 Mon Sep 17 00:00:00 2001 From: Shokunin <30949000+tetrisgm@users.noreply.github.com> Date: Wed, 7 Oct 2026 21:39:20 -0700 Subject: [PATCH] Dynamic resolution floor "display": the output's own lines dynamic_resolution_min = "display" (game.toml, settings.toml, PSX_DYNRES_MIN) floors the controller at the selected internal resolution without the supersample factor, so with supersample > 1 it only trades supersampling and never renders below the output. psx_dynres_floor_scale in internal_resolution.h, tested in test_internal_resolution. OpenBIOS emitter stamp regenerated (config_loader changed). Co-Authored-By: Claude Opus 5.5 --- docs/config_schema.md | 15 +++++++++++++++ generated/OpenBIOS.emitter.sha | 2 +- recompiler/src/config_loader.cpp | 11 ++++------- runtime/include/internal_resolution.h | 14 ++++++++++++++ runtime/src/main.cpp | 16 +++++++++++----- runtime/tests/test_internal_resolution.cpp | 7 +++++++ 6 files changed, 52 insertions(+), 13 deletions(-) diff --git a/docs/config_schema.md b/docs/config_schema.md index f5132005..187dd648 100644 --- a/docs/config_schema.md +++ b/docs/config_schema.md @@ -622,6 +622,21 @@ ordered-grid SSAA of everything, 2D included. Native and the legacy its ceiling and steps under it as before. `PSX_SUPERSAMPLE=` overrides it for one run. +To make supersampling the elastic part only, set the dynamic resolution floor +to the output: `dynamic_resolution_min = "display"` (also `PSX_DYNRES_MIN=display` +and settings.toml) means the selected internal resolution *without* the +supersample factor, i.e. the monitor's own lines under Match display or the +preset's lines otherwise. Dynamic resolution then steps between the +supersampled ceiling and the output and never renders below it: + +```toml +[video] +internal_resolution = "display" +supersample = 2.0 +dynamic_resolution = true +dynamic_resolution_min = "display" +``` + `antialiasing_mode` filters the composed game image on OpenGL: after the game quad (4:3, native-wide, Smooth motion and generated frames alike) and before hold-last capture, the OSD, screenshots and the swap. It reads only output diff --git a/generated/OpenBIOS.emitter.sha b/generated/OpenBIOS.emitter.sha index 587a78bd..abec3920 100644 --- a/generated/OpenBIOS.emitter.sha +++ b/generated/OpenBIOS.emitter.sha @@ -1 +1 @@ -be426241e46d80c1d2ca130c73f12b453e31df1c3f20d252196f3c2cecc9f4b6 +0a499298b6a38126d2945801a6e24786e7bb1b83a89248de70d8b28bfdc287c8 diff --git a/recompiler/src/config_loader.cpp b/recompiler/src/config_loader.cpp index 75da3feb..25b7aae7 100644 --- a/recompiler/src/config_loader.cpp +++ b/recompiler/src/config_loader.cpp @@ -715,8 +715,7 @@ static RuntimeConfig parse_runtime_block(const toml::value& cfg, const fs::path& int value = 0; bool ok = false; if (ir.is_string()) { - ok = psx_ir_parse(ir.as_string().str.c_str(), &value) != 0 && - value != PSX_IR_DISPLAY; + ok = psx_ir_parse(ir.as_string().str.c_str(), &value) != 0; } else if (ir.is_integer()) { const auto n = ir.as_integer(); ok = n >= PSX_IR_MIN_LINES && n <= PSX_IR_MAX_LINES; @@ -725,7 +724,7 @@ static RuntimeConfig parse_runtime_block(const toml::value& cfg, const fs::path& if (!ok) { throw std::runtime_error( "[video] dynamic_resolution_min must be native, 720p, 1080p, " - "1440p, 4k, 5k, 8k, or a number of lines"); + "1440p, 4k, 5k, 8k, display, or a number of lines"); } rt.video_dynamic_resolution_min = value; } @@ -2801,8 +2800,7 @@ UserSettings load_user_settings(const fs::path& path) { const toml::value& ir = toml::find(v, "dynamic_resolution_min"); int value = 0; if (ir.is_string()) { - if (psx_ir_parse(ir.as_string().str.c_str(), &value) && - value != PSX_IR_DISPLAY) { + if (psx_ir_parse(ir.as_string().str.c_str(), &value)) { s.dynamic_resolution_min = value; s.has_dynamic_resolution_min = true; } } else if (ir.is_integer()) { @@ -3211,8 +3209,7 @@ bool save_user_settings(const fs::path& path, const UserSettings& s) { if (s.has_dynamic_resolution) f << "dynamic_resolution = " << (s.dynamic_resolution ? "true" : "false") << "\n"; if (s.has_dynamic_resolution_min && - psx_ir_value_valid(s.dynamic_resolution_min) && - s.dynamic_resolution_min != PSX_IR_DISPLAY) { + psx_ir_value_valid(s.dynamic_resolution_min)) { const char* id = psx_ir_id_for(s.dynamic_resolution_min); if (id) f << "dynamic_resolution_min = \"" << id << "\"\n"; diff --git a/runtime/include/internal_resolution.h b/runtime/include/internal_resolution.h index c56cfeb2..50e61016 100644 --- a/runtime/include/internal_resolution.h +++ b/runtime/include/internal_resolution.h @@ -241,3 +241,17 @@ static inline PsxIrAdopted psx_ir_adopt_launcher(int has_row, int preset, } return a; } + +/* ---- Dynamic resolution floor ([video] dynamic_resolution_min) ------------- + * A preset or line count resolves as usual. "display" (PSX_IR_DISPLAY) is the + * output's own lines: the selected internal resolution WITHOUT the supersample + * factor (Match display = the monitor's pixel height). With supersample > 1 + * the controller then trades only supersampling and never drops below the + * output. Clamped to [1, ceiling]. */ +static inline int psx_dynres_floor_scale(int min_value, int selected_ir, int ref_lines, + int display_px_h, int ceiling) { + const int v = (min_value == PSX_IR_DISPLAY && selected_ir != PSX_IR_UNSET) + ? selected_ir : min_value; + int s = psx_resolve_internal_scale(v, ref_lines, display_px_h, ceiling); + return s > ceiling ? ceiling : s; +} diff --git a/runtime/src/main.cpp b/runtime/src/main.cpp index ca6c313b..a3c6788a 100644 --- a/runtime/src/main.cpp +++ b/runtime/src/main.cpp @@ -9334,9 +9334,15 @@ static void dynres_setup(void) { "dual raster)\n"); return; } - int floor_s = psx_resolve_internal_scale(dynres_min_value(), g_video_ref_lines, - psx_sdl_display_pixel_height(nullptr), - ceiling); + /* Floor "display": the output's own lines, i.e. the selected internal + * resolution without the supersample factor (Match display = the + * monitor's pixel height). Dynamic resolution then only gives back + * supersampling and never renders below the output. */ + int floor_s = psx_dynres_floor_scale(dynres_min_value(), + effective_internal_resolution(), + g_video_ref_lines, + psx_sdl_display_pixel_height(nullptr), + ceiling); if (floor_s > ceiling) floor_s = ceiling; DynresParams params; dynres_default_params(¶ms); @@ -18089,9 +18095,9 @@ session_reboot: g_video_dynres_env = (*e && *e != '0') ? 1 : 0; if (const char* e = std::getenv("PSX_DYNRES_MIN")) { int v = 0; - if (psx_ir_parse(e, &v) && v != PSX_IR_DISPLAY) g_video_dynres_min_env = v; + if (psx_ir_parse(e, &v)) g_video_dynres_min_env = v; else std::fprintf(stdout, "psxrecomp: PSX_DYNRES_MIN=%s not understood " - "(native, 720p, 1080p, 1440p, 4k, 5k, 8k, or lines)\n", e); + "(native, 720p, 1080p, 1440p, 4k, 5k, 8k, display, or lines)\n", e); } { /* Per-backend ceiling. OpenGL allocates its hr surface at context init diff --git a/runtime/tests/test_internal_resolution.cpp b/runtime/tests/test_internal_resolution.cpp index 212e193d..25b04cdd 100644 --- a/runtime/tests/test_internal_resolution.cpp +++ b/runtime/tests/test_internal_resolution.cpp @@ -206,6 +206,13 @@ int main() { expect("ss parse 4", psx_ss_parse_milli("4"), 4000); expect("ss reject 0.5", psx_ss_parse_milli("0.5"), 0); expect("ss reject 5", psx_ss_parse_milli("5"), 0); + // Dynamic resolution floor "display": the output's lines, no supersample. + expect("floor display 1080 MD", psx_dynres_floor_scale(PSX_IR_DISPLAY, PSX_IR_DISPLAY, 240, 1080, 9), 5); + expect("floor display 1440 MD", psx_dynres_floor_scale(PSX_IR_DISPLAY, PSX_IR_DISPLAY, 240, 1440, 12), 6); + expect("floor display preset", psx_dynres_floor_scale(PSX_IR_DISPLAY, 1080, 240, 2160, 9), 5); + expect("floor display unset", psx_dynres_floor_scale(PSX_IR_DISPLAY, PSX_IR_UNSET, 240, 1080, 9), 5); + expect("floor display clamp", psx_dynres_floor_scale(PSX_IR_DISPLAY, PSX_IR_DISPLAY, 240, 2160, 4), 4); + expect("floor 720p", psx_dynres_floor_scale(720, PSX_IR_DISPLAY, 240, 1080, 9), 3); expect("ss reject junk", psx_ss_parse_milli("2y"), 0); expect("ss 1.0 is the plain resolve", psx_resolve_internal_scale_ss(PSX_IR_DISPLAY, 240, 1080, 32, 1000), 5);