feat(video): a Window choice that fills the actual window, Hor+ and Vert+ #142
No reviewers
Labels
No labels
area/accessibility
area/audio
area/input
area/install
area/menu
area/renderer
area/saves
area/upscaling
good first issue
gpu/amd
gpu/igpu
gpu/intel
gpu/nvidia
help wanted
kind
bug
kind
chore
kind
docs
kind
feature
kind
performance
kind
question
needs measurements
platform/linux
platform/steam-deck
platform/windows
preview
priority
critical
priority
high
priority
low
status
blocked
status
confirmed
status
duplicate
status
invalid
status
needs-info
status
wontfix
testing wanted
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Longshot!142
Loading…
Reference in a new issue
No description provided.
Delete branch "simonwjackson/Longshot:feat/window-aspect"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Why
Monitor's shape follows the monitor, not a resized window. Window fills the actual window and keeps the 16:9 horizontal view on taller shapes, showing more above and below rather than cropping the sides.
Also proposed on Hookshot: https://github.com/simonwjackson/Hookshot/tree/feat-window-aspect.
What changed
ultrawide=2, using a stable -1 UI sentinel. Default monitor behavior stays unchanged.hos_wide.c/.h, Windows/SDL display startup and resize,hos_main.c,rx_backend.candnx2/oot_wide.pykeep the camera/culling matched to the actual window. Render targets keep their startup size.oot_ui_squeeze.h,vk_draw.cpp,vk_internal.handvk_main.cpppreserve centred 16:9 UI proportions as the window resizes; no HUD docking/size controls.Flags and settings
ultrawide=2HOS_ASPECT(existing override)Measurements
main:
316f9319c8(2026-11-13 UTC)this PR: feat/window-aspect @
1c66d19babHardware: AMD RX 7900 XT 20 GB, RADV Mesa 26.2.3; Ryzen 5 7600X, 32 GB RAM; NixOS 26.11, Linux 7.2.7. Private headless Sway 1.12/Xwayland; udmabuf enabled;
RX_VK_FORCE_MESH_FALLBACK=1; speakers disabled.Scene: Hyrule Castle Town (
Market_Child), stationary at the copied save's wooden door. 1920×1080 drawable and output, 100% render resolution, game's own upscaler, frame generation Off, game frame-rate cap Uncapped, VSync Auto, HDR Off. Effective menu values were read through the control bridge. No overlays, injectors or external shader-cache tools.Method: One short session, main → PR → main → PR, with fresh copies of the same save. Each process warms at the stationary scene for at least 10 s, then samples for 20 s; the last five
vk: N presents, X fpslog lines per run are retained. The per-run median and median of the two run medians are below. These are native-present measurements at the settings below, not a claim about other scenes or 1% lows.How to reproduce: Load a disposable copy of a Castle Town save and stay by the same wooden door, without movement/camera input. Use the settings above, with Aspect ratio Window on this PR; baseline main has stock behavior. Alternate the two binaries twice, let each settle, and take the last five presents-log readings per run. The additional feature proof below is separate from these stationary FPS samples.
316f93191c66d19bWindow proof: both PR runs started with persisted
ultrawide=2; the bridge reported the UI's stablevalue=-1,text=Window. In the last run the same live game was resized from 1920×1080 to 2448×1848, 2560×1080, 1280×1024, then back to 1920×1080. Inspected captures show the full window filled, wider side visibility in ultrawide, more vertical visibility in the tall/5:4 shapes, and preserved character/HUD proportions. This does not add HUD docking at the new edges.After the resizes, the bridge still reported
value=-1,text=Window. Changing Sharpness to 10 and back to 0 keptultrawide=2in the saved options file. There was no separate restart-after-that-save run; the game-free real-save regression covers that persistence path. The entire session took 389.8 s. No Monitor-setting FPS sample on the feature binary is claimed; the comparison here is stock main against the running Window mode. Windows and #95's loading-fade integration remain requested, not tested.Exact presents-log samples (five per run)
The binaries are the previously built Linux release copies at these exact commits, with all tracked source hashes rechecked against their Git trees before this session. Native shader preparation ran for each copy before measurement. No newer or unbuilt binary is substituted.
Current-main merge: current head
1adca03782bdb6ae7a526fa184bc44800f1b2e02merges the Longshot main snapshot fetched for this run,dd02c6cca9582f5bb1b992d5c43268bf5d846c3f, into the existing PR branch with a merge commit and a normal push; no history rewrite. The earlier measurements and screenshots remain evidence only at their named commits, not a new performance comparison on this main. The merge was rebuilt and smoke-tested as described below. Main advanced tofdc2840bduring validation; a three-way merge check against that newer head is clean, but it is not included in the tested binary.Testing
Current merge validation (
1adca03782bd, 2026-11-13): README keeps main’s current rows with the Window choice; test_output_resolution.py retains main’s new default-output cases and the Window/save cases. Focused feature/option-ID/README/menu checks: 11 passed, 0 failed. Full suite on zao, with/tmp/longshot-carry/env.shand the shared suite lock: 506 passed, 3 failed, 16 skipped in 508 s (linux, 8 at once). All three failures reproduce on untouched maindd02c6cc:test_display_pointer.py(missingwin::rx_optionfixture definition),test_texture_staging_publication.py(missinglog_debugfixture declaration), and the known Nix-pathtest_linux_tools.pyfailure. No new failures.git diff --check dd02c6ccpasses for the feature diff; main itself contains an unrelated font-license trailing space. No standalone formatter/typechecker is configured; the suite compiles native fixtures and byte-compiles tracked Python. The pinned source was hash-audited before and after a successful Linux build on aka. The new build loaded a copied Castle Town save (Market_Child, stateplay) in a private headless Sway/Xwayland session on the RX 7900 XT/RADV; its runtime version and live options were read through the control bridge. Audio was disabled, the GPU lock held throughout, and both private units stopped. No owner game, live save orkorri*unit was touched. This was a short startup/gameplay smoke check, not a repeat of the earlier feature/performance measurements. Windows and other GPUs were not retested. The older test counts below describe the historical measured/follow-up heads, not this merge.Tested: fresh Linux main/PR gameplay and feature proof above. All screenshots linked here were opened and checked. Only isolated copies and copied test saves were used; no installed game, live save, owner game session or
korri*unit was changed. No GPU hang/device-lost/crash marker appeared in the completed measured runs.Focused checks after the clean local rebase:
11 passed, 0 failed, 0 skipped in 1 s (linux, 8 at once). These include the feature test, tests touched by the change andtests/test_option_ids.py; the command and individual results are retained locally.git diff --checkpasses. There is no standalone formatter/typechecker in this project. Native fixtures compile through the test runner. Author and both contiguous AI trailers are preserved.The earlier complete Linux suite at the measured commits is recorded in this PR's history. Its unresolved
test_linux_tools.pyfailure also occurs on untouched main: that fixture fixes PATH to/usr/bin:/bin, which omits Nix's readlink/dirname. The separate earlier OpenSSL failure was resolved by adding OpenSSL to PATH; all 18 release tests then passed unchanged on main and each feature. The full suite was not repeated for this documentation/measurement pass; only the focused checks above were rerun.Not tested: Windows native builds/gameplay and non-ASCII Windows paths, physical controllers, other GPU families, HDR, full playthroughs and every feature combination. No Windows machine is available. Any combination specifically requested below remains unverified; a request is not a passing result.
Needs other testers:
These requests are open; if the service marks them waiting for a maintainer, they need the
testing wantedlabel before testers can claim them.Screenshots
Risk and rollback
Tall windows render more pixels at startup and can run slower. Projection or UI cases not visited may render incorrectly. Select a monitor/explicit aspect and restart, or revert. Longshot #95 changes related HUD projection sites; combining it needs a separate integration check.
Reviewer notes
One feature only. The manual merge resolutions change documentation and retain both sets of output-resolution tests. Linux evidence does not certify Windows; that gap is assigned to open test requests rather than marked passed. No public MkwVkHost/MkwVkRenderer structure changed, so no ABI-version bump is needed. The shared tested-configurations file is unchanged; the measured runs are folded below. Authored glslang SPIR-V headers, where present, are source artifacts, not game-dump output.
Checklist
For everyone
type(scope): description).main. Merged the main snapshot fetched for this run,dd02c6cca958as1adca03782bd, as requested by the author; latest mainfdc2840bstill merges cleanly. No rebase or force-push.python -I -B tests/run_all.pyrun and passing (any failure that also fails onmainis named above).third_party/untouched (a new folder with aPROVENANCE.mdinstead).If applicable
Assisted-by:/Co-Authored-By:trailers.tests/that reads changed source updated in this PR, not weakened.Tested configurations: fresh Longshot runs, 2026-11-13 UTC
316f93191c66d19b316f93191c66d19bAdditional same-session feature/off-setting observations and their proof are described above.
d4nkn3ss referenced this pull request2026-11-13 06:34:02 +00:00
Added a fresh interleaved main/PR Linux measurement session at the named commits, the last-five-sample FPS medians, feature proof and inspected screenshots. Windows testing requests are open; the description names the remaining gaps. The branch also rebases cleanly onto main
1be180bbwith an identical stable patch ID and passing focused tests; the force-with-lease push is still gated, so the measured remote head stays pinned. No new baseline FPS is claimed for the local rebase. The four drawable shapes and live resize were checked; #95 loading-fade integration has its own open request.WIP: feat(video): a Window choice that fills the actual window, Hor+ and Vert+to feat(video): a Window choice that fills the actual window, Hor+ and Vert+Merged main snapshot
dd02c6cca9with a merge commit and normal push; resolved README using main’s current rows plus Window, and retained both main’s new default-output cases and our Window/save cases in test_output_resolution.py. Eleven focused checks pass; full suite: 506 passed, 3 failures reproduced on untouched main, 16 skipped; Linux build and isolated Castle Town smoke pass, and newer mainfdc2840bstill merges cleanly (details in Testing).Thanks. This is careful work. The Hor+/Vert+ projection is correct: the tangent is multiplied by 16:9 over the window's shape only when the window is narrower, so the horizontal view stays the 16:9 one. Keeping each projection's original tangent stops the FOV growing on repeated updates, and that also holds with the FOV slider's hook (the new block in
tests/test_oot_fov.py). A minimised window (0x0) is ignored, both inrx_resizeand inhos_wide_resize. A live resize changes no render target, so no upscaler history or guest heap has to be rebuilt. The swapchain is still remade the way main does it (swap_resizefolds a burst of resizes into one remake at the next present). The default doesn't change:ultrawide=0/1and a missing line read and save as before, andhos_wide_windowis set only byultrawide=2.HOS_ASPECTstill overrides. NoMkwVkHost/MkwVkRendererchange,third_party/untouched, no generated files.I read the code only. I didn't build it or run any checks.
Must change
It doesn't merge with current main. Main is now at
fbd42024.README.mdconflicts with the bullet that #55 added in the same place (f647a072, "Changing the Upscaler keeps your chosen render scale..."). Keep both bullets. Every other file merges cleanly (I checked with a three-way merge). The "Rebased on currentmain" box is false until then.After a live resize, movies, the menus' sides and the save picture still use the startup shape.
hos_wide_resizeupdates only the camera/HUD aspect.hos_wide_marginis set only inhos_wide_start, and two things still read it:host_wide_menus→vk_framegen.cpp wide_sides(), which places the blurred or black sides of the pause and map menus.The THUMB site likewise reads the startup
hos_wide_ratio. Two examples:Recompute
hos_wide_squeeze/hos_wide_marginfrom the live aspect inhos_wide_resize. Give THUMB a live ratio of its own.hos_wide_ratiohas to stay at the startup value, because the DLSS jitter (rx_backend.c dlss_jitter_clip) needs the shape the render targets actually have. These are the paths the description lists as untested; "Follows window resizes" isn't true for them yet.The checklist doesn't match the template.
docs/tested-configurations.md" was changed to "the folded table below". Add the rows to the file, as AGENTS.md asks.The screenshots return 404.
git.magicbean.dev/simonwjackson/Longshot/raw/commit/02f8cb12.../*.pngdoesn't resolve: the fork or that commit isn't reachable. Attach them to the PR so a reviewer can see Hor+/Vert+ and the centred HUD. If you can, add a main @ hash shot of the same spot at one of the resized sizes.Should change
hos_wide_picture_sizetakes any aspect: main'shos_wide_clamp(5:4 to 48:9) isn't applied. Its pixel cap can also break the "do not shrink below 1920x1080" comment. Below about 1:3, the width drops under 1920 (540x2160 → 1656x6648). Above about 10:1, the height drops under 1080, and from about 28:1 the width goes past 16384. You can only get there at startup withHOS_WINDOW_SIZEor full screen.HOS_WINDOW_SIZEis set. So a player who picks Window and maximises on a 21:9 screen gets a 2560x1440 picture stretched across 3440, which is softer than Monitor's shape. Say so in the hint and the README ("the picture's size is set by the window's shape at start"), or open the window at the size it last had.hos_sdl_pixel_sizeright afterset_fullscreenalready the full-screen size? On X11/Wayland, going full screen can be asynchronous. I haven't checked this.window_fit_active()callsgetenv("HOS_ASPECT")on every draw and every 0x90 uniform block in Window mode. Read it once.hos_wide_projectiontable.memmove.valid. In a window whose aspect equals the storedpatched_aspect(for example a square window), a new square shadow view at that address is then taken for a patched camera.HOS_HEADLESS: the picture keeps the monitor's shape fromhos_main.c, buthos_wide_aspect()is 0, so the cameras stay at 16:9 and the picture is stretched. The live_ratiodoesn't fall back to_ratio_fixed. This is a minor dev-only case.The other layout work
#95 rewrites the same
HUDstring innx2/oot_wide.py, so they will conflict as text, and whichever lands second has to port. As written, Window mode replaces #95's HUD outright:hos_ui_qx/qyand edge-anchored panes don't apply in Window mode;hos_wide_ui_tickreads the startuphos_wide_squeeze/hos_wide_ratio, which a live resize doesn't change.Please agree with #95's author which one owns the HUD projection. I'd suggest #95's layout taking
hos_wide_aspect()as its shape, with the draw-time refit (oot_ui_squeeze.h) staying as the live correction.#57 only changes which keep-mask bits the menus clear. Window's HUD branch still honours
hos_wide_keep_mask, so the two are compatible. Its fades still depend on the side margin from point 2 above.#199 and this PR edit different parts of
tests/test_oot_fov.py.The Output resolution row: in Window mode only the chosen size's height is used, and its shape is ignored. That matches
picture_for; the hint could say so.Nits
hos_display_startnow waits for the window for everyone, one interleaved pair on the default would settle it.aspect_startedstarts at -1, which is now Window's value untiloutputs_syncruns.@ -497,6 +497,12 @@ HDR, Detail, and GPU and memory (last: the rows to change when something is wronand **Sharpness** sharpens it, by the picture's pixels: when the window is much larger than the picture (the HandheldProfile's 1536x864 on a 4K screen), a Sharpness kept from an older version is stronger than it was, so a lower one maysuit.- **Window aspect ratio** (opt-in): fills the actual window. Wider than 16:9 shows more at the sides (Hor+);This conflicts with current main (
fbd42024): #55 added the 'Changing the Upscaler keeps your chosen render scale' bullet in the same place. Keep both.@ -3989,6 +4006,8 @@ void draw_startup(ui::Ctx& c) {} // namespacebool window_fit_active() { return ultrawide_read == 2 && !getenv("HOS_ASPECT"); }This is called per draw (
draw()) and per 0x90 uniform block (uniform_range) in Window mode, so it callsgetenvon the hot path. CacheHOS_ASPECTonce.@ -0,0 +16,4 @@float hos_wide_aspect(void) { return hos_wide_window ? atomic_load_explicit(&window_aspect, memory_order_relaxed) : hos_aspect_ratio; }void hos_wide_resize(unsigned width, unsigned height) {Only the camera/HUD aspect follows a resize here.
hos_wide_margin/hos_wide_squeeze, set once inhos_wide_start, still drive the MOVIE site and the menus' blurred/black sides (host_wide_menus->vk_framegen.cpp wide_sides), and THUMB reads the startuphos_wide_ratio. After a resize, movies and menu sides keep the startup shape. Recompute squeeze/margin here from the live aspect, and give THUMB its own live ratio. Keephos_wide_ratioas it is for the DLSS jitter, since the targets keep their startup size. The live aspect is also unclamped: a 1600x40 or 40x1600 window gives a view of about 175 degrees.@ -0,0 +21,4 @@if (aspect < 16.0 / 9.0) oh = (unsigned)(ow / aspect / 8 + 0.5) * 8;else ow = (unsigned)(oh * aspect / 8 + 0.5) * 8;/* Upstream's render-target pool guard (issue 1031): cap pixels, not shape. */if ((double)ow * oh > hos_wide_max_px()) {There's no
hos_wide_clamphere, unlike main's path, and this cap can break the comment above. Below about 1:3 the width drops under 1920 (540x2160 -> 1656x6648). Above about 10:1 the height drops under 1080, and from about 28:1 the width passes 16384. Clamp the aspect first (main's 5:4 to 48:9, or a portrait floor you've checked).@ -300,1 +312,4 @@if (getenv("HOS_FULLSCREEN") && atoi(getenv("HOS_FULLSCREEN"))) set_fullscreen(1);int pixel_w = 0, pixel_h = 0;hos_sdl_pixel_size(window, &pixel_w, &pixel_h);hos_wide_start(pixel_w, pixel_h);Is the pixel size already the full-screen size right after
set_fullscreen(1)? On X11/Wayland the switch can be asynchronous, and the picture is sized only from this value. Otherwise, a windowed start opens at 1280x720 in Window mode, so the picture is nearly always 16:9 and a maximised ultrawide window stretches 2560 pixels across 3440. Worth saying in the hint/README.@ -51,1 +87,4 @@HUD = "{ extern int hos_wide_window; if (hos_wide_window) " + HUD + " else " + HUD_FIXED + " }"MOVIE = ("{ extern volatile float hos_wide_margin; const float _m = hos_wide_margin; if (_m > 0.0f) { "MOVIE still reads
hos_wide_margin, which is only set at startup in Window mode, so movies leave the middle 16:9 after a live resize (see host/hos_wide.c).@ -48,6 +72,21 @@ HUD = ("{ extern volatile float hos_wide_squeeze; const float _q = hos_wide_sque"memcpy(c->vreg[1], _row, 16); } } } } /* (nx2/oot_wide.py: HUD in the middle 16:9) */"% (UI_M00, UI_M11, " : ".join("_vt == 0x%xULL ? %du" % (v, 1 << k) for k, v in enumerate(KEEP_VTABLES)) + " : 0u"))HUD = ("{ extern float hos_wide_aspect(void); const float _w = hos_wide_aspect(); if (_w > 0.0f) { float _m00, _m11; "#95 rewrites this same HUD string (
hos_ui_qx/qy, edge-anchored panes). In Window mode this branch replaces it outright, and #95'shos_wide_ui_tickreads the startup squeeze/ratio. Please agree with #95 which one owns the HUD projection before either lands.View command line instructions
Manual merge helper
Use this merge commit message when completing the merge manually.
Checkout
From your project repository, check out a new branch and test the changes.