Repository navigation
feat(menu): the Aspect ratio row (was Ultrawide), at the monitor's shape by default - #2119
Conversation
…ape by default A player on an ultrawide, 16:10 or 4:3 screen starts with the game's 16:9 picture on it until they find the Graphics tab's Ultrawide row, change it and restart. The name also hides that the same row shapes the picture on screens narrower than 16:9. PC games usually follow the monitor's shape from the first start, with 16:9 as a choice. What changed: - The Ultrawide row is now Aspect ratio, and Monitor's shape is its default (gpu/vk/vk_menu.h; host/hos_main.c reads an options file with no ultrawide= line as Monitor's shape). 16:9 stays a choice. On a 16:9 screen nothing changes. - The row keeps its saved key, ultrawide= (ini_key), so a saved ultrawide=0 still means 16:9 and older options files load as before. - New help text for the row, and for Ultrawide menus, whose text named the old row. The readme's settings table and Output resolution notes, menu_restart.h and the comments that named the row follow. - Tests: test_menu_restart.py finds the row by its new name; test_audio_output_menu.py and test_stick_dead_zone.py give their option stubs the ultrawide field that ini_key now reads. Tested: - In game on Windows 11, RTX 5090 (driver 621.14), 5120x2160 (21:9) monitor, in one build with the widescreen cap change from feat/wide-past-4k: the picture takes the monitor's shape, and Kokiri Forest and Castle Town load. This change itself only moves a default and renames a row; the code that sizes the picture is unchanged. - test_menu_restart.py, test_audio_output_menu.py, test_stick_dead_zone.py, and the other tests that build ini_key or the row (test_framegen_preset, test_menu_env_on, test_recomplauncher_json, test_rumble_menu, test_upscaler_rows, test_output_resolution, test_widescreen_shapes) pass with llvm-mingw. - tests/run_all.py on Windows: 326 passed, 26 skipped, 4 failed. The 4 touch none of these files and fail the same on feat/wide-past-4k, which lacks this change: test_recorder_sync (its exit case), test_precompile_compute_guard (no windows.h for the MSVC target on this PC), nx2/test_nup_read_name (no zstandard module here), test_make_game_ngx (the Linux builder test's stub has no switching()). Not tested: - 16:10, 4:3 and 32:9 screens in game (test_widescreen_shapes.py covers their sizes). - Linux and the Steam Deck: SDL gives the screen's size there too, so a Steam Deck (16:10) now starts with a 16:10 picture instead of 16:9. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings in NotRexed#2052 (48:9), NotRexed#2078 (the off value's label) and NotRexed#2041 (the V-Sync row). Conflicts resolved with upstream's text kept and the row's new name on top: - vk_menu.cpp picture_for and hos_main.c's widescreen comment: NotRexed#2052's wording, "Ultrawide" read as the Aspect ratio. - tx_ultrawide: 16:9 again instead of NotRexed#2078's "Off (16:9)". NotRexed#2078 added "Off" because "Ultrawide: 16:9" read as already on; under the name Aspect ratio the value is a shape, and "Aspect ratio: Off (16:9)" would not read well. - readme: the settings table and NotRexed#2052's Output resolution notes, with the row's new name. - test_audio_output_menu.py, test_stick_dead_zone.py: the option stubs take both NotRexed#2041's vsync and the ultrawide key. Also follows the rename where the first commit missed it: recomplauncher.json (label, help, and its default now 1, so the launcher does not write 16:9 over the new default) and tests/test_menu_restart.cpp's sample row names. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-default # Conflicts: # gpu/vk/vk_menu.cpp # readme.md
…reen narrower than 16:9 The game saves every row, so an options file from before this PR holds ultrawide=0, the 16:9 that was the default, whether chosen or not: the new default would reach new installs only. On a screen narrower than 16:9 (16:10 such as the Steam Deck's 1280x800, or 4:3) 16:9 only adds bars above and below, so that file's 16:9 moves to the monitor's shape there; an ultrawide keeps its 16:9, which may have been chosen. - host/hos_wide_size.h hos_aspect_on: the rule, shared. A file from options_version 5 on keeps whichever it says. - host/hos_main.c starts the game with it (ultrawide= and options_version=). - The Insert menu follows what the host started with (host->aspect_ratio, in menu_init before restart_init), so the two agree on Linux too, where the menu has no screen size of its own. save() writes options_version 5. - readme.md: the move, in the Output resolution notes. - New tests/test_aspect_default.py: the rule over 16:10, 4:3, 5:4, 16:9, 1366x768, 21:9, 32:9, portrait and no screen size, and the source checks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Joey-Magnus
left a comment
There was a problem hiding this comment.
This is a good change, and it's well-contained. The host and the menu share one rule (hos_aspect_on), so they can't disagree. The saved key stays ultrawide=, so existing files keep working. The migration only moves the old default where 16:9 can only add bars. host->aspect_ratio was already in MkwVkHost, so no API bump is needed. On Linux, GetSystemMetrics gets SDL's screen size, which display_sdl.c sets before game_main, so the host side works there too. The merge with main is clean. The one red CI job (Windows part 2/2) is tests/test_native_astc_texture_count.py, which fails the same way on other PRs today and isn't related.
should fix: screenshots. The Graphics tab's row is renamed and the default picture changes shape on non-16:9 screens. CONTRIBUTING asks for screenshots when a change shows on screen. A shot of the renamed row, and the first-start picture on the 21:9 screen you tested, would cover it.
minor gpu/vk/vk_menu.cpp:2959-2962 (at the PR's head): menu_init infers "the host started this older file at the monitor's shape" from host->aspect_ratio() alone. That value is also set when HOS_ASPECT is given (host/hos_main.c uses it instead of the file). With an older file holding ultrawide=0, running once with, say, HOS_ASPECT=1.6 flips the row to Monitor's shape, and the next save() writes ultrawide=1 with options_version=5. That stores a choice the player never made. It's rare (a debugging switch), but an easy fix is to skip the move when getenv("HOS_ASPECT") is set, which is what hos_main.c checks.
minor: as you note, #2197 and #2110 also claim options_version=5. Whichever lands second has to bump the number and its version < N checks (here in hos_aspect_on and menu_init). It might be worth a line in the save() comment so the next person sees all three migrations. Steam Deck players with an existing options file will see the picture change to 16:10 after this update without choosing it. That is the intent, but it should go in the changelog/release notes, since the Deck wasn't run.
… 21:9 at the 4K output (#2120) On a 5120x2160 (21:9) monitor, the 3840x2160 output with Ultrawide on made a 4416x1864 picture, which the window copy then stretched to the screen: the monitor never got a native frame, and the Output resolution row showed 4416x1864. The cap behind it, 3840x2160's pixels, came from issue 1031, where a 32:9 screen's 7680x2160 picture ran its render targets' pool out. A 21:9 screen's native picture needs a third more pixels than 4K, not twice as many, and the pool already grows with the picture. **What changed** - `host/hos_wide_size.h`: a picture wider or narrower than 16:9 takes at most 5120x2160's pixels (was 3840x2160's). At the 4K output a 21:9 screen gets its native 5120x2160, and the Output resolution row says "5120x2160 (native)". A 32:9 screen at the 4K output is still capped, now at 6256x1760 (was 5400x1520), its shape kept. - No pool change: `host/nn_applet.c` already scales the render-target pool with the picture's pixels, to 915 MB at 5120x2160 (`test_rt_pool_minimum.py` now checks that case). - `HOS_WIDE_MAX_PX=<pixels>` sets another cap without a rebuild (8294400 is the old 3840x2160's), in case a GPU needs the smaller picture. Listed in `docs/development.md`; the readme gives the new cap. **Relation to #2052** Merged with current main; #2052's code is kept as merged (the output limits, `hos_wide_clamp` up to 48:9, the 24-row alignment at 48:9). With the new cap: - The 7680x1440 output renders at full size, since it has exactly 5120x2160's pixels, instead of 6528x1224. Its pool stays 900 MB, set by the output's own size as before; the 5120x2880 output already plays in that pool with more pixels. - 48:9 at the 4K output is kept to 7680x1440, exactly 48:9, with a 915 MB pool: the same pixels and pool as 21:9's 5120x2160. - `test_widescreen_shapes.py` keeps #2052's 6528x1224 cases (the 24-row alignment and the four-decimal `5.3333`) under `HOS_WIDE_MAX_PX=8294400`, the old cap, and adds the full-size 7680x1440 and 48:9 at 2160. `test_output_resolution.py`'s 7680x1440 output is full size; `test_rt_pool_minimum.py` adds 48:9 at the 4K output. **Tested** - In game on Windows 11, RTX 5090 (driver 621.14), 5120x2160 (21:9) monitor, 4K output, picture at the monitor's shape: picture 5120x2160, pool 915 MB, DLAA at 5120x2160. Kokiri Forest and Castle Town, where issue 1031's pool ran out, load. About 57 fps at native DLAA (GPU about 12 ms a frame). That run was before the merge with #2052. - `tests/run_all.py` on Windows (llvm-mingw) after the merge: 339 passed, 30 skipped, 4 failed. The 4 also fail on untouched main on this PC: `test_recorder_sync` (its exit case), `test_precompile_compute_guard`, `test_bake_portability`, and `nx2/test_nup_read_name` (no zstandard here). `test_widescreen_shapes.py`, `test_output_resolution.py`, `test_rt_pool_minimum.py` and `gpu/vk/tests/present_rect_test.cpp` pass. **Not tested** - 48:9 and 32:9 in game at the new sizes (the full-size 7680x1440 output, 48:9 and 32:9 at the 4K output). - GPUs with less memory than the 5090 at these sizes, and Linux. This and the Aspect ratio PR (#2119) edit the same readme paragraph, so whichever merges second needs a one-line merge. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
NotRexed#2197 (Esc opens the menu by default) took options_version 5 on main, so the Aspect ratio's move of an older file's 16:9 is now version 6: save() writes 6, menu_init and host/hos_wide_size.h hos_aspect_on move ultrawide=0 from files before 6, and both moves are kept in menu_init. tests/test_menu_keys.py (from NotRexed#2197) expected every version below the one save() writes to move Insert or F1 to Insert, F1 or Esc, which would move a version 5 file's chosen Insert or F1 again. It now reads the move's version from menu_init and checks that files from it up to save()'s version keep their choice; its host stub gains aspect_ratio, which menu_init's Aspect ratio move asks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Joey-Magnus
left a comment
There was a problem hiding this comment.
- This changes a default for everyone: new players, and Steam Deck or 16:10 players with an older options file, get the monitor's shape (
hos_aspect_on). It's only tested on one 21:9 machine, so it belongs in the release notes. - The earlier
HOS_ASPECTpoint is still open: the migration atvk_menu.cpp:3121trustshost->aspect_ratio()even whenHOS_ASPECTset it, so a debug run can saveultrawide=1withoptions_version=5. - Screenshots are still missing.
options_version=5is also used by #2197 and #2110, and this PR conflicts with #2177 over the Ultrawide row. Suggested order: #2197, then #2177, then this rebased.
…Rexed#2119's Aspect ratio row and a first start at 21:9 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
hckrman101
left a comment
There was a problem hiding this comment.
Looks good to me. The default flips to the monitor's shape, the saved key stays ultrawide=, and the older-file move is one function (hos_aspect_on) that the host and the menu both follow, so they can't disagree. I read the diff and the tests: test_aspect_default.py covers the shapes (16:10, 4:3, 5:4, 1366x768, ultrawides, portrait, no screen size), test_menu_keys.py now reads the old-shortcut version from menu_init so the version 6 bump doesn't move a chosen Insert/F1 again, and the ultrawide stubs in the other two tests match what ini_key now reads. save() writes 6, so a 16:9 chosen after the update stays. All CI jobs are green on this head.
Since Joey-Magnus's earlier reviews: the screenshots he asked for are in the description now (both links load), and the merge with main is clean. Still open, both small and neither a blocker:
- nit: Joey's
HOS_ASPECTpoint (vk_menu.cpp, theversion < 6block inmenu_init) is not fixed.host->aspect_ratio()is also set byHOS_ASPECTand by a custom output wider than 32:9, so a run with one of those on an olderultrawide=0file on a 16:10 screen can saveultrawide=1the player did not pick. It's a debug switch and a rare setup, so fine to follow up, but skipping the move whengetenv("HOS_ASPECT")is set would close it. options_version=6and the ordering with #2110 and #2177 are covered in the description; whichever lands later takes the next number. Because the default changes for everyone (Steam Deck and 16:10 players included, untested on a real 16:10 screen), it belongs in the release notes.
The decision to merge is yours (and NotRexed's, as maintainer); nothing here objects.
|
Thanks for the reviews and the merge. The The description now has the screenshots (the renamed row, and a first start on the 21:9 monitor) and a suggested release-notes line for the default change, Steam Deck and 16:10 included. |
…and NotRexed#2496) NotRexed#2119 renamed the Screen section's Ultrawide row to Aspect ratio and changed Ultrawide menus' help; NotRexed#2481 added HDR tone mapper after HDR contrast. Both changed rows inside the block this PR moves into sections: the sections keep main's definitions word for word (every one of main's 81 rows matches), with HDR tone mapper last in HDR. The readme's table takes main's Aspect ratio and HDR tone mapper lines in the section order. test_menu_sections.py also pins Profile, Aspect ratio, Ultrawide menus and HDR tone mapper in their sections. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Graphics tab is one list of 24 settings, with the window, HDR, upscaling, frame generation and GPU options mixed together, and about ten more are on their way in open PRs. This puts its rows under section titles, so the upscaler, frame generation or HDR can be found without scrolling the whole list. It is the first step of a menu organization laid out below for discussion. **What changed** - The Graphics rows in six sections, under heading rows like the Debug tab's Developer (a gold title, never selected, nothing to click): - Quality preset (first: it sets rows in every section) - **Resolution and upscaling:** Output resolution, Profile, Upscaler, DLSS model, Render resolution, Sharpness - **Frame generation and latency:** Frame generation, Frame pacing, Game frame rate cap, NVIDIA Reflex, AMD Anti-Lag 2 - **Screen:** Fullscreen, V-Sync, Window scaling filter, Aspect ratio (#2119), Ultrawide menus - **HDR:** HDR, HDR paper white, HDR peak brightness, HDR contrast (#2342), HDR tone mapper (#2481) - **Detail:** Anisotropic filtering - **GPU and memory:** GPU device, VRAM budget (#1859), Mesh shaders (Robust buffers is on Debug ▸ Developer since #2042) - Moves only: every row's definition is unchanged, word for word; no row is renamed and no value or saved key changes, so options files carry over, and #1864's tables (keyed by the English text) still find every row. They need the six headings. - No new drawing or input code: the menu already draws heading rows on any tab, and the keys, the pad and the mouse step over them. - `readme.md`: a sentence on the sections, and the option table in the new order. (Ultrawide menus and Mesh shaders have no line yet; #2105 adds them.) The **Frame generation by** row from #2118 gets its line in the table here too: main's readme has none yet. So do #1859's **VRAM budget** (under GPU and memory) and #2342's **HDR contrast** (under HDR). - `tests/test_upscaler_rows.py`: its order list follows the picture's path (output, upscaler, render size, sharpening, then the frame rows, then the window). New `tests/test_menu_sections.py`: the Graphics sections and which rows they hold, and on every tab no section that `row_shown` could leave empty (a title over nothing on a GPU without Reflex, say). **The plan this is step 1 of (for discussion)** Each tab answers one question; a section groups the rows you set together; a row that depends on another follows it; vendor rows sit beside their counterpart and hide where they can't run (no vendor sections, so nothing is left empty on AMD); rows to touch only when something is wrong go last; moves only, no renames. | Tab | Holds | Sections | |---|---|---| | **Game** (was Gameplay; first, where the menu opens) | how the game behaves and reads, and Restart and Quit, as a pause menu | flat; Restart and Quit last | | Graphics | how the picture is made, and what it costs | Quality preset · Resolution and upscaling · Frame generation and latency · Detail · GPU and memory | | **Display** (new) | how it gets to your screen, and what is drawn over it | Screen · HDR · Overlays (FPS counter, Menu size, from Game) | | Audio | sound out and in | + Microphone (from Game: it picks the input device) | | Controls | how you play | Camera · Controller · Buttons (optional) | | Debug | tools and developer switches | Robust buffers in Developer (#2042, merged) | Next PRs, one topic each: (2) a Display tab, taking the Screen and HDR sections over whole and adding Overlays; (3) the Gameplay tab titled Game, first in the strip, where the menu opens; (4) Microphone to Audio; (5) optional, sections on Controls. If a sixth tab is a no, this PR stands on its own. Open questions: 1. A sixth tab (Display)? 2. V-Sync on Display (the PC convention) or with frame generation (it changes with it)? 3. Sharpness with the upscaling rows (where players look for it) or with the window filter (where it runs)? 4. DLSS 5 (#1590) and #1834's Neural rows as a page opened from a Graphics row, like Motion, rather than a seventh tab? <details><summary>Where open PRs' rows would go</summary> - Resolution and upscaling: Sharpening method (#1979), DLSS 5 page (#1590), Native 3D resolution (#2017) - Frame generation and latency: Frame generation by (#2118, merged: in this section now), Frame generation method (#1834) - Detail: Shadow resolution (#1892), Screen filter and Filter strength (#1488) - GPU and memory: Texture streaming cache (#2017), Refresh memory (#888); VRAM budget (#1859, merged) is in this section now - Game (was Gameplay): Game language and Menu language (#1864), Skip title screen (#1560), Amiibo (#1521) - Controls: Camera speed (#1945) - #2042 (merged) moved Robust buffers to Debug ▸ Developer. - #2105: its readme check would read the heading rows as rows; it needs to skip `{TAB_..., "...", nullptr, 0, 0, 0, ..., true}` headings, or this PR adds that if it lands first. Whichever PR lands second moves one row on rebase; happy to do it for anyone. </details> **Testing** - `tests/run_all.py` on Windows 11 (llvm-mingw clang, 8 at once) after merging main at 2dfe901 (with #2119's Aspect ratio, #2481's HDR tone mapper, #2118's Frame generation by, #2342's HDR contrast and #1859's VRAM budget): 422 passed, 33 skipped, 2 failed, both outside this PR: `test_vram_budget_row.py` (fails the same on main's own sources here) and `test_portable_builder_startup.py` (its `dotnet publish` needs packages this PC does not have). A build of the branch compiles. All 81 of main's rows keep main's definitions word for word; the only additions are the six headings. `test_menu_sections.py` fails on main as it stands (no sections) and passes here; a layout with a section of Reflex and Anti-Lag 2 alone fails it. - A build of this branch (Windows 11, RTX 5090), headless with scripted menu keys at Small, Medium and Large: 12 Downs from the Quality preset land on V-Sync and 13 Ups come back past it to the last row, so every heading is stepped over both ways and Up from the first row wraps to the last; the help box always shows the selected row (at the first head, when Robust buffers was the last row). Again at this head (25feb16, after #2042, #1859 and #2119): Up from the Quality preset wraps to Mesh shaders, the last row now, under VRAM budget (the bottom screenshot). **Not tested**: in a window with a pad, the mouse wheel and a click just under a heading (the headless runs drive the keys); values kept across a restart (no row's saved key changes); AMD and Intel (their rows hide through `row_shown` as before; the new check makes sure no section can end up empty), Linux. **Screenshots** The Graphics tab at 1920x1080, Medium menu size: the top, then scrolled with the keys to V-Sync and to the last row (the selection steps over every heading; Up from the first row wraps to the last row). The bottom one is retaken at this head (25feb16, a 16:9 picture: `HOS_ASPECT=1.7778`, as on a 16:9 monitor); the others are from the first head (634ef65).    Small and Large:   _Taken from headless runs of this branch (`HOS_HEADLESS=1`, `RX_VK_SHOT_MENU=1`, the keys from `RX_VK_MENU_SCRIPT`), RTX 5090. Headless, the list never scrolls on main either: `menu_record_image` leaves `ease_step` at 0, so the build behind the scrolled shots had one more line there (`ease_step = 1.0f`, hence "-dirty" in the first head's build label), not part of this PR. The windowed menu eases as before._ 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…2119, NotRexed#2197, NotRexed#2481, NotRexed#2496) NotRexed#2177 landed (squashed), so this takes main's sections as they shipped and keeps this PR's change on top: the Screen, HDR and Overlays rows on the Display tab. Main's newer rows go with them, word for word: NotRexed#2119's Aspect ratio and Ultrawide menus text and NotRexed#2481's HDR tone mapper (last in Display's HDR). Every one of main's 88 rows keeps main's definition; the only changes are the moved rows' tab and the Overlays heading. draw_options keeps main's Save my pipeline list line (NotRexed#2447) with the restart line on Display too. The readme takes main's Menu shortcut text (NotRexed#2197) with six tabs, NotRexed#2177's VRAM budget line, and main's HDR tone mapper and Field of view lines. test_menu_sections.py pins Aspect ratio by name and HDR tone mapper in Display's HDR; test_scene_tonemap_pass.py (NotRexed#2481) finds the tone mapper row on Display. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
NotRexed#2197, NotRexed#2481, NotRexed#2496) into feat/menu-game-tab Takes NotRexed#2178 as merged with main (cf90c14f) and keeps this PR's change on top: the Game tab first, where the menu opens. Main's new Gameplay row, Field of view, goes with the Game rows at the table's top, word for word, after Game speed. Every one of main's 88 rows keeps main's definition; the only changes are the moved rows' tab (Display) and the Overlays heading. The readme takes main's Menu shortcut text (NotRexed#2197) with this PR's tab sentence, and Field of view among Game's rows. The tab enum, the strip's titles and the control bridge's names follow the same Game-first order. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…display modes The Output resolution row offered six fixed sizes, and the Aspect ratio row (NotRexed#2119) only 16:9 or the monitor's shape, so a 21:9, 16:10 or 4:3 size the screen has needed a hand-written output_res. The two rows now list what the screen offers: - Aspect ratio: 16:9, Monitor's shape (the default, its shape named), then the other shapes of the screen's display modes (5:4, 4:3, 16:10, 21:9, 32:9...). It sits right above the Output resolution, whose sizes it chooses. - Output resolution: the sizes of that shape (each side rounded down to 8, those the game accepts), the screen's own marked native, and the game's own 2048x1152 at 16:9. Choosing a shape takes the screen's own size when it has it, else the largest no taller than the screen. - A size more than 1 % off 16:9 sets the picture's shape (cameras, the HUD in the middle 16:9), over HOS_ASPECT and ultrawide=, in host/hos_main.c and the menu alike (hos_output_own_shape); ultrawide= shapes a 16:9 size, as before. - ultrawide= stays 0 or 1 with main's default and its move for older files; another shape is output_res's own. Old saved output numbers still load (kOutputs). Where the modes are not known (Linux), the sizes offered before. HOS_DISPLAY_MODES=<w>x<h>,... stands in for the screen's modes. - The control bridge's option_set output_res still reaches every size: one of another shape moves the Aspect ratio first, as the arrows keep to one shape. gpu/vk/display_modes.h holds the rules as plain code; tests/test_display_modes.py checks them. The menu's tests that cut out the output code follow the new numbering, test_menu_sections has the Aspect ratio above the Output resolution, and test_menu_setting_walk walks to a size of another shape through the Aspect ratio. Assisted-by: Claude Opus 5.5 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…S_ASPECT (#2541) #2119's review asked for this, and it came after the merge. `menu_init` takes the shape the host started with as the move of an older options file's 16:9. `HOS_ASPECT`, a debugging switch `host/hos_main.c` takes instead of the options file, sets that shape too: one run with `HOS_ASPECT=1.6` and an older `ultrawide=0` moved the row to Monitor's shape, and the next save kept it, a choice the player never made. **What changed** - `gpu/vk/vk_menu.cpp` `menu_init`: the move skips runs with `HOS_ASPECT` set, as `hos_main.c` does. - `tests/test_aspect_default.py` pins the condition. Without it the check fails (tried by taking the condition out). **Tested** - `tests/run_all.py` on Windows (llvm-mingw) on main 2dfe901: 421 passed, 33 skipped, 2 failed, both outside this change: `test_vram_budget_row` (fails the same on main here: clang stops on the unused `kVramMinimumOnly`) and `test_portable_builder_startup` (its `dotnet publish` needs packages this PC does not have). - Not run in game: it only changes what a run with the debugging switch saves. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…display modes The Output resolution row offered six fixed sizes, and the Aspect ratio row (NotRexed#2119) only 16:9 or the monitor's shape, so a 21:9, 16:10 or 4:3 size the screen has needed a hand-written output_res. The two rows now list what the screen offers: - Aspect ratio: 16:9, Monitor's shape (the default, its shape named), then the other shapes of the screen's display modes (5:4, 4:3, 16:10, 21:9, 32:9...). It sits right above the Output resolution, whose sizes it chooses. - Output resolution: the sizes of that shape (each side rounded down to 8, those the game accepts), the screen's own marked native, and the game's own 2048x1152 at 16:9. Choosing a shape takes the screen's own size when it has it, else the largest no taller than the screen. - A size more than 1 % off 16:9 sets the picture's shape (cameras, the HUD in the middle 16:9), over HOS_ASPECT and ultrawide=, in host/hos_main.c and the menu alike (hos_output_own_shape); ultrawide= shapes a 16:9 size, as before. - ultrawide= stays 0 or 1 with main's default and its move for older files; another shape is output_res's own. Old saved output numbers still load (kOutputs). Where the modes are not known (Linux), the sizes offered before. HOS_DISPLAY_MODES=<w>x<h>,... stands in for the screen's modes. - The control bridge's option_set output_res still reaches every size: one of another shape moves the Aspect ratio first, as the arrows keep to one shape. gpu/vk/display_modes.h holds the rules as plain code; tests/test_display_modes.py checks them. The menu's tests that cut out the output code follow the new numbering, test_menu_sections has the Aspect ratio above the Output resolution, and test_menu_setting_walk walks to a size of another shape through the Aspect ratio. Assisted-by: Claude Opus 5.5 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…display modes The Output resolution row offered six fixed sizes, and the Aspect ratio row (NotRexed#2119) only 16:9 or the monitor's shape, so a 21:9, 16:10 or 4:3 size the screen has needed a hand-written output_res. The two rows now list what the screen offers: - Aspect ratio: 16:9, Monitor's shape (the default, its shape named), then the other shapes of the screen's display modes (5:4, 4:3, 16:10, 21:9, 32:9...). It sits right above the Output resolution, whose sizes it chooses. - Output resolution: the sizes of that shape (each side rounded down to 8, those the game accepts), the screen's own marked native, and the game's own 2048x1152 at 16:9. Choosing a shape takes the screen's own size when it has it, else the largest no taller than the screen. - A size more than 1 % off 16:9 sets the picture's shape (cameras, the HUD in the middle 16:9), over HOS_ASPECT and ultrawide=, in host/hos_main.c and the menu alike (hos_output_own_shape); ultrawide= shapes a 16:9 size, as before. - ultrawide= stays 0 or 1 with main's default and its move for older files; another shape is output_res's own. Old saved output numbers still load (kOutputs). Where the modes are not known (Linux), the sizes offered before. HOS_DISPLAY_MODES=<w>x<h>,... stands in for the screen's modes. - The control bridge's option_set output_res still reaches every size: one of another shape moves the Aspect ratio first, as the arrows keep to one shape. gpu/vk/display_modes.h holds the rules as plain code; tests/test_display_modes.py checks them. The menu's tests that cut out the output code follow the new numbering, test_menu_sections has the Aspect ratio above the Output resolution, and test_menu_setting_walk walks to a size of another shape through the Aspect ratio. Assisted-by: Claude Opus 5.5 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…display modes The Output resolution row offered six fixed sizes, and the Aspect ratio row (NotRexed#2119) only 16:9 or the monitor's shape, so a 21:9, 16:10 or 4:3 size the screen has needed a hand-written output_res. The two rows now list what the screen offers: - Aspect ratio: 16:9, Monitor's shape (the default, its shape named), then the other shapes of the screen's display modes (5:4, 4:3, 16:10, 21:9, 32:9...). It sits right above the Output resolution, whose sizes it chooses. - Output resolution: the sizes of that shape (each side rounded down to 8, those the game accepts), the screen's own marked native, and the game's own 2048x1152 at 16:9. Choosing a shape takes the screen's own size when it has it, else the largest no taller than the screen. - A size more than 1 % off 16:9 sets the picture's shape (cameras, the HUD in the middle 16:9), over HOS_ASPECT and ultrawide=, in host/hos_main.c and the menu alike (hos_output_own_shape); ultrawide= shapes a 16:9 size, as before. - ultrawide= stays 0 or 1 with main's default and its move for older files; another shape is output_res's own. Old saved output numbers still load (kOutputs). Where the modes are not known (Linux), the sizes offered before. HOS_DISPLAY_MODES=<w>x<h>,... stands in for the screen's modes. - The control bridge's option_set output_res still reaches every size: one of another shape moves the Aspect ratio first, as the arrows keep to one shape. gpu/vk/display_modes.h holds the rules as plain code; tests/test_display_modes.py checks them. The menu's tests that cut out the output code follow the new numbering, test_menu_sections has the Aspect ratio above the Output resolution, and test_menu_setting_walk walks to a size of another shape through the Aspect ratio. Assisted-by: Claude Opus 5.5 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…display modes The Output resolution row offered six fixed sizes, and the Aspect ratio row (NotRexed#2119) only 16:9 or the monitor's shape, so a 21:9, 16:10 or 4:3 size the screen has needed a hand-written output_res. The two rows now list what the screen offers: - Aspect ratio: 16:9, Monitor's shape (the default, its shape named), then the other shapes of the screen's display modes (5:4, 4:3, 16:10, 21:9, 32:9...). It sits right above the Output resolution, whose sizes it chooses. - Output resolution: the sizes of that shape (each side rounded down to 8, those the game accepts), the screen's own marked native, and the game's own 2048x1152 at 16:9. Choosing a shape takes the screen's own size when it has it, else the largest no taller than the screen. - A size more than 1 % off 16:9 sets the picture's shape (cameras, the HUD in the middle 16:9), over HOS_ASPECT and ultrawide=, in host/hos_main.c and the menu alike (hos_output_own_shape); ultrawide= shapes a 16:9 size, as before. - ultrawide= stays 0 or 1 with main's default and its move for older files; another shape is output_res's own. Old saved output numbers still load (kOutputs). Where the modes are not known (Linux), the sizes offered before. HOS_DISPLAY_MODES=<w>x<h>,... stands in for the screen's modes. - The control bridge's option_set output_res still reaches every size: one of another shape moves the Aspect ratio first, as the arrows keep to one shape. gpu/vk/display_modes.h holds the rules as plain code; tests/test_display_modes.py checks them. The menu's tests that cut out the output code follow the new numbering, test_menu_sections has the Aspect ratio above the Output resolution, and test_menu_setting_walk walks to a size of another shape through the Aspect ratio. Assisted-by: Claude Opus 5.5 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…display modes The Output resolution row offered six fixed sizes, and the Aspect ratio row (NotRexed#2119) only 16:9 or the monitor's shape, so a 21:9, 16:10 or 4:3 size the screen has needed a hand-written output_res. The two rows now list what the screen offers: - Aspect ratio: 16:9, Monitor's shape (the default, its shape named), then the other shapes of the screen's display modes (5:4, 4:3, 16:10, 21:9, 32:9...). It sits right above the Output resolution, whose sizes it chooses. - Output resolution: the sizes of that shape (each side rounded down to 8, those the game accepts), the screen's own marked native, and the game's own 2048x1152 at 16:9. Choosing a shape takes the screen's own size when it has it, else the largest no taller than the screen. - A size more than 1 % off 16:9 sets the picture's shape (cameras, the HUD in the middle 16:9), over HOS_ASPECT and ultrawide=, in host/hos_main.c and the menu alike (hos_output_own_shape); ultrawide= shapes a 16:9 size, as before. - ultrawide= stays 0 or 1 with main's default and its move for older files; another shape is output_res's own. Old saved output numbers still load (kOutputs). Where the modes are not known (Linux), the sizes offered before. HOS_DISPLAY_MODES=<w>x<h>,... stands in for the screen's modes. - The control bridge's option_set output_res still reaches every size: one of another shape moves the Aspect ratio first, as the arrows keep to one shape. gpu/vk/display_modes.h holds the rules as plain code; tests/test_display_modes.py checks them. The menu's tests that cut out the output code follow the new numbering, test_menu_sections has the Aspect ratio above the Output resolution, and test_menu_setting_walk walks to a size of another shape through the Aspect ratio. Assisted-by: Claude Opus 5.5 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…display modes The Output resolution row offered six fixed sizes, and the Aspect ratio row (NotRexed#2119) only 16:9 or the monitor's shape, so a 21:9, 16:10 or 4:3 size the screen has needed a hand-written output_res. The two rows now list what the screen offers: - Aspect ratio: 16:9, Monitor's shape (the default, its shape named), then the other shapes of the screen's display modes (5:4, 4:3, 16:10, 21:9, 32:9...). It sits right above the Output resolution, whose sizes it chooses. - Output resolution: the sizes of that shape (each side rounded down to 8, those the game accepts), the screen's own marked native, and the game's own 2048x1152 at 16:9. Choosing a shape takes the screen's own size when it has it, else the largest no taller than the screen. - A size more than 1 % off 16:9 sets the picture's shape (cameras, the HUD in the middle 16:9), over HOS_ASPECT and ultrawide=, in host/hos_main.c and the menu alike (hos_output_own_shape); ultrawide= shapes a 16:9 size, as before. - ultrawide= stays 0 or 1 with main's default and its move for older files; another shape is output_res's own. Old saved output numbers still load (kOutputs). Where the modes are not known (Linux), the sizes offered before. HOS_DISPLAY_MODES=<w>x<h>,... stands in for the screen's modes. - The control bridge's option_set output_res still reaches every size: one of another shape moves the Aspect ratio first, as the arrows keep to one shape. gpu/vk/display_modes.h holds the rules as plain code; tests/test_display_modes.py checks them. The menu's tests that cut out the output code follow the new numbering, test_menu_sections has the Aspect ratio above the Output resolution, and test_menu_setting_walk walks to a size of another shape through the Aspect ratio. Assisted-by: Claude Opus 5.5 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…display modes The Output resolution row offered six fixed sizes, and the Aspect ratio row (NotRexed#2119) only 16:9 or the monitor's shape, so a 21:9, 16:10 or 4:3 size the screen has needed a hand-written output_res. The two rows now list what the screen offers: - Aspect ratio: 16:9, Monitor's shape (the default, its shape named), then the other shapes of the screen's display modes (5:4, 4:3, 16:10, 21:9, 32:9...). It sits right above the Output resolution, whose sizes it chooses. - Output resolution: the sizes of that shape (each side rounded down to 8, those the game accepts), the screen's own marked native, and the game's own 2048x1152 at 16:9. Choosing a shape takes the screen's own size when it has it, else the largest no taller than the screen. - A size more than 1 % off 16:9 sets the picture's shape (cameras, the HUD in the middle 16:9), over HOS_ASPECT and ultrawide=, in host/hos_main.c and the menu alike (hos_output_own_shape); ultrawide= shapes a 16:9 size, as before. - ultrawide= stays 0 or 1 with main's default and its move for older files; another shape is output_res's own. Old saved output numbers still load (kOutputs). Where the modes are not known (Linux), the sizes offered before. HOS_DISPLAY_MODES=<w>x<h>,... stands in for the screen's modes. - The control bridge's option_set output_res still reaches every size: one of another shape moves the Aspect ratio first, as the arrows keep to one shape. gpu/vk/display_modes.h holds the rules as plain code; tests/test_display_modes.py checks them. The menu's tests that cut out the output code follow the new numbering, test_menu_sections has the Aspect ratio above the Output resolution, and test_menu_setting_walk walks to a size of another shape through the Aspect ratio. Assisted-by: Claude Opus 5.5 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…display modes The Output resolution row offered six fixed sizes, and the Aspect ratio row (NotRexed#2119) only 16:9 or the monitor's shape, so a 21:9, 16:10 or 4:3 size the screen has needed a hand-written output_res. The two rows now list what the screen offers: - Aspect ratio: 16:9, Monitor's shape (the default, its shape named), then the other shapes of the screen's display modes (5:4, 4:3, 16:10, 21:9, 32:9...). It sits right above the Output resolution, whose sizes it chooses. - Output resolution: the sizes of that shape (each side rounded down to 8, those the game accepts), the screen's own marked native, and the game's own 2048x1152 at 16:9. Choosing a shape takes the screen's own size when it has it, else the largest no taller than the screen. - A size more than 1 % off 16:9 sets the picture's shape (cameras, the HUD in the middle 16:9), over HOS_ASPECT and ultrawide=, in host/hos_main.c and the menu alike (hos_output_own_shape); ultrawide= shapes a 16:9 size, as before. - ultrawide= stays 0 or 1 with main's default and its move for older files; another shape is output_res's own. Old saved output numbers still load (kOutputs). Where the modes are not known (Linux), the sizes offered before. HOS_DISPLAY_MODES=<w>x<h>,... stands in for the screen's modes. - The control bridge's option_set output_res still reaches every size: one of another shape moves the Aspect ratio first, as the arrows keep to one shape. gpu/vk/display_modes.h holds the rules as plain code; tests/test_display_modes.py checks them. The menu's tests that cut out the output code follow the new numbering, test_menu_sections has the Aspect ratio above the Output resolution, and test_menu_setting_walk walks to a size of another shape through the Aspect ratio. Assisted-by: Claude Opus 5.5 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A player on an ultrawide, 16:10 or 4:3 screen starts with the game's 16:9 picture until they find the Graphics tab's Ultrawide row, change it and restart. The name also hides that the same row shapes the picture on screens narrower than 16:9. PC games usually follow the monitor's shape from the first start, with 16:9 as a choice.
What changed
gpu/vk/vk_menu.h;host/hos_main.creads an options file with noultrawide=line as Monitor's shape). 16:9 stays a choice. On a 16:9 screen nothing changes.ultrawide=(ini_key), so a savedultrawide=0still means 16:9.ultrawide=0, the old default, whether chosen or not. On a screen narrower than 16:9 (16:10 such as the Steam Deck's1280x800, or 4:3), where 16:9 only adds bars above and below, that file's 16:9 moves to Monitor's shape; an ultrawide keeps
its 16:9 (often a real choice there). The rule is one function,
hos_aspect_oninhost/hos_wide_size.h:host/hos_main.cstarts the game with it, and the menu's row follows what the host started with (
host->aspect_ratio(), inmenu_initbefore
restart_init), so the two agree on Linux too, where the menu has no screen size of its own.save()writesoptions_version=6, so a 16:9 chosen after the update stays. Not in this PR: skipping the move in runs withHOS_ASPECTset (Joey's point)came after the merge and follows in its own PR.
recomplauncher.json: the same label and help, and its default is1, so the launcher does not write 16:9 over the new default.menu_restart.hand the comments that named the row follow (feat(video): support 48:9 triple-monitor picture geometry #2052's wording kept, with the new name).test_menu_restart.pyandtest_menu_restart.cppuse the new name;test_audio_output_menu.pyandtest_stick_dead_zone.pygive their option stubs theultrawidefield thatini_keynow reads. Newtest_aspect_default.py:hos_aspect_onover 16:10, 4:3, 5:4, 16:9, 1366x768, 21:9, 32:9, portrait and no screen size, plus the host and menu source checks.For the release notes (this changes a default for everyone, and only a 21:9 PC ran it):
Tested
tests/run_all.pyon Windows (llvm-mingw) after merging main at 06c30bd (with feat(menu): Esc opens the Insert menu by default (Menu shortcut "Insert, F1 or Esc") #2197): 392 passed, 31 skipped, 1 failed:test_vram_budget_row, which fails the same way on main's own sources on this PC (clang stops on the unusedkVramMinimumOnly).hos_main.c,vk_menu.cpp) before that merge; the merge itself only adds the version 6 move next to feat(menu): Esc opens the Insert menu by default (Menu shortcut "Insert, F1 or Esc") #2197's. The move for older files was not run in game: this PC has a 21:9 screen, where an older 16:9 stays. The tests this PR touches pass, and so do the others that buildini_keyor the row:test_framegen_preset,test_menu_env_on,test_recomplauncher_json,test_rumble_menu,test_upscaler_rows,test_vsync_row,test_output_resolution,test_widescreen_shapes.Not tested
test_widescreen_shapes.pycovers their sizes).Screenshots
From a headless run of this branch (head 2a74ec4) on Windows 11, RTX 5090, with a 5120x2160 (21:9) monitor and no options file, so a first start (
HOS_HEADLESS=1,RX_VK_SHOTSat 2560x1080; the save loaded withHOS_INPUT_SCRIPTpresses). The log:widescreen: picture 3416x1440, scene 2560x1080, camera ratio 2.3722.The menu shot is the middle 16:9 of the frame: behind it the title movie stays 16:9 with black sides. Its build had one extra line so the list scrolls headless (
ease_step = 1.0finmenu_record_image), not part of this PR.The widescreen cap PR (#2120) edited the same readme paragraph; it has merged, and the paragraph is merged with it here.
options_version: #2197 (merged) took 5 for the Esc default, so this writes 6 and moves an older file's 16:9 from filesbefore 6 (
version < 6inhos_aspect_onandmenu_init). #2110 also writes 5 today; whichever of the two lands latertakes the next number. #2197's
test_menu_keys.pycompilesmenu_init's options read, which now also readshost->aspect_ratio, so itshoststub gains that member. Its check of older files expected every version below the onesave()writes to move Insert or F1 to Insert, F1 or Esc, which would move a version 5 file's chosen Insert or F1 again; itnow reads the move's version from
menu_initand checks that files from it up tosave()'s version keep their choice.🤖 Generated with Claude Code