From ca1bae99d39db853dee09a369302ae19e871e672 Mon Sep 17 00:00:00 2001 From: Felipe Prado Date: Sun, 8 Nov 2026 02:16:34 -0300 Subject: [PATCH] Feat(Resolution): added 11520x2160 (48:9) render resolution MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit EN-US - Added Resolution Patch - 11520x2160 (48:9, three 4K screens side by side), Hor+, with single-sample scene buffers and the 3.98 GiB Video Arena (#719) - Added the 48:9 aspect, with that size marked beta, to the launcher, the Insert menu, the in-game Display menu and run.py - Noted in the patch that with the upscaler off it needs more than 12 GB of video memory, as 8K does --- PT-BR - Adicionado Resolution Patch - 11520x2160 (48:9, três telas 4K lado a lado), Hor+, com buffers de cena sem MSAA e Video Arena de 3,98 GiB (#719) - Adicionada a proporção 48:9, com esse tamanho marcado como beta, ao launcher, ao menu Insert, ao menu Display do jogo e ao run.py - Registrado no patch que, com o upscaler desligado, ele precisa de mais de 12 GB de memória de vídeo, como o 8K --- docs/PATCHES.md | 2 +- gpu/shim/spartanport_graphics.h | 4 +-- gpu/shim/spartanport_overlay.cpp | 2 +- gpu/shim/spartanport_settings.cpp | 8 +++--- launcher-rust/src/core/options.rs | 11 +++++++- patches/CUSA01623.xml | 47 +++++++++++++++++++++++++++++++ scripts/graphics_options.py | 4 +-- tests/test_graphics_options.py | 5 ++-- tests/test_graphics_settings.cpp | 8 ++++-- 9 files changed, 75 insertions(+), 16 deletions(-) diff --git a/docs/PATCHES.md b/docs/PATCHES.md index f7d9de5861..abbb76327c 100644 --- a/docs/PATCHES.md +++ b/docs/PATCHES.md @@ -25,7 +25,7 @@ each site), so they also work on other builds whose code matches. | **Bug Fix - Texture Corruption Fix** | Fixes corrupted textures by giving the game a larger video arena (more video memory) | Applied whenever the render resolution is **Native**; a resolution patch replaces it | cuesta4 | | **Skip Any Video With X Button** | Lets you skip any cinematic with Cross | **Game** page, on by default | cuesta4 | | **Skip Intro** | Skips the logos at start | **Game** page, off by default | cuesta4 | -| **Resolution Patch - <size>** (21 sizes, from 480p to 5120 × 2160) | Sets the render resolution and the video arena; the ultrawide sizes also widen the field of view (Hor+) | **Render resolution** (launcher **Performance** page, or the in-game menu); one at a time | kvicken + cuesta4 (480p, 720p, 1440p, 1800p, 4K); SpartanPort (900p and the ultrawide, 16:10 and 4:3 sizes) | +| **Resolution Patch - <size>** (25 sizes, from 480p to 8K, with 11520 × 2160 for three screens side by side) | Sets the render resolution and the video arena; the ultrawide sizes also widen the field of view (Hor+) | **Render resolution** (launcher **Performance** page, or the in-game menu); one at a time | kvicken + cuesta4 (480p, 720p, 1440p, 1800p, 4K); SpartanPort (900p and the ultrawide, 48:9, 16:10 and 4:3 sizes) | | **Frame Rate Patch - 240 FPS** | Raises the engine's frame rate limit and corrects the swing energy so it matches the original 60 Hz simulation | Applied on every start; the chosen **Engine frame rate** (60, 100, 120, 144, 180 or 240) is then written live | kvicken, SpartanPort | | Frame Rate Patch - 120 FPS / 144 FPS / 100 FPS | The same patch with a limit of 120, 144 or 100 | Not used by the launcher any more; kept for other uses of the file | kvicken / kvicken, SpartanPort | diff --git a/gpu/shim/spartanport_graphics.h b/gpu/shim/spartanport_graphics.h index fc89078ee7..ce3fca16a8 100644 --- a/gpu/shim/spartanport_graphics.h +++ b/gpu/shim/spartanport_graphics.h @@ -27,12 +27,12 @@ inline constexpr std::array FpsLimits = {30, 60, 100, 120, 144, 180, 240 /// Game patch suffixes ("Resolution Patch - "); "native" is no resolution patch. /// The ones after 900p are the Ultrawide mod's sizes (#202) and the 16:10 and 4:3 ones, built in as beta /// (scripts/graphics_options.py). -inline constexpr std::array Resolutions = { +inline constexpr std::array Resolutions = { "native", "480p", "720p", "900p", "1440p", "1800p", "4K", "2560x1080", "3440x1440", "3840x1600", "5120x1440", "1920x1200", "2560x1600", "1280x800", "1440x900", "1360x768", "1280x960", "1440x1080", "1920x1440", "2048x1536", "2560x1920", - "5120x2160", "2880x1800", "5120x2880", "8K"}; + "5120x2160", "2880x1800", "5120x2880", "11520x2160", "8K"}; /// The resolutions to choose from: the built-in ones, then those of the enabled mods' patch files /// (SPARTAN_EXTRA_RESOLUTIONS, ";"-separated sizes such as "3440x1440"; run.py sets it at every start). inline std::vector ResolutionNames() { diff --git a/gpu/shim/spartanport_overlay.cpp b/gpu/shim/spartanport_overlay.cpp index 17b2c5382a..b997a985d5 100644 --- a/gpu/shim/spartanport_overlay.cpp +++ b/gpu/shim/spartanport_overlay.cpp @@ -803,7 +803,7 @@ void PerformanceTab() { "1360 × 768 · 16:9", "1280 × 960 · 4:3", "1440 × 1080 · 4:3 (beta)", "1920 × 1440 · 4:3", "2048 × 1536 · 4:3 (beta)", "2560 × 1920 · 4:3 (beta)", "5120 × 2160 · 21:9", - "2880 × 1800 · 16:10 (beta)", "5120 × 2880 · 16:9 (beta)", + "2880 × 1800 · 16:10 (beta)", "5120 × 2880 · 16:9 (beta)", "11520 × 2160 · 48:9 (beta)", "8K · 7680 × 4320 · 16:9 (beta)"}; static_assert(std::size(builtin_labels) == G::Resolutions.size()); const std::vector resolution_names = G::ResolutionNames(); diff --git a/gpu/shim/spartanport_settings.cpp b/gpu/shim/spartanport_settings.cpp index 6c3ef84f1b..3efa206961 100644 --- a/gpu/shim/spartanport_settings.cpp +++ b/gpu/shim/spartanport_settings.cpp @@ -806,9 +806,9 @@ extern "C" uint64_t spartanportgpu_native_setting(unsigned function) { const auto names = SpartanGraphics::ResolutionNames(); const auto aspect = [](const std::string& name) { const auto [w, h] = SpartanGraphics::ResolutionSize(name); - const char* labels[] = {"16:9", "16:10", "21:9", "32:9", "4:3"}; - const double ratios[] = {16.0/9, 1.6, 2.37, 32.0/9, 4.0/3}; - for (unsigned i = 0; i < 5; ++i) if (std::abs(double(w)/h - ratios[i]) < 0.05) + const char* labels[] = {"16:9", "16:10", "21:9", "32:9", "48:9", "4:3"}; + const double ratios[] = {16.0/9, 1.6, 2.37, 32.0/9, 48.0/9, 4.0/3}; + for (unsigned i = 0; i < 6; ++i) if (std::abs(double(w)/h - ratios[i]) < 0.05) return std::string(labels[i]); const int divisor = std::gcd(w, h); return std::to_string(w/divisor) + ":" + std::to_string(h/divisor); @@ -827,7 +827,7 @@ extern "C" uint64_t spartanportgpu_native_setting(unsigned function) { }; if (index <= 1) { std::vector aspects; - for (const char* a : {"16:9", "16:10", "21:9", "32:9", "4:3"}) aspects.emplace_back(a); + for (const char* a : {"16:9", "16:10", "21:9", "32:9", "48:9", "4:3"}) aspects.emplace_back(a); for (const auto& name : names) { const auto a = aspect(name); if (std::find(aspects.begin(), aspects.end(), a) == aspects.end()) aspects.push_back(a); diff --git a/launcher-rust/src/core/options.rs b/launcher-rust/src/core/options.rs index 95b4053b27..55e9a13082 100644 --- a/launcher-rust/src/core/options.rs +++ b/launcher-rust/src/core/options.rs @@ -306,6 +306,7 @@ pub static RESOLUTIONS: &[&str] = &[ "5120x2160", "2880x1800", "5120x2880", + "11520x2160", "8K", ]; @@ -341,6 +342,7 @@ const ASPECTS: &[(&str, f64)] = &[ ("16:10", 16.0 / 10.0), ("21:9", 2.37), ("32:9", 32.0 / 9.0), + ("48:9", 48.0 / 9.0), ("4:3", 4.0 / 3.0), ]; @@ -517,6 +519,12 @@ mod tests { assert_eq!(aspect_of("2560x1080").as_deref(), Some("21:9")); assert_eq!(aspect_of("bogus"), None); assert_eq!(aspect_of("1000x800").as_deref(), Some("5:4")); + // #719: three 4K screens side by side. + assert_eq!(aspect_of("11520x2160").as_deref(), Some("48:9")); + assert_eq!( + resolutions_for("48:9", &strings(RESOLUTIONS)), + ["11520x2160"] + ); } #[test] @@ -577,7 +585,7 @@ mod tests { } let names: HashSet<_> = RESOLUTIONS.iter().collect(); assert_eq!(names.len(), RESOLUTIONS.len()); - assert_eq!(RESOLUTIONS.len(), 25); + assert_eq!(RESOLUTIONS.len(), 26); } #[test] @@ -609,6 +617,7 @@ mod tests { "2560x1920", "2880x1800", "5120x2880", + "11520x2160", "8K" ] ); diff --git a/patches/CUSA01623.xml b/patches/CUSA01623.xml index a79bb9728e..2f9ab59379 100644 --- a/patches/CUSA01623.xml +++ b/patches/CUSA01623.xml @@ -497,6 +497,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/graphics_options.py b/scripts/graphics_options.py index 4cdf84a76f..9400e0c178 100644 --- a/scripts/graphics_options.py +++ b/scripts/graphics_options.py @@ -15,7 +15,7 @@ '2560x1080', '3440x1440', '3840x1600', '5120x1440', '1920x1200', '2560x1600', '1280x800', '1440x900', '1360x768', '1280x960', '1440x1080', '1920x1440', '2048x1536', '2560x1920', - '5120x2160', '2880x1800', '5120x2880', '8K') + '5120x2160', '2880x1800', '5120x2880', '11520x2160', '8K') # The ultrawide, 16:10 and 4:3 sizes of the Ultrawide mod (#202), built in: beta until more screens confirm # them, except the ones tested in game (the maintainer's own tests and players' reports in issues #180, #505 # and #519): 2560x1080, 1440x1080, 2048x1536, 2560x1920 and 8K are still beta. @@ -24,7 +24,7 @@ BETA_RESOLUTIONS = tuple(r for r in RESOLUTIONS[7:] if r not in TESTED_RESOLUTIONS) SIZES = {'native': (1920, 1080), '480p': (854, 480), '720p': (1280, 720), '900p': (1600, 900), '1440p': (2560, 1440), '1800p': (3200, 1800), '4K': (3840, 2160), '8K': (7680, 4320)} -ASPECTS = (('16:9', 16 / 9), ('16:10', 16 / 10), ('21:9', 2.37), ('32:9', 32 / 9), ('4:3', 4 / 3)) +ASPECTS = (('16:9', 16 / 9), ('16:10', 16 / 10), ('21:9', 2.37), ('32:9', 32 / 9), ('48:9', 48 / 9), ('4:3', 4 / 3)) ENGINE_FPS = (60, 100, 120, 144, 180, 240) # The Steam Deck's DMI names: LCD (Jupiter) and OLED (Galileo), both with a 1280 × 800 screen (#855). STEAM_DECK_PRODUCTS = ('Jupiter', 'Galileo') diff --git a/tests/test_graphics_options.py b/tests/test_graphics_options.py index c218ae2ed5..8f22a20c9f 100644 --- a/tests/test_graphics_options.py +++ b/tests/test_graphics_options.py @@ -344,8 +344,9 @@ def test_aspect_of_a_resolution(self): self.assertEqual(g.aspect_of('1280x1024'), '5:4') # a mod's size outside the named ones def test_aspects_in_a_fixed_order_with_the_others_last(self): - self.assertEqual(g.aspects(g.RESOLUTIONS), ['16:9', '16:10', '21:9', '32:9', '4:3']) - self.assertEqual(g.aspects(g.resolutions(('1280x1024',))), ['16:9', '16:10', '21:9', '32:9', '4:3', '5:4']) + self.assertEqual(g.aspects(g.RESOLUTIONS), ['16:9', '16:10', '21:9', '32:9', '48:9', '4:3']) + self.assertEqual(g.resolutions_for('48:9', g.RESOLUTIONS), ['11520x2160']) # #719 + self.assertEqual(g.aspects(g.resolutions(('1280x1024',))), ['16:9', '16:10', '21:9', '32:9', '48:9', '4:3', '5:4']) def test_resolutions_of_an_aspect_smallest_first_native_on_top(self): self.assertEqual(g.resolutions_for('16:9', g.RESOLUTIONS), diff --git a/tests/test_graphics_settings.cpp b/tests/test_graphics_settings.cpp index 452ad59198..0f8c89f6e9 100644 --- a/tests/test_graphics_settings.cpp +++ b/tests/test_graphics_settings.cpp @@ -230,10 +230,12 @@ int main() { assert(G::ParseMonitor(std::to_string(std::numeric_limits::max() + 1LL)) == G::PrimaryMonitor); assert(G::ParseMonitor("99999999999999999999") == G::PrimaryMonitor); assert(G::ParseFpsLimit(240) == 240 && G::ParseFpsLimit(144) == 144 && G::ParseFpsLimit(180) == 180 && G::ParseFpsLimit(100) == 100 && G::ParseFpsLimit(75) == 0); - assert(G::ParseResolution("900p") == 3 && G::ParseResolution("1440p") == 4 && G::ParseResolution("8K") == 24); + assert(G::ParseResolution("900p") == 3 && G::ParseResolution("1440p") == 4 && G::ParseResolution("8K") == 25); // #964, #1038: 2880x1800 (16:10) and 5120x2880 (5K, 16:9). assert(G::ParseResolution("2880x1800") == 22 && G::ParseResolution("5120x2880") == 23); assert(G::ResolutionSize("5120x2880") == std::make_pair(5120, 2880)); + // #719: 11520x2160 (48:9, three 4K screens). + assert(G::ParseResolution("11520x2160") == 24 && G::ResolutionSize("11520x2160") == std::make_pair(11520, 2160)); // #238: 900p (1600x900) sits between 720p and 1440p in the built-in list. assert(G::Resolutions[3] == "900p" && G::Resolutions[2] == "720p" && G::Resolutions[4] == "1440p"); // The window's starting size (window.cpp): built-in names, a mod's size, native otherwise. @@ -346,13 +348,13 @@ int main() { // ones and keep their names through a load and a save; repeated or built-in names are not added twice. SetEnvironment("SPARTAN_EXTRA_RESOLUTIONS", "3840x1080;2880x1200;;2880x1200;4K;3440x1440"); const auto names = G::ResolutionNames(); - assert(names.size() == 27 && names[24] == "8K" && names[25] == "3840x1080" && names[26] == "2880x1200"); + assert(names.size() == 28 && names[25] == "8K" && names[26] == "3840x1080" && names[27] == "2880x1200"); { std::ofstream out(path); out << "render_resolution=2880x1200\n"; } SpartanSettings::Load(); - assert(v.render_resolution == 26); + assert(v.render_resolution == 27); SpartanSettings::Save(); assert(Read(path).find("render_resolution=2880x1200\n") != std::string::npos); SetEnvironment("SPARTAN_EXTRA_RESOLUTIONS", ""); // the mod is off: its resolution is unknown, so native