fix(linux): the log says why the renderer did not load #34
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!34
Loading…
Reference in a new issue
No description provided.
Delete branch "dyst/Longshot:fix/renderer-load-error"
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
When the Linux renderer fails to load, the log says only
(2), for a missing file and alongshot_vk.sofrom another build alike. The second needs explaining: it takes symbols from the executable, so it fails atdlopen, beforemkwvk_init's version check, and the game quietly falls back to WebGPU.What changed
host:
LoadLibraryAinhost/posix/win32_posix.ckeepsdlerror()'s text for the last failure, per thread (load_error, cleared at the start of each call), andhos_load_error()returns it (""after a success).GetLastError()is stillERROR_FILE_NOT_FOUND, as before.host/posix/windows.hdeclareshos_load_error.host:
load_vulkaninhost/rx_backend.cprints that text in its failure line on Linux:Windows keeps the error number, which already tells a missing file (126) from a missing function (127).
tests:
tests/test_renderer_load_error.py(new): everywhere, the source keeps the text and logs it (it fails onmain); on Linux, it buildswin32_posix.cas it is and loads, throughLoadLibraryA, a.dllname with no library behind it, a library that needs a symbol nothing has, then a good one.Flags and settings
None.
Measurements
Nothing to measure in the game: the change is the failure path's log line on Linux. What was run, on the GitHub version (https://github.com/NotRexed/Hookshot/pull/2875):
The new check's Linux part, by hand on WSL Ubuntu (gcc), as the check does it:
Every check that reads the changed files (37), through
run_all.py, rebased on GitHub'smainof that day, on Windows: 34 pass;nx2/test_bc_storage_views.py,nx2/test_dll.pyandnx2/test_leaf.pyfail the same way onmainthere. The new check's Linux part is skipped on Windows.Testing
Tested: the runs under Measurements (on the GitHub version) and the game-free checks this description names. Checked but not measured: the Linux part of the new check, on WSL Ubuntu (gcc).
Not tested: the game on Linux. The loader message runs in the game, and was exercised outside it (WSL) only.
Needs other testers:
Screenshots
Nothing on screen.
Risk and rollback
Only the Linux failure path's log line changes: a successful load and Windows are as before, and
GetLastError()still saysERROR_FILE_NOT_FOUND. The text is cut to 512 bytes in a per-thread buffer. Reverting the commit is enough.Reviewer notes
Carried over from https://github.com/NotRexed/Hookshot/pull/2875 with the move to Longshot: the same change on Longshot's
main, with its rename applied. Its review and discussion are there.A draft: its Linux loader message runs in the game, and was exercised outside it (WSL) only.
The checks were done on the GitHub version; this branch is the same change with the rename applied (the same files and line counts). Forgejo runs no checks; they were not run again here. Only the checks that read the changed files were run, not the whole suite. Part of the dev-loop proposal's B1 (https://github.com/NotRexed/Hookshot/pull/2636): a renderer swap during development needs this line to say why the new one was refused.
Issues
None on Longshot. See https://github.com/NotRexed/Hookshot/pull/2636 (B1).
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
Back to WIP under #48's process: this was carried over before the new template. Its description will be brought to the template and measured on Longshot (main against this PR, at named commits) when it's wanted for review; ask here if you'd like it sooner.
Description brought to Longshot's PR template (#48). Still WIP: its numbers are from the GitHub version, and it isn't rebased on current main; it gets measured on Longshot (main against this PR, at named commits) before it is marked ready.
Description moved to the template's Testing section and whole checklist (#72): what was tested, what wasn't, and what needs other testers (their test requests to open while this is WIP).
The description follows the template after #76 and #106: If applicable keeps only the boxes that apply (one that applies and isn't done yet stays unticked while this is WIP), the Flags table has the template's seven columns (Goal is now Recommendation), and GitHub references are full URLs. Nothing else changed.
Read-only review against current
main(fbd42024). I built nothing and ran no checks.What's good. The dlerror() handling is right. It is called once, straight after the failing
dlopen, checked for NULL, and copied into a per-thread buffer. glibc keeps dlerror's state per thread, andlast_errorbeside it is__threadtoo, so adlopenon another thread can't change whatload_vulkanreads. The buffer is cleared before the earlyif (!name)return, so a reason from an earlier call can't stay behind. The line is written before the fallback, and the game keeps running into a black window afterwards (linux_stubs.cwgr_start), so the line reacheslongshot_log.txt. Every failure insidedlopengets its reason in the log: the file missing, a wrong ELF class,GLIBC_2.xx not found, an undefined symbol under RTLD_NOW, and "cannot allocate memory in static TLS block". That last one matters once #232 lands. Windows is unchanged: its#ifdef _WIN32branch is the old line word for word, and it keeps GetLastError's number (126/127), with no FormatMessage. The PR still applies to main.win32_posix.candwindows.hhaven't changed since the fork, and main's only change torx_backend.c(the 256-markcollect_in) is far fromload_vulkan. No other check cutsLoadLibraryAorload_vulkan, so the new test is the only one that reads these lines. The unticked boxes (rebased, run_all, both builds, measured) are honestly unticked.Must change
#2636. On Forgejo that links to Longshot's own #2636. Writehttps://github.com/NotRexed/Hookshot/pull/2636(AGENTS.md).Should change
The reason given for the PR doesn't match main's load path. The description and the test's docstring say
longshot_vk.sotakes symbols from the executable, so one from another build fails atdlopenwithundefined symbol: mkwvk_..., before the version check. On main I can't find any such link-time import:MkwVkHosttable (vk_host) and throughdlsym(RTLD_DEFAULT, "hos_posix_*").hos_posix_*(--export-dynamic-symbol)..sohas its own copy ofwin32_posix.cand links-Bsymbolic.hos_*it calls ingpu/vkarestatic inline(hos_wide_size.h).mkwvk_*are the.so's own exports, not imports.So a renderer from another build most likely loads, then fails
MKWVK_API_VERSION, and that failure is already logged. I had no built.soto check withnm. Please check one, then restate the cases this actually helps: a missing file, a too-old glibc (the AppImage on an older distro), a wrong ELF class, static TLS. The change is still worth having for those.A missing libvulkan.so.1 still logs no reason. The renderer loads the Vulkan loader itself (
gpu/vk/vk_main.cppload_global,LoadLibraryA("vulkan-1.dll")). That goes through the renderer's own copy ofwin32_posix.c, so after this PR the reason is in that copy'shos_load_error(). Butvk_main.cpp:1594logs onlyvk: load_global failed (...), and the next line ismkwvk_init returned NULL. The PR'sdlerror()call also uses up glibc's message, so the renderer can't calldlerror()itself afterwards. Addinghos_load_error()to that log line on Linux would cover this case, likely the most common one on a system without Vulkan. NVIDIA's DLSS runtime and FSR 4 are loaded later.vk_fsr4.cpp:928already logs dlerror, and DLSS is loaded by the NGX library linked into the renderer, not by this path.The new Linux-only line says ": WebGPU", but Linux has no WebGPU. The next lines are "no WebGPU renderer in the Linux build: nothing is drawn" and "the window stays black". This line is new and Linux-only, so it can say what happens there instead, for example
...: nothing is drawn. The description's "quietly falls back to WebGPU" is wrong on Linux for the same reason.Privacy and repeated text. glibc formats the message as
<file>: <reason>, so the line prints the full path twice:... but /home/<name>/.../longshot_vk.so did not load (/home/<name>/.../longshot_vk.so: cannot open ...). The host log redacts nothing. The only redaction is the Bug report's (gpu/vk/vk_bug_report.cppredact), and that lives in the renderer's menu, which isn't there when the renderer failed to load. These players attach the raw log. The path was already in this line before the PR, so this isn't a new leak. Still, dropping the leading<path>:fromload_errorwhen it matchespathwould halve the home-folder copies and make the line shorter.Conflict with #183. That PR inserts
dev_dll_swap(path);between thememcpyand theLoadLibraryAinload_vulkan, inside this PR's hunk context. Whichever lands second has a small rebase. The two fit together: after a swap, this line is what says why a staged renderer was refused.Nit: the test compiles with
--cc gccbut doesn't declareRUN_ALL_ARGS = ["--cc", "gcc"]. Several C checks do (test_audio_biquad.py,test_camera_speed.py), and with it run_all skips the test instead of failing on a Linux box with only clang. The 512 bytes go into the renderer's TLS segment too (its copy ofwin32_posix.c). That is negligible against the room #232 reserves, but worth knowing.@ -776,0 +777,4 @@if (!m) {last_error = ERROR_FILE_NOT_FOUND;const char *why = dlerror();snprintf(load_error, sizeof load_error, "%s", why ? why : "");Good: called once, straight after
dlopen, NULL-checked, and copied per thread. The renderer's own copy of this file now keeps the reason too.gpu/vk/vk_main.cppload_globalcould loghos_load_error()whenvulkan-1.dll(libvulkan.so.1) doesn't load, since this call has already used updlerror()for it. Today that case logs onlyvk: load_global failed.@ -278,1 +278,4 @@#ifdef _WIN32hos_log("rx: RX_BACKEND=vulkan, but %s did not load (%lu): WebGPU", path, GetLastError());#else /* (why: a missing file, or a symbol it needs that this executable lacks, as one from another build) */hos_log("rx: RX_BACKEND=vulkan, but %s did not load (%s): WebGPU", path, hos_load_error());This line is Linux-only, and Linux has no WebGPU: the next lines say "nothing is drawn" and "the window stays black". Could it end with what actually happens, e.g.
: nothing is drawn? Also,hos_load_error()begins with the samepathalready printed before it (glibc writes<file>: <reason>), so the home folder appears twice in a log that players attach raw. The Bug report's redaction lives in the renderer's menu, which isn't there in this case. Dropping that leading<path>:would help.@ -0,0 +1,100 @@"""Why the Linux renderer did not load is in the log (host/posix/win32_posix.c hos_load_error, host/rx_backend.c load_vulkan).longshot_vk.so is opened with RTLD_NOW and takes symbols from the executable, so one from another build fails at dlopen,before mkwvk_init's version check, and the game falls back to WebGPU. The POSIX LoadLibraryA kept onlyOn main, does
longshot_vk.soimport any symbol from the executable at link time? I can't find one. The host is reached through theMkwVkHosttable anddlsym(RTLD_DEFAULT, "hos_posix_*"), the exe exports onlyhos_posix_*, and the.sohas its ownwin32_posix.cwith-Bsymbolic. A renderer from another build would then load and fail theMKWVK_API_VERSIONcheck, which is already logged. Please check withnm -D --undefined-onlyon a built.soand restate the cases this catches: a missing file, GLIBC_2.xx not found, a wrong ELF class, the static TLS block.@ -0,0 +3,4 @@longshot_vk.so is opened with RTLD_NOW and takes symbols from the executable, so one from another build fails at dlopen,before mkwvk_init's version check, and the game falls back to WebGPU. The POSIX LoadLibraryA kept onlyERROR_FILE_NOT_FOUND, so the log said "(2)" for that and for a missing file alike. It now keeps dlerror()'s text, whichnames the file or the symbol, and load_vulkan prints it (the dev-loop proposal's B1, #2636).#2636is a GitHub PR. On Forgejo, a bare#2636links to Longshot's own number. Please use the full URL: https://github.com/NotRexed/Hookshot/pull/26363ec509630deb7602580bI rebased this onto main @
fbd42024(no conflicts) and addressed my review in one commit of mine on top of yours. It's pushed aseb760258.My commit:
#2636; I left it, as it's yours.mkwvk_initappendshos_load_error(), the reason the renderer's ownLoadLibraryAkept, to its line. For example:vk: load_global failed (Vulkan loader library or vkCreateInstance missing): libvulkan.so.1: cannot open shared object file: No such file or directory. It reads no seconddlerror(). Windows is unchanged.load_vulkanline now ends: nothing is drawn(wgr_startis a stub there). Windows keeps: WebGPU.LoadLibraryAactually opened (longshot_vk.so, not.dll). It drops dlerror's leading"<that path>: "when the text starts with exactly that, for examplerx: RX_BACKEND=vulkan, but /…/longshot_vk.so did not load (cannot open shared object file: No such file or directory): nothing is drawn. A message about another file, such as glibc'sversion 'GLIBC_2.38' not found (required by …), is printed whole.RUN_ALL_ARGS = ["--cc", "gcc"]is added like the other C checks. The test's source checks now cover 2–4, and the docstring lists the cases dlopen really fails for.No tests or builds were run here.
Please update the description:
undefined symbol: mkwvk_...") likely can't happen on main. The renderer reaches the host throughMkwVkHostanddlsym(RTLD_DEFAULT, "hos_posix_*"), so a mismatched renderer loads and then fails theMKWVK_API_VERSIONcheck, which is already logged.nm -D --undefined-onlyon a built.sowould settle it. Please restate the cases this catches: a missing file, an old glibc, a wrong ELF class, the static TLS block, and now a missing libvulkan.so.1.It will conflict a little with #183, which inserts
dev_dll_swap(path)in this hunk, whichever lands second.WIP: fix(linux): the log says why the renderer did not loadto fix(linux): the log says why the renderer did not loadRe-review after the merge. This PR landed on main as
5b81a8a1, squashed fromeb760258(the rebase ontofbd42024plus my commit on top). I compared the squash witheb760258for the PR's five files, and they're identical. I read the code only and ran nothing.From my last review, all fixed in the code that merged:
#2636. The test's docstring now gives the full URL. dyst's commit message still says#2636, but the squash replaced it.mkwvk_initappends the renderer's ownhos_load_error()toload_global's failure line and calls no seconddlerror(). The buffer is__threadin the renderer's copy ofwin32_posix.cand is cleared at eachLoadLibraryA, so a reason from an earlier load can't stay behind. When the loader did load andvkCreateInstanceis what's missing, the reason is empty and the line is unchanged.: nothing is drawn. Windows keeps its line word for word.longshot_vk.so, and it drops dlerror's leading<path>:only when that is exactly what the text starts with. LoadLibraryA's.dll→.sorewrite and the\→/change both leave the pathGetModuleFileNameAgave unchanged on Linux. So the prefix matches dlopen's argument, and a message about another file (glibc'sGLIBC_2.xx not found (required by …)) stays whole.RUN_ALL_ARGS = ["--cc", "gcc"]is added, and the source checks cover the new lines.Left over now that it's merged (no code change needed):
5b81a8a1took the PR description as it was. It still gives "another build fails at dlopen withundefined symbol: mkwvk_..." as the reason, "quietly falls back to WebGPU", and the old example line with the path twice and: WebGPU. Main's history can't be edited now. @dyst, please correct the description here for anyone who finds the PR from the commit: the cases are a missing file, a glibc too old, a wrong ELF class, the static TLS block and a missing libvulkan.so.1, and Linux draws nothing.load_vulkan(dev_dll_swap(path)) now falls to it. The two fit together.