feat: Monitor's shape for screens narrower than 16:9 (4:3, 16:10, 5:4) #1224

Merged
nesrodzair merged 2 commits from feat/narrow-screens into main 2026-11-09 22:38:50 +00:00
nesrodzair commented 2026-11-09 18:09:42 +00:00 (Migrated from github.com)

What changed

The Ultrawide option's Monitor's shape only acted on screens wider than 16:9. On a 4:3 or 16:10 screen the log said a 1.3333 screen is not wider than 16:9: the game's own picture and the game showed its 16:9 picture with black bars.

host/hos_main.c now runs the same sizing for a narrower screen too, down to 5:4 (a narrower one is kept at 5:4, the same floor as present_window_size in rx_present_fx.cpp):

  • the picture keeps the game's height and gets narrower: 1920 x 1440 for a 4:3 screen at the 1440 output, 2880 x 2160 at the 4K one;
  • the scene stays 3/4 of the picture (1440 x 1080 for 4:3);
  • the cameras take the picture's ratio (same height as 16:9, narrower sides);
  • hos_wide_squeeze is capped at 1, so the HUD and menus fill the picture's width instead of a middle 16:9 wider than the screen (it was 133% and cut the menus at both sides). hos_wide_margin is then 0 and movies fill the picture.

Screens wider than 16:9 go through the exact same code as before (same sizes, squeeze and margin, checked by the test for 21:9 and 32:9). A 16:9 screen still gets the game's own picture. The menu's help line and the readme's Ultrawide rows now mention narrower screens.

Known limitation

As with ultrawide, the HUD is drawn on the game's 16:9 grid and stretched to the picture: on 4:3 it is 25% narrower than on 16:9, and movies are squeezed the same way. Proper 4:3 HUD placement would need new UI patches in nx2/oot_wide.py and is not part of this PR.

How it was tested

  • tests/test_widescreen_shapes.py (new, game-free, registered in tests/run_all.py): lifts the block out of hos_main.c and checks picture, scene, camera ratio, HUD squeeze and movie margin for 4:3, 16:10, 5:4, narrower than 5:4, 4:3 at the 4K output, 16:9, 21:9, 32:9 and Ultrawide off. It fails on main for the narrow cases and passes for the wide ones unchanged.
  • python -I -B tests/run_all.py: 140 passed, 0 failed, 17 skipped (Linux, missing headers or tools only).
  • In game: the same change was first tried as a binary patch of hookshot.exe (threshold of the 16:9 test, min(ratio, 16/9) for the HUD width). Played on a 4:3 IBM CRT at 2048 x 1536 (RTX 5080, Windows): the log shows picture 1920x1440, scene 1440x1080, camera ratio 1.3333, HUD and movies in the middle 100.0%, no black bars, correct camera and FOV, and the pause and System menus fit on screen. Kokiri Forest and Link's house.
## What changed The Ultrawide option's *Monitor's shape* only acted on screens wider than 16:9. On a 4:3 or 16:10 screen the log said `a 1.3333 screen is not wider than 16:9: the game's own picture` and the game showed its 16:9 picture with black bars. `host/hos_main.c` now runs the same sizing for a narrower screen too, down to 5:4 (a narrower one is kept at 5:4, the same floor as `present_window_size` in `rx_present_fx.cpp`): - the picture keeps the game's height and gets narrower: 1920 x 1440 for a 4:3 screen at the 1440 output, 2880 x 2160 at the 4K one; - the scene stays 3/4 of the picture (1440 x 1080 for 4:3); - the cameras take the picture's ratio (same height as 16:9, narrower sides); - `hos_wide_squeeze` is capped at 1, so the HUD and menus fill the picture's width instead of a middle 16:9 wider than the screen (it was 133% and cut the menus at both sides). `hos_wide_margin` is then 0 and movies fill the picture. Screens wider than 16:9 go through the exact same code as before (same sizes, squeeze and margin, checked by the test for 21:9 and 32:9). A 16:9 screen still gets the game's own picture. The menu's help line and the readme's Ultrawide rows now mention narrower screens. ## Known limitation As with ultrawide, the HUD is drawn on the game's 16:9 grid and stretched to the picture: on 4:3 it is 25% narrower than on 16:9, and movies are squeezed the same way. Proper 4:3 HUD placement would need new UI patches in `nx2/oot_wide.py` and is not part of this PR. ## How it was tested - `tests/test_widescreen_shapes.py` (new, game-free, registered in `tests/run_all.py`): lifts the block out of `hos_main.c` and checks picture, scene, camera ratio, HUD squeeze and movie margin for 4:3, 16:10, 5:4, narrower than 5:4, 4:3 at the 4K output, 16:9, 21:9, 32:9 and Ultrawide off. It fails on `main` for the narrow cases and passes for the wide ones unchanged. - `python -I -B tests/run_all.py`: 140 passed, 0 failed, 17 skipped (Linux, missing headers or tools only). - In game: the same change was first tried as a binary patch of `hookshot.exe` (threshold of the 16:9 test, `min(ratio, 16/9)` for the HUD width). Played on a 4:3 IBM CRT at 2048 x 1536 (RTX 5080, Windows): the log shows `picture 1920x1440, scene 1440x1080, camera ratio 1.3333, HUD and movies in the middle 100.0%`, no black bars, correct camera and FOV, and the pause and System menus fit on screen. Kokiri Forest and Link's house.
hckrman101 (Migrated from github.com) reviewed 2026-11-09 18:21:35 +00:00
hckrman101 (Migrated from github.com) left a comment

