feat(menu): the Aspect ratio and Output resolution from the screen's display modes #2566
Closed
nullifyshot wants to merge 11 commits from
nullifyshot/feat/display-modes into main
pull from: nullifyshot/feat/display-modes
merge into: MagicBeanDev:main
MagicBeanDev:main
MagicBeanDev:github/pr/2893
MagicBeanDev:github/pr/2890
MagicBeanDev:github/pr/2887
MagicBeanDev:github/pr/2878
MagicBeanDev:github/pr/2875
MagicBeanDev:github/pr/2874
MagicBeanDev:github/pr/2873
MagicBeanDev:github/pr/2871
MagicBeanDev:github/pr/2756
MagicBeanDev:github/pr/2897
MagicBeanDev:github/pr/2896
MagicBeanDev:github/pr/2718
MagicBeanDev:github/pr/2842
MagicBeanDev:github/pr/2691
MagicBeanDev:github/pr/2895
MagicBeanDev:github/pr/2743
MagicBeanDev:github/pr/2765
MagicBeanDev:github/pr/2708
MagicBeanDev:github/pr/2732
MagicBeanDev:github/pr/2755
MagicBeanDev:github/pr/2817
MagicBeanDev:github/pr/2876
MagicBeanDev:release-please--branches--main
MagicBeanDev:release-please--branches--main--release-notes
MagicBeanDev:github/pr/2778
MagicBeanDev:github/pr/2888
MagicBeanDev:github/pr/2866
MagicBeanDev:github/pr/2877
MagicBeanDev:github/pr/2800
MagicBeanDev:github/pr/2829
MagicBeanDev:github/pr/2850
MagicBeanDev:github/pr/2889
MagicBeanDev:github/pr/2882
MagicBeanDev:github/pr/2860
MagicBeanDev:github/pr/2849
MagicBeanDev:github/pr/2886
MagicBeanDev:github/pr/2794
MagicBeanDev:github/pr/2840
MagicBeanDev:github/pr/2830
MagicBeanDev:github/pr/2834
MagicBeanDev:github/pr/2719
MagicBeanDev:github/pr/2742
MagicBeanDev:github/pr/2741
MagicBeanDev:github/pr/2820
MagicBeanDev:github/pr/2771
MagicBeanDev:github/pr/2859
MagicBeanDev:github/pr/2858
MagicBeanDev:github/pr/2823
MagicBeanDev:github/pr/2757
MagicBeanDev:github/pr/2769
MagicBeanDev:github/pr/2844
MagicBeanDev:github/pr/2843
MagicBeanDev:github/pr/2673
MagicBeanDev:github/pr/2855
MagicBeanDev:github/pr/2739
MagicBeanDev:github/pr/2807
MagicBeanDev:github/pr/2851
MagicBeanDev:github/pr/2789
MagicBeanDev:github/pr/2814
MagicBeanDev:github/pr/2818
MagicBeanDev:github/pr/2774
MagicBeanDev:github/pr/2772
MagicBeanDev:github/pr/2750
MagicBeanDev:github/pr/2786
MagicBeanDev:github/pr/2785
MagicBeanDev:github/pr/2782
MagicBeanDev:github/pr/2779
MagicBeanDev:github/pr/2775
MagicBeanDev:github/pr/2706
MagicBeanDev:github/pr/2766
MagicBeanDev:github/pr/2747
MagicBeanDev:github/pr/2759
MagicBeanDev:github/pr/2745
MagicBeanDev:github/pr/2740
MagicBeanDev:github/pr/2733
MagicBeanDev:github/pr/2725
MagicBeanDev:github/pr/2727
MagicBeanDev:github/pr/2728
MagicBeanDev:github/pr/2729
MagicBeanDev:github/pr/2721
MagicBeanDev:github/pr/2722
MagicBeanDev:github/pr/2715
MagicBeanDev:github/pr/2714
MagicBeanDev:github/pr/2707
MagicBeanDev:github/pr/2703
MagicBeanDev:github/pr/2705
MagicBeanDev:github/pr/2697
MagicBeanDev:github/pr/2679
MagicBeanDev:github/pr/2676
MagicBeanDev:github/pr/2669
MagicBeanDev:docs/what-hookshot-connects-to
MagicBeanDev:fix/hdr-encode-test
MagicBeanDev:fix/main-tests
MagicBeanDev:fix/play-cache-files
MagicBeanDev:perf/game-barrier-order
MagicBeanDev:feat/heap-lazy
MagicBeanDev:feat/texture-detail
MagicBeanDev:perf/astc-opaque-bc1
MagicBeanDev:fix/fs-dir-lock
MagicBeanDev:fix/framegen-native-kept
MagicBeanDev:fix/update-no-downgrade
MagicBeanDev:port/k10-gcn-f16-mirrors
MagicBeanDev:fix/framegen-mult-cap
MagicBeanDev:chore/release-as-2.1.1
MagicBeanDev:perf/pcache-load-switch
MagicBeanDev:fix/prepare-progress-log
MagicBeanDev:fix/update-closes-stale-game
MagicBeanDev:fix/hdr-scaled-sampled
MagicBeanDev:perf/first-start-preset
MagicBeanDev:perf/zero-upload
MagicBeanDev:fix/vcore-escape-2s
MagicBeanDev:test/fake-gh-atomic
MagicBeanDev:perf/batch-idle-flush
MagicBeanDev:test/finish-stub-a
MagicBeanDev:chore/record-2.1.0
MagicBeanDev:fix/main-red-tests
MagicBeanDev:fix/test-display-startup-driver-cache
MagicBeanDev:feat/mesh-shader-mode
MagicBeanDev:fix/menu-probes-at-startup
MagicBeanDev:fix/fps-counter-classic
MagicBeanDev:fix/test-log-batch-flake
MagicBeanDev:fix/narrow-screen-hud
MagicBeanDev:ci/prereleases
MagicBeanDev:release/v2.0.15
MagicBeanDev:hotfix/v2.0.15
MagicBeanDev:fix/present-ahead-replay
MagicBeanDev:release/v2.0.13
MagicBeanDev:fix/bracket-paths-2013
MagicBeanDev:fix/hdr-overlays-2013
MagicBeanDev:fix/fps-counter-scale-2013
MagicBeanDev:fix/stick-seen-at-rest
MagicBeanDev:hotfix/menu-fits-screen
MagicBeanDev:fix/f1-always-opens-menu
MagicBeanDev:fix/ini-keep-lines
MagicBeanDev:claude/cranky-pike-7cf247
MagicBeanDev:perf/texture-answer-flat
MagicBeanDev:fix/stamp-test-modules
MagicBeanDev:fix/appimage-fonts
MagicBeanDev:fix/display-awake
MagicBeanDev:release/v2.0.12
MagicBeanDev:fix/discord-windows
MagicBeanDev:rc/v2.0.12-tests
MagicBeanDev:fix/dlss-keep-feature
MagicBeanDev:rc/v2.0.12
MagicBeanDev:button-icons
No reviewers
Labels
Clear labels
accessibility
Barrier affecting people with disabilities
autorelease: pending
autorelease: tagged
bug
Something isn't working
dependencies
Pull requests that update a dependency file
documentation
Improvements or additions to documentation
duplicate
This issue or pull request already exists
enhancement
New feature or request
github_actions
Pull requests that update GitHub Actions code
good first issue
Good for newcomers
help wanted
Extra attention is needed
invalid
This doesn't seem right
question
Further information is requested
testing wanted
wontfix
This will not be worked on
No labels
accessibility
autorelease: pending
autorelease: tagged
bug
dependencies
documentation
duplicate
enhancement
github_actions
good first issue
help wanted
invalid
question
testing wanted
wontfix
Milestone
Clear milestone
No items
No milestone
Assignees
Clear assignees
No assignees
2 participants
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Hookshot!2566
Loading…
Reference in a new issue
No description provided.
Delete branch "nullifyshot/feat/display-modes"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
A player on a 21:9, 16:10 or 4:3 screen can only get a size of that shape by writing
output_res=by hand: the Output resolution row offers six fixed sizes, and the Aspect ratio row (#2119) only 16:9 or the monitor's shape. This fills both rows from the screen's own display modes.What changed
Aspect ratio (the same row, saved as
ultrawide=as before) is now one list of shapes, narrowest first: every shape the screen's modes have (5:4, 4:3, 3:2, 16:10, 16:9, 21:9, 32:9, ...). There's no separate Monitor's shape value any more. Instead, the shape that matches the monitor is marked, e.g. "16:9 (monitor's)" on a 1080p screen, "21:9 (monitor's)" on a 3440x1440 one, "16:10 (monitor's)" on a Steam Deck-shaped 1280x800. It's still the default. An unusual shape shows as its ratio (1600x1024 is 25:16). The row moves from Screen to the top of Resolution and upscaling, right above the Output resolution whose sizes it now picks.Output resolution lists the sizes of the chosen shape:
EnumDisplaySettingsW) and its own size, every size once, each side rounded down to 8, only thosehos_output_size_validaccepts;display_modes.hcommon_sizes). A 1080p screen gets 1440p and 4K without DSR. A shape the screen has none of gets none;The screen's own size is marked "(native)", rounded the same way, so 1366x768 screens see "1360x768 (native)". Picking a shape takes the screen's own size if it has that shape, else the one whose height is nearest the screen's: 21:9 on a 1080p screen is 2560x1080, and 3:2 there is 1920x1280, not 720x480. Larger sizes stay a choice rather than the default. Hints say whether a size is the screen's own, larger (drawn bigger, then scaled down) or smaller.
A screen too big for the game's size limit (e.g. a 7680x2160 32:9 monitor, past 5120x2880's pixels) still gets its shape, at the most pixels a picture of another shape takes (6256x1760 there). A screen whose shape the game can't draw at all (portrait) gets "Monitor's shape" as a value of its own at the end of the row, as on main, and that value saves
ultrawide=1.Which monitor gets marked: "(monitor's)", "(native)" and the sizes listed come from the monitor the game window is on (
MonitorFromWindow, with that monitor's own modes), or from the primary before the window exists. They also follow it while playing: about every 30 frames the menu checks the window's screen. If the window moved to another monitor or the desktop resolution changed, that screen's new sizes are added after the existing choices, so a choice already made keeps its size, and the Aspect ratio row stays on its shape.A size that isn't 16:9 sets the picture's shape, so the cameras widen or narrow and the HUD stays in the middle 16:9. Before, only a size wider than 32:9 did that, and 2560x1080 with 16:9 was stretched. This uses the same rule in
host/hos_main.cand the menu (hos_output_own_shape, 1% off 16:9). It takes precedence overHOS_ASPECTandultrawide=, which now only shape a 16:9 size.Saved options:
ultrawide=stays 0 or 1, with main's default and #2119's rule for older files. It's checked against the host's screen, the primary, whose shapehost/hos_main.cgives a 16:9 size at startup: 1 when that shape is picked, else 0. So the next start draws the shape picked, even when the window is on another monitor. The host's screen and its shape are always in the list. Any other shape comes fromoutput_res's own size, so nothing new is saved. The launcher's own 16:9 / Monitor's shape setting (recomplauncher.json) writes the same key and still works. The old saved numbers (output_res=1to5) still load (kOutputs, whichnx2/pipeline_share.pymirrors).Linux: a new host call,
MkwVkHost.screen_size(MKWVK_API_VERSION20; main took 19 for the ocarina notes, #1930), gives the menu SDL's primary desktop size, the same one the host shapes the picture from. With it, Linux offers the sizes it offered before, plus the screen's own size and the common sizes of their shapes, and marks the monitor's shape like Windows. With no screen size at all, the old fixed sizes are listed. That includes a windowless start (HOS_HEADLESS), which loads no SDL:hos_sdl_desktop_sizenow returns "screen unknown" there, where it used to call an empty SDL2 slot (found by mooki000,tests/test_sdl_desktop_size.py).HOS_DISPLAY_MODES=<w>x<h>,...stands in for the screen's modes (with the common sizes of their shapes added), for testing a screen you don't have (documented indocs/development.md).Control bridge:
option_set output_resstill reaches every size (#2496). The arrows stay within one shape, so for a size of another shape it moves the Aspect ratio first, as a player would.Code: the rules live in
gpu/vk/display_modes.has plain code, tested bytests/test_display_modes.py. Tests updated for the new layout and numbering:test_output_resolution,test_menu_env_overrides,test_menu_setting_walk,test_menu_sections(the row's new section),test_framegen_presetandtest_rumble_menu(stand-ins).Translations (#1864): seven of #1864's entries were this branch's earlier help texts (the old Aspect ratio help and the "The sharpest on a … screen" lines), which the rows no longer show. They're removed from each of the 13 tables, and the rows' current texts are added: the Aspect ratio's help and its four shape lines, the Output resolution's own-size, larger and smaller lines, and
"%s (monitor's)".tx_ultrawideprints the mark from that format, somatch_formattranslates "21:9 (monitor's)" and keeps the shape as written. Like the rest of #1864's tables, these are machine-made and use each table's own words for Output resolution, HUD and render targets. Speakers are welcome to correct them.Tested (Windows 11, RTX 5080, a 1920x1080 primary screen whose modes run from 640x480 to 1920x1080)
tests/run_all.py(llvm-mingw), ata1eb4604+ this branch: 406 passed (test_output_resolutioncovers the 21:9 primary of the retest from its own modes, the Aspect ratio's values, the bridge's way to 16:9, a 16:9 size kept at the monitor's shape, a window on a 16:9 monitor beside a 32:9 primary, and sizes found while running), 71 skipped, 15 failed. Main alone fails the same 15 on this PC (404 passed; this branch addstest_display_modesandtest_sdl_desktop_size). Among them,test_menu_i18nfails on three texts main's menu no longer has,test_framegen_presetandtest_rumble_menuon an undeclaredtemporal_reset_failurein their stand-ins. This branch adds no failures.HOS_LANGUAGE=de, headless): "16:9 (des Monitors)" and the Aspect ratio's help in German. "1920x1080 (native)" stays English, as on main: the size's label is printed with a%s, which the tables don't match.RX_VK_SHOT_W/H) so they're the raw frame. Each size rendered at exactly that size, with the cameras at its shape (boot: widescreen: picture …, camera ratio …) and nothing stretched:HOS_DISPLAY_MODESstanding in for an ultrawide's list: 2560x1080 and 3440x1440 (21:9, 2.3704 and 2.3889, HUD in the middle 75%), 5120x1440 (32:9, 3.5556, HUD in the middle 50%).RX_VK_MENU_SCRIPT,RX_VK_SHOT_MENU): Aspect ratio stepped through every shape on this screen (5:4 → 1280x1024, 4:3 → 1440x1080, 3:2 → 1920x1280, 25:16 → 1600x1024, 16:10 → 1680x1048, 5:3 → 1280x768, 16:9 (monitor's) → 1920x1080 (native)) and on the simulated ultrawide list (21:9 → 2560x1080, 32:9 → 3840x1080). The Output resolution followed each one with the expected size. Arrows stop at the ends, a shape with a single size shows no arrows, and "Restart needed: Aspect ratio, Output resolution" appears.Needs testing (help wanted: I have one 16:9 screen)
HOS_DISPLAY_MODES. Also an older options file withultrawide=1and a 16:9output_resthere: the row should read "21:9 (monitor's)" (or the screen's shape), the picture should be at the monitor's shape, and saving should keepultrawide=1.ultrawide=is saved against the primary (see above). Fullscreen (F11) goes borderless on whichever monitor the window is on.vk: the window's screen is …: N sizes added, and the marks move); changing which monitor is primary between runs; mixed DPI scaling.rx_present_fx), never stretched.output_res=does).output_res=1to5, a hand-written size of a shape the screen doesn't list,ultrawide=0from before options version 6 on a 16:10 screen (#2119's rule), andHOS_ASPECT/HOS_OUTPUT_RESruns (#2541).vk: the window's screen is 2560x1440 …, and the new sizes should appear in the Output resolution while your current choice stays. Not tested here.Screenshots
From the headless runs above (Windows 11, RTX 5080, 1920x1080 primary screen): Upscaler Off at 100%, no window scaling filter, Sharpness 0, each shot at the picture's own size.
The Graphics tab on this screen: the Aspect ratio at the monitor's own shape, marked "(monitor's)", with the Output resolution at the screen's own size.
3:2, a shape this screen has only at 720x480. The Output resolution moved to 1920x1280, one of 3:2's common sizes, the one nearest the screen's height. Both rows are marked for the restart.
With
HOS_DISPLAY_MODESstanding in for an ultrawide's list, at a 3440x1440 output: 32:9, with the Output resolution stepped to 5120x1440.The same spot at all seven sizes, scaled to one height: 16:9, 4:3, 16:10, 5:4, both 21:9 sizes and 32:9. Wider shapes see more at the sides with the HUD in the middle 16:9. Narrower ones keep the height and spread the HUD across their width. Nothing is stretched.
Full size, 21:9 at 3440x1440 (
boot: widescreen: picture 3440x1440, scene 2584x1080, camera ratio 2.3889, HUD and movies in the middle 74.4%):Full size, 4:3 at 1440x1080 (
picture 1440x1080, scene 1080x810, camera ratio 1.3333, HUD and movies in the middle 100.0%):🤖 Generated with Claude Code
Tested on a real 21:9 screen: Windows 11, RTX 5090, primary monitor 5120x2160 at 125% scaling, plus a 1440x2560 portrait secondary. Build of this branch at a8f7f9df, headless runs (
HOS_HEADLESS=1,RX_VK_SHOT_MENU=1,RX_VK_MENU_SCRIPT), noHOS_DISPLAY_MODES.The screen's modes (
EnumDisplaySettingsW(nullptr, i), each size once): 18 sizes, seven of them 21:9: 2560x1080, 3012x1271, 3414x1440, 3440x1440, 3939x1662, 4352x1836, 5120x2160. The others are 4:3, 3:2, 5:4 and 16:9 (up to 3840x2160). No 16:10 or 32:9. The log:the Output resolution's sizes: 32 (the screen's modes), the game's own and no other; shapes 5:4, 4:3, 3:2, 16:9, 21:9.Works as described
picture 3416x1440, scene 2560x1080, camera ratio 2.3722).ultrawide=1and a 16:9output_res=2560x1440: the row reads 21:9 (monitor's), the picture is at the monitor's shape (3416x1440), and saving keepsultrawide=1.output_res=3440x1440:picture 3440x1440, scene 2584x1080, camera ratio 2.3889, HUD and movies in the middle 74.4%output_res=5120x2160:picture 5120x2160, scene 3840x1620, camera ratio 2.3704, HUD and movies in the middle 75.0%output_res=2560x1080:picture 2560x1080, scene 1920x810, camera ratio 2.3704Not as described: 21:9 is the monitor's own shape here, and its list is the 16:9 sizes widened
With the Aspect ratio at 21:9 (monitor's), the Output resolution offers, narrowest first:
1704x720 · 2120x896 · 2560x1080 · 2048x1152 (game's) · 3416x1440 · 4264x1800 · 5120x2160 (native)
Each of these is a 16:9 size at the monitor's shape: 1280x720, 1600x900, 1920x1080, 2560x1440, 3200x1800 (a common 16:9 size, not a mode here) and 3840x2160, widened at their height. The file confirms it: after stepping to 1704x720,
output_res=1280x720was saved. So on this screen:My guess is that the monitor's shape still goes through the old Ultrawide rule (
ultrawide=1: a 16:9output_reswidened) rather than through the 21:9 shape's own sizes. YourHOS_DISPLAY_MODESrun kept a 16:9 primary, where 21:9 is not the monitor's shape, so it took the other path. A test withHOS_DISPLAY_MODESand a 21:9 primary size would need the monitor's size simulated too.Windowed, on the same PC (picture 3416x1440, the first-start default):
Not tested yet: F11 and Alt+Enter, the game alone on the portrait monitor, and DLSS / DLSS-G at 21:9.
Two suggestions from playing it
output_reslike #2052's 7680x1440. (Within the picture's pixel cap: three 1440p screens, 7680x1440, are exactly 5120x2160's pixels; three 4K ones would still be capped.)@pmwork-personal Thanks, this is exactly the screen I couldn't test. Your build (
a8f7f9df) is two commits behind what fixed part of this, so here's where each point stands. 14320fa7 fixes the rest.The 21:9 (monitor's) list was 16:9 sizes widened. You were right: the arrows walked the 16:9 sizes from a 16:9
output_reskept atultrawide=1, which a first start always has with the game's own size.On your screen, Right from the first start's game's own size now goes 2560x1080 → 3408x1440 → 3440x1440 → 3936x1656 → 4352x1832 → 5120x2160 (native).
test_output_resolutionchecks exactly that list."2048x1152 (game's)" with its 16:9 numbers. 14320fa7: it now shows what the game draws at the monitor's shape. That's 2728x1152 on your 5120x2160, the same figure you worked out.
Back at 21:9 gave 5120x2160 and "Restart needed". 14320fa7: going back to the shape the game started with now gives back the size it started with, so there's nothing to restart for.
"no other" in the log. That meant "no size of your own", not "no common sizes". Your list did get 16:9's common sizes: 3200x1800 is the 4264x1800 you saw. The line now says
the Output resolution's sizes: N (the screen's modes, with the common sizes of their shapes, and the game's own).Also new since your build:
vk: the window's screen is …).ultrawide=is still saved against the primary, which is whathost/hos_main.cshapes the picture from at startup.On your setup, the window on the 1440x2560 portrait screen should now show the separate Monitor's shape value. It's still a shape the game can't draw.
Suggestion 2, rendering at the window's size live: I agree it'd be nicer, but it's a different mechanism. The picture's shape is fixed at boot:
nx2/oot_wide.py's patches readhos_wide_outonce, and the render targets' pool is sized from it. Following the window live would mean reshaping the cameras and reallocating the targets mid-game. I'd rather keep this PR to what the menu offers and pick that up separately, if it's doable at all.If you have time, a retest at the head on the same screen would cover what changed. Thanks again for the detailed run.
Tested on Linux: Arch, Hyprland 0.56.2 (Wayland), i9-13900K, RTX 4090 with NVIDIA 615.78.08, one 1920x1080 monitor at 60 Hz and scale 1.25, SDL 3.4.18. The runs are on
14320fa7; the branch has since been rebased tobd8fd684, andhost/hos_sdl.candhost/rx_backend.care identical between the two, so the first point applies to the new head as well.A windowless start crashes on Linux. With
HOS_HEADLESS=1,14320fa7exits with 139 before the first frame and before any menu key: 0 frames, the log reaches boot and thenCRASH: exception 0xc0000005 ... at ?+0, the kernel records the main thread at address 0, and there are no driver lines. The same run ona8f7f9dfearlier completed normally (150 s), so it came in with the last commits.The cause, as far as the evidence goes: the new host screen callback calls
hos_sdl_desktop_size, and a windowless start never loads SDL, soversionis 0 and the function falls through to the SDL2 branch and calls an empty function pointer. The core shows the jump to 0 but is cut short before the caller's stack, so this is from the code and from a reproduction: those two functions compiled alone with GCC and Clang and an unloaded table fault the same way. Withif (!version) return 0;at the top of the helper the same fixture returns "screen unknown" instead. I did not patch the game. Windows takes theGetSystemMetricsbranch and is not affected by this path.With a real window on Wayland it works. 142 s, no crash, no device loss, no driver lines. Stepping both rows by script, the menu offered:
The log:
the Output resolution's sizes: 9 (the screen's modes, with the common sizes of their shapes, and the game's own); shapes 16:9, 32:9. So on Linux the list is the desktop's size plus the common sizes, not the monitor's whole mode list. The window is 1536x864 in logical units and the swapchain 1920x1080, and 1920x1080 is the one marked native, so the scale of 1.25 is handled. Going back to 16:9 gave back the size the run started with. The last choice was saved asoutput_res=2048x1152withultrawide=1, to apply at the next start. Nothe window's screen isline appeared, as expected on one unchanged monitor.Checks: the eight tests this adds or changes pass with GCC 16.2.1 and Clang 23.1.3 (
run_all.py --strict). Against main's sources six of them fail as they should and two, which only update stand-ins, pass. The whole suite on14320fa7: 450 passed, 1 failed, 20 skipped; the one is main'stest_env_onof that hour, since fixed by #2575. The Linux build succeeds.Overlap with #2594 (a typed Custom output size, mine): the two conflict in six places in four files, among them the host callbacks both add (
screen_sizehere,take_textthere) and the API version. They fit together: the typed size belongs beside this list. Whichever lands second needs a small rebase; if this lands first I will do it on #2594 and move the typed-size path to your dynamic list.Not tested: fullscreen (my test harness does not allow it), X11 and other compositors, a second monitor, Windows, and the restart that applies the saved 2048x1152. The windowed run's evidence is the log and the menu's own state, not pictures of the menu.
Retest at
bd8fd684with main merged in, on the same PC: Windows 11, RTX 5090, 5120x2160 primary at 125%, 1440x2560 portrait secondary. Headless (HOS_HEADLESS=1,RX_VK_SHOT_MENU=1, a menu key script), noHOS_DISPLAY_MODES, from a first start with no options file. The windowless start is fine on Windows, as expected from mooki's note on theGetSystemMetricsbranch.All three fixes work on this screen
the Output resolution's sizes: 32 (the screen's modes, with the common sizes of their shapes, and the game's own); shapes 5:4, 4:3, 3:2, 16:9, 21:9. The picture is 3416x1440.ultrawide=1,output_res=2048x1152.Two small notes on the list. Neither looks wrong to me:
test_output_resolution's list: 3008x1264 (this screen's 3012x1271 mode, rounded down to 8) and 3840x1600 (21:9's common size). Both fit the log line's rule. The test fillsoutput_extraswith the six sizes directly, so it doesn't cover these two.Merging main. GitHub shows the branch conflicting with main. To build it I merged main (up to
a016b7a6) intobd8fd684locally. All five conflicts are ingpu/vk/vk_menu.cpp, against #1864's Game language row, and each one keeps both sides:display_modes.h, then main'svk_menu_i18n.h.save(): yourultrawidebranch, then main's last branch, which now skipsgame_lang(that one goes to language.txt):set_row(): main's step line (it only adds a comment), then youroutput_for_aspectblock.outputs_follow_screen(), then main'sgame_lang_tick().menu_tick(): both calls,outputs_follow_screen();thengame_lang_tick();.With that:
de866017) with no more conflicts.test_framegen_presetandtest_rumble_menudon't compile (temporal_reset_failureundeclared in their stand-ins). They fail the same way on main here (a016b7a6andde866017), so they're not from this PR.Not tested yet (these need a real window): the window moved to the portrait screen (the
the window's screen isline), F11 and Alt+Enter, and DLSS / DLSS-G at 21:9.WIP: feat(menu): the Aspect ratio and Output resolution from the screen's display modesto feat(menu): the Aspect ratio and Output resolution from the screen's display modesThanks to both of you. The branch is now rebased on main through #1864 and #2637, and the head has two new commits for your points.
@mooki000 The headless crash on Linux is fixed in
86aa3c42, as you suggested. With no SDL loaded,hos_sdl_desktop_sizenow returns "screen unknown". Without SDL the menu then lists the sizes it lists when it knows no screen. Before this PR, nothing called the helper before SDL was loaded. The menu now asks at startup and every 30 frames, which is how a windowless start reached the empty SDL2 slot.tests/test_sdl_desktop_size.pycuts the function fromhos_sdl.cand runs it with stand-ins. Without the guard it faults at address 0, matching your core. It also checks SDL3's mode in pixels (your 1536x864 at 1.25 is 1920x1080), SDL3 with no mode, and SDL2.On Linux the list is the desktop's size plus the common sizes of its shapes, as you saw. SDL gives the menu the desktop mode only, not the monitor's whole mode list. #2594: agreed, the typed size belongs beside this list. Whichever lands second takes the rebase, and thanks for offering to do it on yours.
@pmwork-personal Your merge matches the rebase now on the PR: the same five spots in
vk_menu.cpp, each keeping both sides. The only difference is the order of the two calls inmenu_tick, which don't depend on each other. The rebase also puts the rows' new texts into #1864's tables. "21:9 (monitor's)" now goes through a translatable format, so it shows as "16:9 (des Monitors)" in German, for example.The two sizes the test didn't cover are in
2e141301. The 21:9 case now takes the screen's modes as Windows lists them, including your 3012x1271, through the same rounding and common-size steps as the list. It walks your eight sizes: 2560x1080 → 3008x1264 → 3408x1440 → 3440x1440 → 3840x1600 → 3936x1656 → 4352x1832 → 5120x2160."(game's)" before 2560x1080: that's its place in the list. The game's own size is choice 0, so older saves keep their
output_res=0, and the arrows step by list position. At 21:9 it draws 2728x1152, which reads as out of order next to 2560x1080. If you'd rather the arrows reach it at its drawn width, between 2560x1080 and 3008x1264, I can do that. It doesn't change what's saved.test_framegen_presetandtest_rumble_menufail the same way on main here (temporal_reset_failurein their stand-ins), so they're not from this PR. Of the 15 tests that fail on this PC, main fails the same 15.@pmwork-personal I made the "(game's)" change in
e4caac77. The Output resolution's arrows now go by the size each choice shows. On your screen at 21:9 the order is 2560x1080 → 2728x1152 (game's) → 3008x1264 → …, the same both ways. At 32:9, a kept 1920x1080 (drawn 3840x1080) and the game's own (4096x1152) now come before 5120x1440 as well. Saves don't change: the game's own is stilloutput_res=0. Handheld draws nothing wider, so it keeps the old order.test_output_resolutionchecks your screen's nine choices up and back down.I merged this into current main. tests/test_display_modes.py, test_output_resolution.py, test_menu_env_overrides.py, test_menu_sections.py, test_menu_setting_walk.py, test_framegen_preset.py, test_rumble_menu.py, test_sdl_desktop_size.py, test_menu_i18n.py and test_env_on.py all pass. pmwork-personal tested it on a real 5120x2160 screen and mooki000 on Linux/Wayland. Every point they raised has a commit: the widened 16:9 list, "(game's)" with its 16:9 numbers, the restart notice when coming back, and the headless SDL crash, now guarded at host/hos_sdl.c:179.
Two things for the maintainers rather than the author:
hos_output_own_shapechanges a default. Any savedoutput_resmore than 1% off 16:9 now sets the picture's shape overultrawide=/HOS_ASPECT. Before, only sizes wider than 32:9 did. For someone with e.g. 2560x1080 saved that's a fix (it was stretched), but it is a visible change on update.take_text), and with #2600/#2604/#2760. The two conflict in vk_api.h, vk_menu.cpp, menu_i18n.h, rx_backend.c and readme.md. mooki000 has offered to rebase #2594 if this lands first.Linux re-test of the fixed head, as promised above.
Hardware: RTX 4090 24 GB, NVIDIA 615.78.08, i9-13900K, Arch Linux 7.2.15-1-omarchy.
Built
ea30380112ac6aebed292627b90f61fff094776f; source checks compared with mainee29dd55b593577ae0a7a6e2f871848ee5e176b4. The head is nowd550ea55: the same eleven commits rebased onto main51da0464(a range-diff marks all eleven identical). I did not rebuild for the rebase.The Linux windowless startup crash is gone. Nine starts on this head reached the copied Kokiri Forest save without a crash, fatal error, device loss or kernel driver fault. I captured the title, the loaded save and the Graphics rows. The first start logs:
That last size is the fixed offscreen extent, not a detected monitor or the chosen output. SDL3 loads later for controller support; there is still no video desktop here.
With the screen unknown, Aspect ratio offers 16:9, 32:9 and Monitor's shape. The outputs are:
I selected all six sizes and started a new process at each, checking the output log and same-sized captures. Returning to the starting shape restored the starting size. A menu-selected 1920x1080 survived the next start, with that size in the log and menu and no pending restart notice. The 32:9 start logged
picture 5120x1440, scene 3840x1080, camera ratio 3.5556, HUD and movies in the middle 50.0%.Authored mode lists:
HOS_DISPLAY_MODES1280x720,1600x900,1920x10802560x1080,3440x1440,3840x1600,5120x2160The 21:9 start logged
picture 2560x1080, scene 1920x810, camera ratio 2.3704, HUD and movies in the middle 75.0%; the capture shows the wider view and centred HUD. Larger values in that menu run were selected, not all applied.An existing custom setting: main
46780503wrote an options file containingoutput_res=2400x1600,ultrawide=1. This head read the identical file as 3:2 / 2400x1600, retained the size, and showed the “Your own size” hint without a pending restart. Its boot line sayspicture 2400x1600, scene 1800x1200, camera ratio 1.5000.Pictures (windowless captures from this PC, labelled):
Evidence limits: neither authored list supplies a monitor's size, so neither produces a
(monitor's)or(native)mark on windowless Linux. Both retain the separate Monitor's shape choice. Also, the unknown-screen fallback really is the old six sizes: the log's generic “screen's modes, with the common sizes” wording does not describe that case literally. Capture dimensions alone are not a source-texture measurement; the output/picture logs are included for that reason.Checks: all eight changed/new tests pass with GCC 16.2.1 and Clang 23.1.3. Against main, two stand-in updates pass; six fail: the SDL fixture actually faults without the guard, the section check detects the old layout, and four fixtures stop at the absent new header/extraction anchors. Seven C/C++ fixtures pass Windows-target syntax checking, not Windows execution. No full-suite rerun. One virtual-core escape occurred in the heavily loaded first start; these runs make no performance claim.
Not tested: a real window on this head, real ultrawide or other monitor shapes, native marks from a real desktop, multi-monitor/DPI/hotplug changes, fullscreen/window resizing, Windows runtime, handheld mode, or other upscaler/frame-generation combinations.
Follow-up: one run in a real window, for the mark the windowless test could not show.
The real-window follow-up also passes on this monitor: Arch Linux, Hyprland/Wayland, RTX 4090 24 GB, NVIDIA 615.78.08, i9-13900K; 1920x1080 at scale 1.25. Same build
ea303801as above, not a new build of the patch-identicald550ea55rebase.One process, 118.2 seconds total. The Graphics menu's own state reports 16:9 (monitor's) and 1920x1080 (native). I stepped the 16:9 outputs through 1280x720, 1920x1080, 2048x1152, 2560x1440, 3200x1800, 3840x2160 and 5120x2880, then selected 32:9 / 3840x1080 and returned to the marked 16:9 / 1920x1080 pair. The larger choices were pending settings, not applied during this process; I did not walk every 32:9 size.
The window was 1536x864 logical pixels; the source picture, content and swapchain were all 1920x1080. Thus the native mark uses the physical size, not the scaled logical window size. No crash, device loss or driver fault; the window did not take focus. No performance claim.
I inspected two 1920x1080 captures made by the game itself. They show the title picture, not the Insert overlay: the marked labels above come from the real menu's state and log, not a desktop screenshot. The first capture has the title over the ranch; the later one has the title over a black background.
Still not tested: other physical monitor shapes, multiple monitors, monitor/DPI/resolution changes, moving or resizing the window, fullscreen transitions, or Windows.
Thanks, mooki000, for both Linux runs: windowless on the fixed head, and the real window with the "(monitor's)" and "(native)" marks at 125% scaling. Thanks too, Joey-Magnus, for the merge check and the review.
On the two notes for the maintainers:
MKWVK_API_VERSION20: if #2594 (or another of the PRs that took 20) merges first, I'll rebase this one and take the next number, so the other PRs don't have to change.Windows, real window: passes. RTX 5080, Windows 11 (NVIDIA 621.24), one 1920x1080 screen. Built from head
d49f1632. Three windowed starts and one headless; the Insert menu was driven withRX_VK_MENU_SCRIPT, and each row's text was read over the control bridge (option get). The window was captured with PrintWindow, which shows the menu; the game's ownshotleaves it out.boot: widescreen: a 1.7778 screen is 16:9: the game's own picture,vk: the Output resolution's sizes: 33 (the screen's modes, with the common sizes of their shapes, and the game's own); shapes 5:4, 4:3, 3:2, 25:16, 16:10, 5:3, 16:9.display: fullscreen (F11 for a window)and a 1920x1080 swapchain; Off gavedisplay: windowedand a 1280x720 swapchain.vk: swapchain 1600x900 ...,960x540,1280x720) and the menu laid itself out at that size. Moving it logged nothing, as expected with one screen (the window's screen isappears only when the screen changes).A crash in the build's shader step (not reproduced). When this head was built, the build's
--prepare-shadersrun stopped with 0xC0000005, right after the version line and before the Vulkan device line. Started again by hand a few minutes later, it went through to@@done shaders. Three more prepare runs, the headless start and the windowed starts all passed too. This PC's NVIDIA driver changed between the morning's build (621.14) and these runs (621.24), and I couldn't find where the crash happened. Windows has no twin of the Linux windowless crash: on Windows the screen's size and modes come from GetSystemMetrics and EnumDisplaySettingsW, never from SDL. The headless log shows the sizes line with no SDL loaded.Not covered: two screens or moving the window to another one, display scaling other than this screen's, a desktop resolution change while running, starting with a new size after a restart (Restart now wasn't pressed), and HDR.
Merged current main into the branch (b5d88566), no force-push. The only textual conflict was the readme's option table: the Aspect ratio line moves up to Resolution and upscaling as this PR has it, and main's new Ultrawide menus line stays.
MKWVK_API_VERSION20 is still the right bump: main is at 19, andscreen_sizeis the only interface change.test_opening_skip(#2827) pinned the version to 19, so this branch's bump failed it. It now checks what it meant, that Skip opening added nothing toMkwVkHost/MkwVkRenderer.The display-mode, output-resolution, SDL desktop-size and menu tests pass, and the Vulkan back end builds with the ABI check.
test_readme_menu_rowsfails on main too (Skip opening has no readme line yet), so that's not from this PR.Thanks for all the rounds on this, and thanks to pmwork-personal and mooki000 for the testing on real screens.