feat(runtime): HOS_DEV_DLL_SWAP, a renderer built alone taken at the next start, checked against the game first #183
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 milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Longshot!183
Loading…
Reference in a new issue
No description provided.
Delete branch "dyst/Longshot:feat/renderer-swap"
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
Trying a change to the renderer means quitting the game, building the whole game folder again and starting it, or copying the DLL in by hand with nothing to say whether it pairs with the executable. On Linux the renderer reaches the host through weak
hos_*symbols, so one from another tree fails atdlopen, or calls through NULL. This builds the renderer alone while the game keeps running, checks it against the game, and has the game take it at its next start.What changed
dev_dll_swapinhost/rx_backend.c, called byload_vulkanbeforeLoadLibraryA. WithHOS_DEV_DLL_SWAP=1, a<renderer>.newbeside the game (longshot_vk.dll.new,longshot_vk.so.newon Linux, the nameLoadLibraryA's shim opens) is renamed over the renderer first, logged either way. The old process has ended by then (a restart waits for it:HOS_RESTART_OF), so nothing holds the old file. Off, nothing is looked for: a release never swaps.scripts/stage_renderer.py GAME_DIR [RENDERER](new, standard library) copies the renderer (default: the work folder'ssa/build/one) whole to<name>.newbeside the executable (bin/when the game has one), only when it pairs. Linux: every name it leaves for the loader (nm -D --undefined-only), weak ones too, unless versioned (the C and C++ runtime's) or the start files' own (__gmon_start__,_ITM_*...), is one the executable exports. Windows: every function it imports from the executable is one the executable exports (llvm-objdump -p); today the renderer imports none (the host hands it a table). One that doesn't pair isn't staged, and the names are said.tests/test_stage_renderer.py(new): the readers onnmandllvm-objdumpoutput, the staging, a real Linux executable and shared object paired, anddev_dll_swapcut out ofhost/rx_backend.cand run with the switch on and off, a renderer staged and not: on Windows with the real Win32 calls, on Linux overhost/posix/win32_posix.c, where it swaps the.so.Flags and settings
HOS_DEV_DLL_SWAP<renderer>.newbeside the game takes the renderer's place before it is loadedMeasurements
main:
f41db065(2026-11-13)this PR: feat/renderer-swap @ 2a333461
Hardware: RTX 3080 (NVIDIA 621.14), Ryzen 9 3900X, 64 GB, Windows 11
Scene: Kokiri Forest from a save (the title's Continue), headless, muted; both builds from one work folder
Method: from a comment added to
gpu/vk/vk_menu.cppwhile the game is in play to being in play again on the new renderer, by the script's clock, rounds interleaved (main, PR, main, PR...). main: quit,make_game.py(the game folder built again; the lift skipped), launch, Continue. This PR:RX_VK_ONLY=1 nx2/build.py --standalone(the renderer alone) while the game plays on,scripts/stage_renderer.py,longshot_ctl.py relaunch(the game started withHOS_DEV_DLL_SWAP=1), Continue. The PR side was this commit on feat/relaunch @ 0fa72f49 (#97; the same patch), since a staged renderer is taken at the next start.How to reproduce: as the method, with a save copied into each game folder.
f41db065The medians are of rounds 2 to 4. Round 1 is left out on both sides: main's build ran the lift again, because the PR's first build from the other checkout had changed the shared work folder's lift key, and the PR's was its first
RX_VK_ONLYbuild there, which compiles every renderer file once. In a warm work folder building the renderer alone is no faster than the game folder's build (everything else is cached); the ~22 s a loop saved is the game left running through the build and relaunched over the bridge, against quit, launch and wait. Rebased since onto mainc67cd4a0: of its commits, one toucheshost/rx_backend.c(#63, the write marks' collection), not the loader this changes, and none touch the build's renderer path, so the numbers hold.Testing
Tested: on the Windows PC above: the loop in the game (four rounds, the log saying the staged renderer took the old one's place, play in Kokiri Forest, no errors in the logs);
tests/run_all.py, the whole suite, on this branch and on mainc67cd4a0(496 passed and 5 failed on main, 497 passed and 5 failed here, no failure main doesn't have: test_builder_dlss_update, test_check_third_party, test_fpx_sticky, test_release and test_spawn_emit fail on main too);tests/test_stage_renderer.pyon Windows and on Linux (Docker, python:3.12 with gcc and binutils: the readers, the staging, a real executable and shared object paired, anddev_dll_swaprun over the POSIX shim, swapping the.so; a copy with the.dll-to-.sorename removed fails it), three times on this branch's head.Not tested: the swap in the game on Linux (its code ran there over the shim, not in a game build); a game folder with a launcher (
bin/), where the script stages besidebin/longshot.Needs other testers:
HOS_DEV_DLL_SWAP=1, a renderer staged withscripts/stage_renderer.py, thenlongshot_ctl.py relaunch, expecting "HOS_DEV_DLL_SWAP: ... took the old renderer's place" in the log and the game playing (T-PZ2F)Screenshots
Nothing on screen.
Risk and rollback
With the switch set and a stale or wrong
.newbeside the game, that renderer loads in place of the old one: the script refuses one that doesn't pair, but a file copied by hand isn't checked. Without the switch nothing changes: no file is looked for. UnsetHOS_DEV_DLL_SWAP(or delete the.new) to turn it off; reverting the two commits removes it.Reviewer notes
The switch reference (docs/development.md, the README) isn't updated here: this PR carries code and tests, and the flag is noted under Flags and settings.
The dev-loop proposal's B1, its second part (https://github.com/NotRexed/Hookshot/pull/2636). It is independent of the relaunch PR (#97) in code, and measured with it, since a staged renderer is taken at the next start.
build.py'scheck_vk_symbolschecks the renderer alone at build time; this pairs it with the executable it will run under.Issues
See https://github.com/NotRexed/Hookshot/pull/2636
Checklist
For everyone
type(scope): description).main.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.mainand passing here.🤖 Generated with Claude Code
Test request T-PZ2F opened for the Linux run (it needs the 'testing wanted' label to be claimed).
WIP: feat(runtime): HOS_DEV_DLL_SWAP, a renderer built alone taken at the next start, checked against the game firstto feat(runtime): HOS_DEV_DLL_SWAP, a renderer built alone taken at the next start, checked against the game firstView command line instructions
Checkout
From your project repository, check out a new branch and test the changes.