Nice, small change. What I checked:

  • Wider than 16:9 still goes through the exact same math (only the new || arm and the 5:4 clamp are added; fmin(1.0, ...) is a no-op when the ratio is above 16:9), so 21:9 / 32:9 results are unchanged.
  • With squeeze = 1 and margin = 0, the HUD and MOVIE patches in nx2/oot_wide.py become no-ops (*= 1.0, and _m > 0.0f is false), so a narrow screen just gets the game's 16:9 UI stretched to the picture, which is the limitation you describe.
  • hos_rt_pool_size takes the larger of the output and the widescreen picture, so a narrower picture can't shrink the RT pool.
  • The new else-branch log text is right: that branch is now only reached for 16:9 +-1%.
  • The test lifts the real block out of hos_main.c, checks real values (including 21:9/32:9 as regression cases) and is registered in run_all.py with --cc gcc, like the other C fixtures.

One issue before this is ready:

The default window stretches the 4:3 picture. host/display.c (and host/posix/display_sdl.c on Linux) size the initial window as hos_aspect_ratio > 1.8f ? 720 * ratio : 1280, so with this PR a 4:3 or 16:10 screen still opens a 1280x720 window. On the Vulkan path vk_replay.cpp sets preserve_stretch whenever aspect_ratio() > 1.0f, so present_rect stretches the 1920x1440 picture over the whole 16:9 window: everything ends up 33% too wide until the player goes fullscreen. Fullscreen is off by default, and your CRT test was fullscreen, so this probably didn't show up. Something like (hos_aspect_ratio > 1.8f || (hos_aspect_ratio > 0.0f && hos_aspect_ratio < 1.76f)) ? (int)(720 * hos_aspect_ratio + 0.5f) : 1280 in both files would open a 960x720 window for 4:3 instead.

Small things:

  • The comments that describe the feature still say it only widens: the block comment above this code in hos_main.c ("the world wider at the sides", "the HUD, menus and movies the middle 16:9") and the nx2/oot_wide.py docstring ("the width grows"). Worth updating so they match.
  • FYI, #1183 (save picture on ultrawide) uses hos_wide_squeeze too. With squeeze = 1 its patch leaves the viewport alone, so on 4:3 the save thumbnail will be the whole 4:3 picture squashed into the 16:9 texture. Not a blocker, just something to keep in mind if both land.
