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
nullifyshot commented 2026-11-11 23:29:11 +00:00 (Migrated from github.com)

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.

#2541 has merged, and this branch is rebased on main after it. Its commit is no longer here. Rebased again on #1864 (the Insert menu in 13 languages): the rows' new texts are in its tables (see Translations below).

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:

    • the screen's display modes (EnumDisplaySettingsW) and its own size, every size once, each side rounded down to 8, only those hos_output_size_valid accepts;
    • the common sizes of each of those shapes the screen doesn't list: 720p, 1080p, 1440p, 1800p, 4K and 5K at 16:9; 1280x800 to 3840x2400 at 16:10; 1024x768 to 2880x2160 at 4:3; 1280x1024 at 5:4; 1920x1280 to 3000x2000 at 3:2; 2560x1080, 3440x1440, 3840x1600 and 5120x2160 at 21:9; 3840x1080 and 5120x1440 at 32:9 (display_modes.h common_sizes). A 1080p screen gets 1440p and 4K without DSR. A shape the screen has none of gets none;
    • the game's own 2048x1152 under 16:9.

    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.c and the menu (hos_output_own_shape, 1% off 16:9). It takes precedence over HOS_ASPECT and ultrawide=, 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 shape host/hos_main.c gives 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 from output_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=1 to 5) still load (kOutputs, which nx2/pipeline_share.py mirrors).

  • Linux: a new host call, MkwVkHost.screen_size (MKWVK_API_VERSION 20; 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_size now 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 in docs/development.md).

  • Control bridge: option_set output_res still 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.h as plain code, tested by tests/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_preset and test_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_ultrawide prints the mark from that format, so match_format translates "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), at a1eb4604 + this branch: 406 passed (test_output_resolution covers 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 adds test_display_modes and test_sdl_desktop_size). Among them, test_menu_i18n fails on three texts main's menu no longer has, test_framegen_preset and test_rumble_menu on an undeclared temporal_reset_failure in their stand-ins. This branch adds no failures.
  • The menu in German (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.
  • In game, headless, the save loaded through the control bridge. The screen's modes plus the common sizes gave 33 sizes in 7 shapes. Every run used the Upscaler Off at 100%, no window scaling filter and Sharpness 0, and shots at the picture's own size (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:
    • From this screen's modes: 1920x1080 (16:9), 1440x1080 (4:3, camera ratio 1.3333), 1680x1048 (16:10, 1.6031), 1280x1024 (5:4, 1.25).
    • From HOS_DISPLAY_MODES standing 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%).
    • Common sizes this 1080p screen doesn't list: 2560x1440 (16:9) and 2560x1600 (16:10, camera ratio 1.6000), each rendered at that size.
  • The Insert menu, headless (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)

  • A real ultrawide, 16:10 or 4:3 screen: the list, "(monitor's)" on the right shape and "(native)" on the right size, from the actual modes rather than HOS_DISPLAY_MODES. Also an older options file with ultrawide=1 and a 16:9 output_res there: 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 keep ultrawide=1.
  • Screens at unusual sizes: 1366x768 (16:9, not a multiple of 8), 1600x900, 3840x1600, 5120x2160, a 7680x2160 32:9 (past the size limit), and a laptop at 3:2 or 16:10. Check the right shape is marked and the default size picked.
  • Common sizes above the screen: 1440p, 4K and 5K on a 1080p screen. They should render at that size and be scaled down to the window, and the hint should say "Larger than your screen".
  • Multi-monitor:
    • The marks and sizes follow the monitor the window is on, while ultrawide= is saved against the primary (see above). Fullscreen (F11) goes borderless on whichever monitor the window is on.
    • Please check the game on a secondary monitor with a different size or shape from the primary (e.g. a 1440p 16:9 primary and a 21:9 secondary). Expected: the secondary's shape is marked "(monitor's)" and its own size "(native)"; picking 16:9 there and restarting gives a 16:9 picture, and picking 21:9 gives 21:9.
    • Also check: dragging the window to the other monitor while running (the log says vk: the window's screen is …: N sizes added, and the marks move); changing which monitor is primary between runs; mixed DPI scaling.
    • Not tested here (one monitor).
  • Window resizing:
    • The picture stays at the Output resolution until a restart. Resizing, maximizing or snapping the window only changes how it's scaled to the window, with bars where the shapes differ (rx_present_fx), never stretched.
    • Please check: a 21:9 or 4:3 Output resolution in a 16:9 window and the other way round; dragging the window to odd shapes (very tall, very wide); maximize and restore; F11 and Alt+Enter in and out of fullscreen at each shape; a window larger than the picture with each Window scaling filter.
    • Also check that the Window scaling filter hint says "Not needed" only when the window is exactly the Output resolution.
  • Display changes while running: changing the desktop resolution, plugging a monitor in or out, or turning DSR on doesn't change the lists until the next start. A saved size the screen no longer offers stays as the player's own choice (as a hand-written output_res= does).
  • DSR / DLDSR: their sizes appear in the list (Windows lists them as modes). Picking a shape shouldn't default to one of them; they should be reachable with the arrows.
  • Upscalers and frame generation at other shapes: DLSS, DLAA, FSR 3.1, FSR 4 and DLSS Frame Generation at 21:9, 32:9, 16:10 and 4:3, checking the Render resolution sizes the row shows and that nothing is cropped or smeared at the edges.
  • The parts of the game outside the 3D at each shape: pause, item and map menus (Ultrawide menus), loading screens, pre-rendered movies, cutscenes, and the HUD at 4:3 and 5:4.
  • Older options files: output_res=1 to 5, a hand-written size of a shape the screen doesn't list, ultrawide=0 from before options version 6 on a 16:10 screen (#2119's rule), and HOS_ASPECT / HOS_OUTPUT_RES runs (#2541).
  • Handheld profile: both rows are docked only. Check nothing changes in handheld.
  • Linux and the Steam Deck: the menu now gets the screen size from SDL (API 20), so the Deck's 1280x800 should show "16:10 (monitor's)", and 16:9 should be selectable and kept after a restart. Not run on Linux here: CI's Linux build compiles the host side.
  • Desktop resolution changed while playing: e.g. switch the desktop to 2560x1440 with DSR while the game runs. The log should say 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.
  • Portrait screens: their modes are narrower than 5:4, so they're left out. Check the list then falls back sensibly.

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.