Nice, small change. What I checked: - Wider than 16:9 still goes through the exact same math (only the new `||` arm and the 5:4 clamp are added; `fmin(1.0, ...)` is a no-op when the ratio is above 16:9), so 21:9 / 32:9 results are unchanged. - With squeeze = 1 and margin = 0, the `HUD` and `MOVIE` patches in `nx2/oot_wide.py` become no-ops (`*= 1.0`, and `_m > 0.0f` is false), so a narrow screen just gets the game's 16:9 UI stretched to the picture, which is the limitation you describe. - `hos_rt_pool_size` takes the larger of the output and the widescreen picture, so a narrower picture can't shrink the RT pool. - The new else-branch log text is right: that branch is now only reached for 16:9 +-1%. - The test lifts the real block out of `hos_main.c`, checks real values (including 21:9/32:9 as regression cases) and is registered in `run_all.py` with `--cc gcc`, like the other C fixtures. One issue before this is ready: **The default window stretches the 4:3 picture.** `host/display.c` (and `host/posix/display_sdl.c` on Linux) size the initial window as `hos_aspect_ratio > 1.8f ? 720 * ratio : 1280`, so with this PR a 4:3 or 16:10 screen still opens a 1280x720 window. On the Vulkan path `vk_replay.cpp` sets `preserve_stretch` whenever `aspect_ratio() > 1.0f`, so `present_rect` stretches the 1920x1440 picture over the whole 16:9 window: everything ends up 33% too wide until the player goes fullscreen. Fullscreen is off by default, and your CRT test was fullscreen, so this probably didn't show up. Something like `(hos_aspect_ratio > 1.8f || (hos_aspect_ratio > 0.0f && hos_aspect_ratio < 1.76f)) ? (int)(720 * hos_aspect_ratio + 0.5f) : 1280` in both files would open a 960x720 window for 4:3 instead. Small things: - The comments that describe the feature still say it only widens: the block comment above this code in `hos_main.c` ("the world wider at the sides", "the HUD, menus and movies the middle 16:9") and the `nx2/oot_wide.py` docstring ("the width grows"). Worth updating so they match. - FYI, #1183 (save picture on ultrawide) uses `hos_wide_squeeze` too. With squeeze = 1 its patch leaves the viewport alone, so on 4:3 the save thumbnail will be the whole 4:3 picture squashed into the 16:9 texture. Not a blocker, just something to keep in mind if both land.
@ -1376,2 +1378,4 @@
if (want > game * 1.01 || (want > 1.0f && want < game * 0.99)) {
double r = want > 32.0 / 9.0 ? 32.0 / 9.0 : want < 5.0 / 4.0 ? 5.0 / 4.0 : want;
uint32_t hos_output_size(int height); /* (host/nn_applet.c: the Output resolution's height, 1440 as shipped) */
unsigned oh = hos_output_size(1), ow = (unsigned)(oh * r / 8 + 0.5) * 8; /* (whole multiples of 8 pixels) */
hckrman101 (Migrated from github.com) commented 2026-11-09 18:21:35 +00:00

This now also covers 16:10 and 4:3, but the initial window size in host/display.c / host/posix/display_sdl.c only follows hos_aspect_ratio above 1.8. A narrow picture still gets a 1280x720 window and is stretched over it (preserve_stretch in vk_replay.cpp). See the review summary.

This now also covers 16:10 and 4:3, but the initial window size in `host/display.c` / `host/posix/display_sdl.c` only follows `hos_aspect_ratio` above 1.8. A narrow picture still gets a 1280x720 window and is stretched over it (`preserve_stretch` in `vk_replay.cpp`). See the review summary.
nesrodzair (Migrated from github.com) reviewed 2026-11-09 19:42:40 +00:00
@ -1376,2 +1378,4 @@
if (want > game * 1.01 || (want > 1.0f && want < game * 0.99)) {
double r = want > 32.0 / 9.0 ? 32.0 / 9.0 : want < 5.0 / 4.0 ? 5.0 / 4.0 : want;
uint32_t hos_output_size(int height); /* (host/nn_applet.c: the Output resolution's height, 1440 as shipped) */
unsigned oh = hos_output_size(1), ow = (unsigned)(oh * r / 8 + 0.5) * 8; /* (whole multiples of 8 pixels) */
nesrodzair (Migrated from github.com) commented 2026-11-09 19:42:40 +00:00

Thanks for the review! Pushed 3060261 (rebased on main, so the vk_menu.cpp conflict is gone):

  • Default window: host/display.c and host/posix/display_sdl.c now use hos_aspect_ratio > 0.0f ? 720 * ratio : 1280. hos_aspect_ratio is only set when Monitor's shape is active, and it is never between 1.76 and 1.8 then, so this covers both sides: 960x720 for 4:3, 1720x720 for 21:9 as before.
  • Comments: hos_main.c's block comment, oot_wide.py's docstring and the menu hint now describe narrower screens too.
  • #1183 landed first, so I handled it here: THUMB now reads hos_wide_ratio. Past 16:9 it widens the viewport exactly as before; narrower than 16:9 it makes it taller and centred, so the save picture shows the picture's middle band at its own shape (no squeeze, no bars). tests/test_oot_wide_save_picture.py now also checks 4:3, 16:10 and 5:4, and the wide cases are unchanged.
Thanks for the review! Pushed 3060261 (rebased on main, so the vk_menu.cpp conflict is gone): - Default window: host/display.c and host/posix/display_sdl.c now use `hos_aspect_ratio > 0.0f ? 720 * ratio : 1280`. hos_aspect_ratio is only set when Monitor's shape is active, and it is never between 1.76 and 1.8 then, so this covers both sides: 960x720 for 4:3, 1720x720 for 21:9 as before. - Comments: hos_main.c's block comment, oot_wide.py's docstring and the menu hint now describe narrower screens too. - #1183 landed first, so I handled it here: THUMB now reads hos_wide_ratio. Past 16:9 it widens the viewport exactly as before; narrower than 16:9 it makes it taller and centred, so the save picture shows the picture's middle band at its own shape (no squeeze, no bars). tests/test_oot_wide_save_picture.py now also checks 4:3, 16:10 and 5:4, and the wide cases are unchanged.
hckrman101 (Migrated from github.com) approved these changes 2026-11-09 20:07:06 +00:00
hckrman101 (Migrated from github.com) left a comment

Looks good, thanks. The window now opens at the picture's shape on both platforms (960x720 for 4:3), HOS_WINDOW_SIZE and the DPI scaling are unaffected, and 16:9 and wide are unchanged. The save picture crop checks out too: the viewport is 306*(16/9)/ratio tall, centred, so the middle band fills the texture at the picture's own shape. Comments and tests are updated and registered. CI shows nothing run yet (workflows waiting for approval).

Looks good, thanks. The window now opens at the picture's shape on both platforms (960x720 for 4:3), HOS_WINDOW_SIZE and the DPI scaling are unaffected, and 16:9 and wide are unchanged. The save picture crop checks out too: the viewport is 306*(16/9)/ratio tall, centred, so the middle band fills the texture at the picture's own shape. Comments and tests are updated and registered. CI shows nothing run yet (workflows waiting for approval).
Commenting is not possible because the repository is archived.
No description provided.