Aspect ratio 16:9 (monitor's), Output resolution 1920x1080 (native)

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.

Aspect ratio 3:2, Output resolution 1920x1280, restart needed

With HOS_DISPLAY_MODES standing in for an ultrawide's list, at a 3440x1440 output: 32:9, with the Output resolution stepped to 5120x1440.

Aspect ratio 32:9, Output resolution 5120x1440 on a simulated ultrawide list

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.

The same spot at 1920x1080, 1440x1080, 1680x1048, 1280x1024, 2560x1080, 3440x1440 and 5120x1440

Full size, 21:9 at 3440x1440 (boot: widescreen: picture 3440x1440, scene 2584x1080, camera ratio 2.3889, HUD and movies in the middle 74.4%):

21:9 at 3440x1440

Full size, 4:3 at 1440x1080 (picture 1440x1080, scene 1080x810, camera ratio 1.3333, HUD and movies in the middle 100.0%):

4:3 at 1440x1080

🤖 Generated with Claude Code

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. > #2541 has merged, and this branch is rebased on main after it. Its commit is no longer here. Rebased again on #1864 (the Insert menu in 13 languages): the rows' new texts are in its tables (see Translations below). **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: - the screen's display modes (`EnumDisplaySettingsW`) and its own size, every size once, each side rounded down to 8, only those `hos_output_size_valid` accepts; - **the common sizes of each of those shapes the screen doesn't list**: 720p, 1080p, 1440p, 1800p, 4K and 5K at 16:9; 1280x800 to 3840x2400 at 16:10; 1024x768 to 2880x2160 at 4:3; 1280x1024 at 5:4; 1920x1280 to 3000x2000 at 3:2; 2560x1080, 3440x1440, 3840x1600 and 5120x2160 at 21:9; 3840x1080 and 5120x1440 at 32:9 (`display_modes.h` `common_sizes`). A 1080p screen gets 1440p and 4K without DSR. A shape the screen has none of gets none; - the game's own 2048x1152 under 16:9. 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.c` and the menu (`hos_output_own_shape`, 1% off 16:9). It takes precedence over `HOS_ASPECT` and `ultrawide=`, 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 shape `host/hos_main.c` gives 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 from `output_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=1` to `5`) still load (`kOutputs`, which `nx2/pipeline_share.py` mirrors). - **Linux:** a new host call, `MkwVkHost.screen_size` (**`MKWVK_API_VERSION` 20**; 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_size` now 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 in `docs/development.md`). - **Control bridge:** `option_set output_res` still 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.h` as plain code, tested by `tests/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_preset` and `test_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_ultrawide` prints the mark from that format, so `match_format` translates "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), at a1eb4604 + this branch: 406 passed (`test_output_resolution` covers 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 adds `test_display_modes` and `test_sdl_desktop_size`). Among them, `test_menu_i18n` fails on three texts main's menu no longer has, `test_framegen_preset` and `test_rumble_menu` on an undeclared `temporal_reset_failure` in their stand-ins. This branch adds no failures. - The menu in German (`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. - In game, headless, the save loaded through the control bridge. The screen's modes plus the common sizes gave 33 sizes in 7 shapes. Every run used the Upscaler Off at 100%, no window scaling filter and Sharpness 0, and shots at the picture's own size (`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: - From this screen's modes: 1920x1080 (16:9), 1440x1080 (4:3, camera ratio 1.3333), 1680x1048 (16:10, 1.6031), 1280x1024 (5:4, 1.25). - From `HOS_DISPLAY_MODES` standing 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%). - Common sizes this 1080p screen doesn't list: 2560x1440 (16:9) and 2560x1600 (16:10, camera ratio 1.6000), each rendered at that size. - The Insert menu, headless (`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) - **A real ultrawide, 16:10 or 4:3 screen:** the list, "(monitor's)" on the right shape and "(native)" on the right size, from the actual modes rather than `HOS_DISPLAY_MODES`. Also an older options file with `ultrawide=1` and a 16:9 `output_res` there: 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 keep `ultrawide=1`. - **Screens at unusual sizes:** 1366x768 (16:9, not a multiple of 8), 1600x900, 3840x1600, 5120x2160, a 7680x2160 32:9 (past the size limit), and a laptop at 3:2 or 16:10. Check the right shape is marked and the default size picked. - **Common sizes above the screen:** 1440p, 4K and 5K on a 1080p screen. They should render at that size and be scaled down to the window, and the hint should say "Larger than your screen". - **Multi-monitor:** - The marks and sizes follow the monitor the window is on, while `ultrawide=` is saved against the primary (see above). Fullscreen (F11) goes borderless on whichever monitor the window is on. - Please check the game on a secondary monitor with a different size or shape from the primary (e.g. a 1440p 16:9 primary and a 21:9 secondary). Expected: the secondary's shape is marked "(monitor's)" and its own size "(native)"; picking 16:9 there and restarting gives a 16:9 picture, and picking 21:9 gives 21:9. - Also check: dragging the window to the other monitor while running (the log says `vk: the window's screen is …: N sizes added`, and the marks move); changing which monitor is primary between runs; mixed DPI scaling. - Not tested here (one monitor). - **Window resizing:** - The picture stays at the Output resolution until a restart. Resizing, maximizing or snapping the window only changes how it's scaled to the window, with bars where the shapes differ (`rx_present_fx`), never stretched. - Please check: a 21:9 or 4:3 Output resolution in a 16:9 window and the other way round; dragging the window to odd shapes (very tall, very wide); maximize and restore; F11 and Alt+Enter in and out of fullscreen at each shape; a window larger than the picture with each Window scaling filter. - Also check that the Window scaling filter hint says "Not needed" only when the window is exactly the Output resolution. - **Display changes while running:** changing the desktop resolution, plugging a monitor in or out, or turning DSR on doesn't change the lists until the next start. A saved size the screen no longer offers stays as the player's own choice (as a hand-written `output_res=` does). - **DSR / DLDSR:** their sizes appear in the list (Windows lists them as modes). Picking a shape shouldn't default to one of them; they should be reachable with the arrows. - **Upscalers and frame generation at other shapes:** DLSS, DLAA, FSR 3.1, FSR 4 and DLSS Frame Generation at 21:9, 32:9, 16:10 and 4:3, checking the Render resolution sizes the row shows and that nothing is cropped or smeared at the edges. - **The parts of the game outside the 3D at each shape:** pause, item and map menus (Ultrawide menus), loading screens, pre-rendered movies, cutscenes, and the HUD at 4:3 and 5:4. - **Older options files:** `output_res=1` to `5`, a hand-written size of a shape the screen doesn't list, `ultrawide=0` from before options version 6 on a 16:10 screen (#2119's rule), and `HOS_ASPECT` / `HOS_OUTPUT_RES` runs (#2541). - **Handheld profile:** both rows are docked only. Check nothing changes in handheld. - **Linux and the Steam Deck:** the menu now gets the screen size from SDL (API 20), so the Deck's 1280x800 should show "16:10 (monitor's)", and 16:9 should be selectable and kept after a restart. Not run on Linux here: CI's Linux build compiles the host side. - **Desktop resolution changed while playing:** e.g. switch the desktop to 2560x1440 with DSR while the game runs. The log should say `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. - **Portrait screens:** their modes are narrower than 5:4, so they're left out. Check the list then falls back sensibly. **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. ![Aspect ratio 16:9 (monitor's), Output resolution 1920x1080 (native)](https://raw.githubusercontent.com/nullifyshot/Hookshot/pr-screenshots/display-modes/menu-monitors-shape.webp) 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. ![Aspect ratio 3:2, Output resolution 1920x1280, restart needed](https://raw.githubusercontent.com/nullifyshot/Hookshot/pr-screenshots/display-modes/menu-3x2-common-size.webp) With `HOS_DISPLAY_MODES` standing in for an ultrawide's list, at a 3440x1440 output: 32:9, with the Output resolution stepped to 5120x1440. ![Aspect ratio 32:9, Output resolution 5120x1440 on a simulated ultrawide list](https://raw.githubusercontent.com/nullifyshot/Hookshot/pr-screenshots/display-modes/menu-ultrawide-32x9.webp) 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. ![The same spot at 1920x1080, 1440x1080, 1680x1048, 1280x1024, 2560x1080, 3440x1440 and 5120x1440](https://raw.githubusercontent.com/nullifyshot/Hookshot/pr-screenshots/display-modes/game-seven-shapes.webp) Full size, 21:9 at 3440x1440 (`boot: widescreen: picture 3440x1440, scene 2584x1080, camera ratio 2.3889, HUD and movies in the middle 74.4%`): ![21:9 at 3440x1440](https://raw.githubusercontent.com/nullifyshot/Hookshot/pr-screenshots/display-modes/game-21x9-3440x1440.webp) Full size, 4:3 at 1440x1080 (`picture 1440x1080, scene 1080x810, camera ratio 1.3333, HUD and movies in the middle 100.0%`): ![4:3 at 1440x1080](https://raw.githubusercontent.com/nullifyshot/Hookshot/pr-screenshots/display-modes/game-4x3-1440x1080.webp) 🤖 Generated with [Claude Code](https://claude.com/claude-code)
pmwork-personal commented 2026-11-12 02:04:33 +00:00 (Migrated from github.com)

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), no HOS_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

  • First start (no options file): Aspect ratio 21:9 (monitor's). The picture is the monitor's shape (picture 3416x1440, scene 2560x1080, camera ratio 2.3722).
  • Stepping the Aspect ratio: 16:9, 3:2 → 3000x2000, 4:3 → 2880x2160 (the screen's height), 5:4 → 1280x1024. Arrows stop at both ends; no 32:9 (the screen has none). Back at 21:9 the Output resolution reads 5120x2160 (native). "Restart needed" shows for each change.
  • An older options file with ultrawide=1 and a 16:9 output_res=2560x1440: the row reads 21:9 (monitor's), the picture is at the monitor's shape (3416x1440), and saving keeps ultrawide=1.
  • Hand-written 21:9 sizes render at exactly that size, nothing stretched:
    • 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.3704

Not 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=1280x720 was saved. So on this screen:

  • The screen's own 21:9 modes 3440x1440, 3939x1662, 4352x1836 and 3012x1271 are never offered, and neither are the common 21:9 sizes 3440x1440 and 3840x1600. 3414x1440 shows as 3416x1440, the widened 2560x1440, not the mode rounded down to 8 (3408x1440).
  • "no other" in the log: no common sizes were added for any shape.
  • "2048x1152 (game's)" sits in the 21:9 list with its 16:9 numbers, between 2560x1080 and 3416x1440. At 21:9 it draws about 2728x1152.
  • At first start the pair is 21:9 (monitor's) with 2048x1152 (game's). After stepping the shape away and back it becomes 5120x2160 (native), and "Restart needed: Output resolution" shows although the shape is the one the game started with.

My guess is that the monitor's shape still goes through the old Ultrawide rule (ultrawide=1: a 16:9 output_res widened) rather than through the 21:9 shape's own sizes. Your HOS_DISPLAY_MODES run kept a 16:9 primary, where 21:9 is not the monitor's shape, so it took the other path. A test with HOS_DISPLAY_MODES and a 21:9 primary size would need the monitor's size simulated too.

Windowed, on the same PC (picture 3416x1440, the first-start default):

  • Resizing, snapping and maximizing the window: about 98 window sizes, from 2135x900 up to 5409x2121, and never stretched. The game remade its swapchain each time with no errors (mailbox, V-Sync Auto, about 60 fps).
  • The window dragged across both monitors (the 5120-wide primary and the 1440-wide portrait screen; the window reached 6553x2121): the picture kept its 21:9 shape, scaled to the window's height, with black bars at the sides. That is the "bars where the shapes differ, never stretched" you describe. Filling both screens would take an Output resolution of that shape, which isn't one of the primary's modes and is past the 5120x2160-pixel cap anyway.

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

  1. Offer the monitor's own modes, which is this PR's aim, at the monitor's own shape too: on this screen that would be 2560x1080, 3440x1440, 3936x1656, 4352x1832 and 5120x2160 under 21:9, rather than 16:9 sizes widened to it. That's the convenient list for an ultrawide player.
  2. In a window, render at the window's size. When I resize the window or drag it across both monitors, I'd expect the game to fill the window, at whatever shape it is, rather than keep the Output resolution's shape with bars. I know the picture's shape is set at the game's start today (that's why both rows apply after a restart), so following the window live would mean changing the cameras' shape and the render size while it runs. That may be a separate PR, or not possible with how the game sets its widescreen, so I'm just raising it. It would also make spanning three monitors work with no setup: a window stretched across them would just fill them, instead of needing a hand-written surround output_res like #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.)
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`), no `HOS_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** - First start (no options file): Aspect ratio **21:9 (monitor's)**. The picture is the monitor's shape (`picture 3416x1440, scene 2560x1080, camera ratio 2.3722`). - Stepping the Aspect ratio: 16:9, 3:2 → 3000x2000, 4:3 → 2880x2160 (the screen's height), 5:4 → 1280x1024. Arrows stop at both ends; no 32:9 (the screen has none). Back at 21:9 the Output resolution reads **5120x2160 (native)**. "Restart needed" shows for each change. - An older options file with `ultrawide=1` and a 16:9 `output_res=2560x1440`: the row reads 21:9 (monitor's), the picture is at the monitor's shape (3416x1440), and saving keeps `ultrawide=1`. - Hand-written 21:9 sizes render at exactly that size, nothing stretched: - `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.3704` **Not 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=1280x720` was saved. So on this screen: - The screen's own 21:9 modes 3440x1440, 3939x1662, 4352x1836 and 3012x1271 are never offered, and neither are the common 21:9 sizes 3440x1440 and 3840x1600. 3414x1440 shows as 3416x1440, the widened 2560x1440, not the mode rounded down to 8 (3408x1440). - "no other" in the log: no common sizes were added for any shape. - "2048x1152 (game's)" sits in the 21:9 list with its 16:9 numbers, between 2560x1080 and 3416x1440. At 21:9 it draws about 2728x1152. - At first start the pair is 21:9 (monitor's) with 2048x1152 (game's). After stepping the shape away and back it becomes 5120x2160 (native), and "Restart needed: Output resolution" shows although the shape is the one the game started with. My guess is that the monitor's shape still goes through the old Ultrawide rule (`ultrawide=1`: a 16:9 `output_res` widened) rather than through the 21:9 shape's own sizes. Your `HOS_DISPLAY_MODES` run kept a 16:9 primary, where 21:9 is not the monitor's shape, so it took the other path. A test with `HOS_DISPLAY_MODES` and a 21:9 *primary size* would need the monitor's size simulated too. **Windowed, on the same PC** (picture 3416x1440, the first-start default): - Resizing, snapping and maximizing the window: about 98 window sizes, from 2135x900 up to 5409x2121, and never stretched. The game remade its swapchain each time with no errors (mailbox, V-Sync Auto, about 60 fps). - The window dragged across both monitors (the 5120-wide primary and the 1440-wide portrait screen; the window reached 6553x2121): the picture kept its 21:9 shape, scaled to the window's height, with black bars at the sides. That is the "bars where the shapes differ, never stretched" you describe. Filling both screens would take an Output resolution of that shape, which isn't one of the primary's modes and is past the 5120x2160-pixel cap anyway. **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** 1. **Offer the monitor's own modes**, which is this PR's aim, at the monitor's own shape too: on this screen that would be 2560x1080, 3440x1440, 3936x1656, 4352x1832 and 5120x2160 under 21:9, rather than 16:9 sizes widened to it. That's the convenient list for an ultrawide player. 2. **In a window, render at the window's size.** When I resize the window or drag it across both monitors, I'd expect the game to fill the window, at whatever shape it is, rather than keep the Output resolution's shape with bars. I know the picture's shape is set at the game's start today (that's why both rows apply after a restart), so following the window live would mean changing the cameras' shape and the render size while it runs. That may be a separate PR, or not possible with how the game sets its widescreen, so I'm just raising it. It would also make spanning three monitors work with no setup: a window stretched across them would just fill them, instead of needing a hand-written surround `output_res` like #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.)
nullifyshot commented 2026-11-12 02:23:58 +00:00 (Migrated from github.com)

@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_res kept at ultrawide=1, which a first start always has with the game's own size.

  • 9cde059f: from that state the arrows walk the monitor shape's own sizes.
  • 62f322f3: the host's screen (your primary) is always among the sizes too.

On your screen, Right from the first start's game's own size now goes 2560x1080 → 3408x1440 → 3440x1440 → 3936x1656 → 4352x1832 → 5120x2160 (native). test_output_resolution checks 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:

  • 62f322f3: "(monitor's)", "(native)" and the list follow the monitor the window is on. If you drag it to another monitor or change the desktop resolution while playing, that screen's sizes are added and logged (vk: the window's screen is …).
  • ultrawide= is still saved against the primary, which is what host/hos_main.c shapes the picture from at startup.
  • Linux gets the screen size from SDL through a new host call (API 19).

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 read hos_wide_out once, 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.

@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_res` kept at `ultrawide=1`, which a first start always has with the game's own size. - **9cde059f:** from that state the arrows walk the monitor shape's own sizes. - **62f322f3:** the host's screen (your primary) is always among the sizes too. On your screen, Right from the first start's game's own size now goes 2560x1080 → 3408x1440 → 3440x1440 → 3936x1656 → 4352x1832 → 5120x2160 (native). `test_output_resolution` checks 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:** - **62f322f3:** "(monitor's)", "(native)" and the list follow the monitor the window is on. If you drag it to another monitor or change the desktop resolution while playing, that screen's sizes are added and logged (`vk: the window's screen is …`). - `ultrawide=` is still saved against the primary, which is what `host/hos_main.c` shapes the picture from at startup. - Linux gets the screen size from SDL through a new host call (API 19). 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 read `hos_wide_out` once, 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.
mooki000 commented 2026-11-12 03:35:58 +00:00 (Migrated from github.com)

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 to bd8fd684, and host/hos_sdl.c and host/rx_backend.c are 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, 14320fa7 exits with 139 before the first frame and before any menu key: 0 frames, the log reaches boot and then CRASH: exception 0xc0000005 ... at ?+0, the kernel records the main thread at address 0, and there are no driver lines. The same run on a8f7f9df earlier 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, so version is 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. With if (!version) return 0; at the top of the helper the same fixture returns "screen unknown" instead. I did not patch the game. Windows takes the GetSystemMetrics branch 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:

  • 16:9 (monitor's): 1280x720, 1920x1080 (native), 2048x1152 (game's), 2560x1440, 3200x1800, 3840x2160, 5120x2880
  • 32:9: 3840x1080, 5120x1440

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 as output_res=2048x1152 with ultrawide=1, to apply at the next start. No the window's screen is line 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 on 14320fa7: 450 passed, 1 failed, 20 skipped; the one is main's test_env_on of 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_size here, take_text there) 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.

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 to `bd8fd684`, and `host/hos_sdl.c` and `host/rx_backend.c` are 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`, `14320fa7` exits with 139 before the first frame and before any menu key: 0 frames, the log reaches boot and then `CRASH: exception 0xc0000005 ... at ?+0`, the kernel records the main thread at address 0, and there are no driver lines. The same run on `a8f7f9df` earlier 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](https://git.magicbean.dev/MagicBeanDev/Hookshot/src/commit/14320fa7307eaf207ec91a2b55c07852f54958be/host/rx_backend.c#L148-L158) calls [`hos_sdl_desktop_size`](https://git.magicbean.dev/MagicBeanDev/Hookshot/src/commit/14320fa7307eaf207ec91a2b55c07852f54958be/host/hos_sdl.c#L178-L193), and a windowless start never loads SDL, so `version` is 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. With `if (!version) return 0;` at the top of the helper the same fixture returns "screen unknown" instead. I did not patch the game. Windows takes the `GetSystemMetrics` branch 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: - **16:9 (monitor's):** 1280x720, **1920x1080 (native)**, 2048x1152 (game's), 2560x1440, 3200x1800, 3840x2160, 5120x2880 - **32:9:** 3840x1080, 5120x1440 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 as `output_res=2048x1152` with `ultrawide=1`, to apply at the next start. No `the window's screen is` line 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 on `14320fa7`: 450 passed, 1 failed, 20 skipped; the one is main's `test_env_on` of 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_size` here, `take_text` there) 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.
pmwork-personal commented 2026-11-12 04:03:42 +00:00 (Migrated from github.com)

Retest at bd8fd684 with 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), no HOS_DISPLAY_MODES, from a first start with no options file. The windowless start is fine on Windows, as expected from mooki's note on the GetSystemMetrics branch.

All three fixes work on this screen

  • The log: 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.
  • First start: 21:9 (monitor's) with 2728x1152 (game's).
  • Right from there: 2560x1080 → 3008x1264 → 3408x1440 → 3440x1440 → 3840x1600 → 3936x1656 → 4352x1832. The script stopped at 4352x1832, so I didn't step on to 5120x2160 (native).
  • 21:9 → 16:9 (3840x2160, "Restart needed: Aspect ratio, Output resolution") → back to 21:9 gives 2728x1152 (game's) again, with no restart notice. Saved: ultrawide=1, output_res=2048x1152.

Two small notes on the list. Neither looks wrong to me:

  • On the real screen it has two sizes more than 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 fills output_extras with the six sizes directly, so it doesn't cover these two.
  • "(game's)" comes before 2560x1080, so the first Right goes down a size. That matches what you described; it only reads a little out of order next to the rest.

Merging main. GitHub shows the branch conflicting with main. To build it I merged main (up to a016b7a6) into bd8fd684 locally. All five conflicts are in gpu/vk/vk_menu.cpp, against #1864's Game language row, and each one keeps both sides:

  1. Includes: your display_modes.h, then main's vk_menu_i18n.h.
  2. save(): your ultrawide branch, then main's last branch, which now skips game_lang (that one goes to language.txt):
    } else if (r.value && r.value != &opts.game_lang) {   // (game_lang: language.txt's)
    
  3. set_row(): main's step line (it only adds a comment), then your output_for_aspect block.
  4. Both new functions: your outputs_follow_screen(), then main's game_lang_tick().
  5. menu_tick(): both calls, outputs_follow_screen(); then game_lang_tick();.

With that:

  • The Windows build succeeds.
  • The menu's row definitions match main's except Aspect ratio and Output resolution.
  • The result also merges with today's main (de866017) with no more conflicts.
  • Of the seven tests this PR adds or changes, five pass here. test_framegen_preset and test_rumble_menu don't compile (temporal_reset_failure undeclared in their stand-ins). They fail the same way on main here (a016b7a6 and de866017), 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 is line), F11 and Alt+Enter, and DLSS / DLSS-G at 21:9.

Retest at `bd8fd684` with 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), no `HOS_DISPLAY_MODES`, from a first start with no options file. The windowless start is fine on Windows, as expected from mooki's note on the `GetSystemMetrics` branch. **All three fixes work on this screen** - The log: `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. - First start: **21:9 (monitor's)** with **2728x1152 (game's)**. - Right from there: 2560x1080 → 3008x1264 → 3408x1440 → 3440x1440 → 3840x1600 → 3936x1656 → 4352x1832. The script stopped at 4352x1832, so I didn't step on to 5120x2160 (native). - 21:9 → 16:9 (3840x2160, "Restart needed: Aspect ratio, Output resolution") → back to 21:9 gives **2728x1152 (game's)** again, with no restart notice. Saved: `ultrawide=1`, `output_res=2048x1152`. Two small notes on the list. Neither looks wrong to me: - On the real screen it has two sizes more than `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 fills `output_extras` with the six sizes directly, so it doesn't cover these two. - "(game's)" comes before 2560x1080, so the first Right goes down a size. That matches what you described; it only reads a little out of order next to the rest. **Merging main.** GitHub shows the branch conflicting with main. To build it I merged main (up to `a016b7a6`) into `bd8fd684` locally. All five conflicts are in `gpu/vk/vk_menu.cpp`, against #1864's Game language row, and each one keeps both sides: 1. Includes: your `display_modes.h`, then main's `vk_menu_i18n.h`. 2. `save()`: your `ultrawide` branch, then main's last branch, which now skips `game_lang` (that one goes to language.txt): ```cpp } else if (r.value && r.value != &opts.game_lang) { // (game_lang: language.txt's) ``` 3. `set_row()`: main's step line (it only adds a comment), then your `output_for_aspect` block. 4. Both new functions: your `outputs_follow_screen()`, then main's `game_lang_tick()`. 5. `menu_tick()`: both calls, `outputs_follow_screen();` then `game_lang_tick();`. With that: - The Windows build succeeds. - The menu's row definitions match main's except Aspect ratio and Output resolution. - The result also merges with today's main (`de866017`) with no more conflicts. - Of the seven tests this PR adds or changes, five pass here. `test_framegen_preset` and `test_rumble_menu` don't compile (`temporal_reset_failure` undeclared in their stand-ins). They fail the same way on main here (`a016b7a6` and `de866017`), 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 is` line), F11 and Alt+Enter, and DLSS / DLSS-G at 21:9.
github-sync changed title from WIP: feat(menu): the Aspect ratio and Output resolution from the screen's display modes to feat(menu): the Aspect ratio and Output resolution from the screen's display modes 2026-11-12 06:23:20 +00:00
Owner

@nullifyshot commented on GitHub · 2026-11-12 04:15 UTC · original

Thanks 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_size now 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.py cuts the function from hos_sdl.c and 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 in menu_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_preset and test_rumble_menu fail the same way on main here (temporal_reset_failure in their stand-ins), so they're not from this PR. Of the 15 tests that fail on this PC, main fails the same 15.

> [@nullifyshot](https://github.com/nullifyshot) commented on GitHub · 2026-11-12 04:15 UTC · [original](https://github.com/NotRexed/Hookshot/pull/2566#issuecomment-6814323598) Thanks 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_size` now 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.py` cuts the function from `hos_sdl.c` and 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 in `menu_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_preset` and `test_rumble_menu` fail the same way on main here (`temporal_reset_failure` in their stand-ins), so they're not from this PR. Of the 15 tests that fail on this PC, main fails the same 15. <!-- github-sync:c:6814323598 -->
Owner

@nullifyshot commented on GitHub · 2026-11-12 04:23 UTC · original

@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 still output_res=0. Handheld draws nothing wider, so it keeps the old order. test_output_resolution checks your screen's nine choices up and back down.

> [@nullifyshot](https://github.com/nullifyshot) commented on GitHub · 2026-11-12 04:23 UTC · [original](https://github.com/NotRexed/Hookshot/pull/2566#issuecomment-6814458334) @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 still `output_res=0`. Handheld draws nothing wider, so it keeps the old order. `test_output_resolution` checks your screen's nine choices up and back down. <!-- github-sync:c:6814458334 -->
Owner

@Joey-Magnus approved on GitHub · 2026-11-12 14:33 UTC · original

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:

  • host/hos_wide_size.h:21 hos_output_own_shape changes a default. Any saved output_res more than 1% off 16:9 now sets the picture's shape over ultrawide=/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.
  • MKWVK_API_VERSION 20 (vk_api.h:18) collides with #2594 (also 20, 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.
> [@Joey-Magnus](https://github.com/Joey-Magnus) approved on GitHub · 2026-11-12 14:33 UTC · [original](https://github.com/NotRexed/Hookshot/pull/2566#pullrequestreview-5876216742) 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: - host/hos_wide_size.h:21 `hos_output_own_shape` changes a default. Any saved `output_res` more than 1% off 16:9 now sets the picture's shape over `ultrawide=`/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. - MKWVK_API_VERSION 20 (vk_api.h:18) collides with #2594 (also 20, `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. <!-- github-sync:r:5876216742 -->
Owner

@mooki000 commented on GitHub · 2026-11-12 14:49 UTC · original

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 main ee29dd55b593577ae0a7a6e2f871848ee5e176b4. The head is now d550ea55: the same eleven commits rebased onto main 51da0464 (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:

oe: output resolution 2048x1152 (the game's)
vk: the Output resolution's sizes: 6 (the screen's modes, with the common sizes of their shapes, and the game's own); shapes 16:9, 32:9
vk: no window: presents render offscreen (1920x1080)

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:

  • 16:9 / Monitor's shape: 1920x1080, 2048x1152 (game's), 2560x1440, 3840x2160, 5120x2880.
  • 32:9: 5120x1440.

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_MODES Sizes reached in the menu Applied
1280x720,1600x900,1920x1080 1280x720, 1600x896 (the authored 1600x900, rounded down to 8 as the description says), 1920x1080, 2048x1152, 2560x1440, 3200x1800, 3840x2160, 5120x2880 1920x1080
2560x1080,3440x1440,3840x1600,5120x2160 2560x1080, 3440x1440, 3840x1600, 5120x2160 under 21:9; 2048x1152 under 16:9 2560x1080

The 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 46780503 wrote an options file containing output_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 says picture 2400x1600, scene 1800x1200, camera ratio 1.5000.

Pictures (windowless captures from this PC, labelled):

A windowless start at the title; the saved 1920x1080 after a restart; the menu with no pending restart; a custom 2400x1600 written by main, read as 3:2

The choices with the screen unknown: Monitor's shape, 32:9 and 16:9 with their sizes

The same spot after a restart at each of the six sizes; 5120x1440 keeps the HUD in the middle 16:9

An authored 16:9 mode list: the eight sizes offered (1600x900 appears as 1600x896, the rounding down to 8 the description states)

An authored 21:9 mode list: its four sizes, and the game's own under Monitor's shape

2560x1080 applied from the authored list: the wider view, the HUD in the middle

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.

> [@mooki000](https://github.com/mooki000) commented on GitHub · 2026-11-12 14:49 UTC · [original](https://github.com/NotRexed/Hookshot/pull/2566#issuecomment-6826892844) **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 main `ee29dd55b593577ae0a7a6e2f871848ee5e176b4`. The head is now `d550ea55`: the same eleven commits rebased onto main `51da0464` (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: ```text oe: output resolution 2048x1152 (the game's) vk: the Output resolution's sizes: 6 (the screen's modes, with the common sizes of their shapes, and the game's own); shapes 16:9, 32:9 vk: no window: presents render offscreen (1920x1080) ``` 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: - 16:9 / Monitor's shape: **1920x1080, 2048x1152 (game's), 2560x1440, 3840x2160, 5120x2880**. - 32:9: **5120x1440**. 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_MODES` | Sizes reached in the menu | Applied | |---|---|---| | `1280x720,1600x900,1920x1080` | 1280x720, 1600x896 (the authored 1600x900, rounded down to 8 as the description says), 1920x1080, 2048x1152, 2560x1440, 3200x1800, 3840x2160, 5120x2880 | 1920x1080 | | `2560x1080,3440x1440,3840x1600,5120x2160` | 2560x1080, 3440x1440, 3840x1600, 5120x2160 under 21:9; 2048x1152 under 16:9 | 2560x1080 | The 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 `46780503` wrote an options file containing `output_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 says `picture 2400x1600, scene 1800x1200, camera ratio 1.5000`. **Pictures** (windowless captures from this PC, labelled): ![A windowless start at the title; the saved 1920x1080 after a restart; the menu with no pending restart; a custom 2400x1600 written by main, read as 3:2](https://gist.githubusercontent.com/mooki000/b86bd3f35969803fb032f71f035844f8/raw/b2c18af1f064814948518340abfa7a81ecce0bf9/hookshot-pr2566-start-restart-custom.jpg) ![The choices with the screen unknown: Monitor's shape, 32:9 and 16:9 with their sizes](https://gist.githubusercontent.com/mooki000/b86bd3f35969803fb032f71f035844f8/raw/b2c18af1f064814948518340abfa7a81ecce0bf9/hookshot-pr2566-unknown-screen-choices.jpg) ![The same spot after a restart at each of the six sizes; 5120x1440 keeps the HUD in the middle 16:9](https://gist.githubusercontent.com/mooki000/b86bd3f35969803fb032f71f035844f8/raw/b2c18af1f064814948518340abfa7a81ecce0bf9/hookshot-pr2566-six-applied-outputs.jpg) ![An authored 16:9 mode list: the eight sizes offered (1600x900 appears as 1600x896, the rounding down to 8 the description states)](https://gist.githubusercontent.com/mooki000/b86bd3f35969803fb032f71f035844f8/raw/b2c18af1f064814948518340abfa7a81ecce0bf9/hookshot-pr2566-authored-16x9-choices.jpg) ![An authored 21:9 mode list: its four sizes, and the game's own under Monitor's shape](https://gist.githubusercontent.com/mooki000/b86bd3f35969803fb032f71f035844f8/raw/b2c18af1f064814948518340abfa7a81ecce0bf9/hookshot-pr2566-authored-21x9-choices.jpg) ![2560x1080 applied from the authored list: the wider view, the HUD in the middle](https://gist.githubusercontent.com/mooki000/b86bd3f35969803fb032f71f035844f8/raw/b2c18af1f064814948518340abfa7a81ecce0bf9/hookshot-pr2566-authored-21x9-applied.jpg) **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. <!-- github-sync:c:6826892844 -->
Owner

@mooki000 commented on GitHub · 2026-11-12 15:40 UTC · original

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 ea303801 as above, not a new build of the patch-identical d550ea55 rebase.

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.

boot: widescreen: a 1.7778 screen is 16:9: the game's own picture
vk: 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
vk: swapchain 1920x1080, 4 images, mailbox (V-Sync Auto)

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.

> [@mooki000](https://github.com/mooki000) commented on GitHub · 2026-11-12 15:40 UTC · [original](https://github.com/NotRexed/Hookshot/pull/2566#issuecomment-6828175970) **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 `ea303801` as above, not a new build of the patch-identical `d550ea55` rebase. 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. ```text boot: widescreen: a 1.7778 screen is 16:9: the game's own picture vk: 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 vk: swapchain 1920x1080, 4 images, mailbox (V-Sync Auto) ``` 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. <!-- github-sync:c:6828175970 -->
Owner

@nullifyshot commented on GitHub · 2026-11-12 16:05 UTC · original

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:

  • Saved non-16:9 sizes now set the picture's shape: this is intended, and the description says so ("A size that isn't 16:9 sets the picture's shape"). Someone who saved 2560x1080 with 16:9 gets the wider view instead of a stretched one after updating.
  • MKWVK_API_VERSION 20: 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.
> [@nullifyshot](https://github.com/nullifyshot) commented on GitHub · 2026-11-12 16:05 UTC · [original](https://github.com/NotRexed/Hookshot/pull/2566#issuecomment-6828814598) 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: - **Saved non-16:9 sizes now set the picture's shape**: this is intended, and the description says so ("A size that isn't 16:9 sets the picture's shape"). Someone who saved 2560x1080 with 16:9 gets the wider view instead of a stretched one after updating. - **`MKWVK_API_VERSION` 20**: 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. <!-- github-sync:c:6828814598 -->
github-sync closed this pull request 2026-11-12 17:58:05 +00:00
Owner

@nullifyshot commented on GitHub · 2026-11-12 16:59 UTC · original

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 with RX_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 own shot leaves it out.

  • Start: 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.
  • Marks: Aspect ratio 16:9 (monitor's); Output resolution 2048x1152 (game's) and 1920x1080 (native), with the "your screen's own size" hint.
  • 16:9 sizes: 1280x720, 1360x768, 1600x896, 1920x1080 (native), 2048x1152 (game's), 2560x1440, 3200x1800, 3840x2160, 5120x2880. The arrows stop at both ends.
  • Another shape: Aspect ratio left to 5:3 (1280x768), then 16:10, which picked 1680x1048, the size closest to the screen's height. 16:10's sizes stepped 1680x1048, 1920x1200, 2560x1600. Going back to 16:9 (monitor's) brought back 2048x1152 (game's). The footer said "Restart needed: Aspect ratio, Output resolution".
  • Fullscreen row: On gave display: fullscreen (F11 for a window) and a 1920x1080 swapchain; Off gave display: windowed and a 1280x720 swapchain.
  • Move and resize: the window was moved, then resized from outside (SetWindowPos, not activated) to a 1600x900 client, then 960x540, then back to 1280x720. Each time the swapchain followed (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 is appears only when the screen changes).
  • No crash, device loss or driver fault in any run.

A crash in the build's shader step (not reproduced). When this head was built, the build's --prepare-shaders run 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.

> [@nullifyshot](https://github.com/nullifyshot) commented on GitHub · 2026-11-12 16:59 UTC · [original](https://github.com/NotRexed/Hookshot/pull/2566#issuecomment-6830217893) **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 with `RX_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 own `shot` leaves it out. - **Start:** `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`. - **Marks:** Aspect ratio **16:9 (monitor's)**; Output resolution **2048x1152 (game's)** and **1920x1080 (native)**, with the "your screen's own size" hint. - **16:9 sizes:** 1280x720, 1360x768, 1600x896, 1920x1080 (native), 2048x1152 (game's), 2560x1440, 3200x1800, 3840x2160, 5120x2880. The arrows stop at both ends. - **Another shape:** Aspect ratio left to 5:3 (1280x768), then 16:10, which picked 1680x1048, the size closest to the screen's height. 16:10's sizes stepped 1680x1048, 1920x1200, 2560x1600. Going back to 16:9 (monitor's) brought back 2048x1152 (game's). The footer said "Restart needed: Aspect ratio, Output resolution". - **Fullscreen row:** On gave `display: fullscreen (F11 for a window)` and a 1920x1080 swapchain; Off gave `display: windowed` and a 1280x720 swapchain. - **Move and resize:** the window was moved, then resized from outside (SetWindowPos, not activated) to a 1600x900 client, then 960x540, then back to 1280x720. Each time the swapchain followed (`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 is` appears only when the screen changes). - No crash, device loss or driver fault in any run. **A crash in the build's shader step (not reproduced).** When this head was built, the build's `--prepare-shaders` run 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. <!-- github-sync:c:6830217893 -->
Owner

@hckrman101 approved on GitHub · 2026-11-12 17:06 UTC · original

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_VERSION 20 is still the right bump: main is at 19, and screen_size is the only interface change.
  • No option ids added, so nothing to move.
  • 79253918: main's 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 to MkwVkHost/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_rows fails 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.

> [@hckrman101](https://github.com/hckrman101) approved on GitHub · 2026-11-12 17:06 UTC · [original](https://github.com/NotRexed/Hookshot/pull/2566#pullrequestreview-5878709448) 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_VERSION` 20 is still the right bump: main is at 19, and `screen_size` is the only interface change. - No option ids added, so nothing to move. - 79253918: main's `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 to `MkwVkHost`/`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_rows` fails 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. <!-- github-sync:r:5878709448 -->
Commenting is not possible because the repository is archived.
No description provided.