fix(linux): the log says why the renderer did not load #34

Merged
HumanBean merged 2 commits from dyst/Longshot:fix/renderer-load-error into main 2026-11-14 00:46:29 +00:00
Member

Why

When the Linux renderer fails to load, the log says only (2), for a missing file and a longshot_vk.so from another build alike. The second needs explaining: it takes symbols from the executable, so it fails at dlopen, before mkwvk_init's version check, and the game quietly falls back to WebGPU.

What changed

  • host: LoadLibraryA in host/posix/win32_posix.c keeps dlerror()'s text for the last failure, per thread (load_error, cleared at the start of each call), and hos_load_error() returns it ("" after a success). GetLastError() is still ERROR_FILE_NOT_FOUND, as before. host/posix/windows.h declares hos_load_error.

  • host: load_vulkan in host/rx_backend.c prints that text in its failure line on Linux:

    rx: RX_BACKEND=vulkan, but .../longshot_vk.so did not load (.../longshot_vk.so: undefined symbol: mkwvk_...): WebGPU
    

    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 on main); on Linux, it builds win32_posix.c as it is and loads, through LoadLibraryA, a .dll name 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:

    failed|2|<tmp>/absent_vk.so: cannot open shared object file: No such file or directory
    failed|2|<tmp>/needs.so: undefined symbol: mkwvk_from_another_build
    loaded|0|
    
  • Every check that reads the changed files (37), through run_all.py, rebased on GitHub's main of that day, on Windows: 34 pass; nx2/test_bc_storage_views.py, nx2/test_dll.py and nx2/test_leaf.py fail the same way on main there. 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:

  • Linux: a renderer that fails to load (a missing symbol), expecting longshot_log.txt to name the reason (request to open)

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 says ERROR_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

  • One topic.
  • The title is a Conventional Commit (type(scope): description).
  • Rebased on current main.
  • python -I -B tests/run_all.py run and passing (any failure that also fails on main is named above).
  • Tested in the game, or what wasn't is named under Testing.
  • Testing filled in: what was tested, what wasn't, and what needs other testers (with test requests).
  • Measured: main and this PR at named commits, on the same PC, scene and settings, at least 2 interleaved runs each, the median reported.
  • No game files, dumps, build output or generated files.
  • third_party/ untouched (a new folder with a PROVENANCE.md instead).
  • The license is OK: GPL-3.0-or-later, nothing incompatible and nothing decompiled.

If applicable

  • AI assistance recorded with Assisted-by: / Co-Authored-By: trailers.
  • A game-free test added for the fix, failing on main and passing here.
  • Host code builds on both Windows (llvm-mingw) and Linux, with UTF-8 paths.

🤖 Generated with Claude Code

## Why When the Linux renderer fails to load, the log says only `(2)`, for a missing file and a `longshot_vk.so` from another build alike. The second needs explaining: it takes symbols from the executable, so it fails at `dlopen`, before `mkwvk_init`'s version check, and the game quietly falls back to WebGPU. ## What changed - host: `LoadLibraryA` in `host/posix/win32_posix.c` keeps `dlerror()`'s text for the last failure, per thread (`load_error`, cleared at the start of each call), and `hos_load_error()` returns it (`""` after a success). `GetLastError()` is still `ERROR_FILE_NOT_FOUND`, as before. `host/posix/windows.h` declares `hos_load_error`. - host: `load_vulkan` in `host/rx_backend.c` prints that text in its failure line on Linux: ``` rx: RX_BACKEND=vulkan, but .../longshot_vk.so did not load (.../longshot_vk.so: undefined symbol: mkwvk_...): WebGPU ``` 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 on `main`); on Linux, it builds `win32_posix.c` as it is and loads, through `LoadLibraryA`, a `.dll` name 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: ``` failed|2|<tmp>/absent_vk.so: cannot open shared object file: No such file or directory failed|2|<tmp>/needs.so: undefined symbol: mkwvk_from_another_build loaded|0| ``` - Every check that reads the changed files (37), through `run_all.py`, rebased on GitHub's `main` of that day, on Windows: 34 pass; `nx2/test_bc_storage_views.py`, `nx2/test_dll.py` and `nx2/test_leaf.py` fail the same way on `main` there. 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:** - Linux: a renderer that fails to load (a missing symbol), expecting longshot_log.txt to name the reason (request to open) ## 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 says `ERROR_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 - [x] One topic. - [x] The title is a Conventional Commit (`type(scope): description`). - [ ] Rebased on current `main`. - [ ] `python -I -B tests/run_all.py` run and passing (any failure that also fails on `main` is named above). - [x] Tested in the game, or what wasn't is named under Testing. - [x] Testing filled in: what was tested, what wasn't, and what needs other testers (with test requests). - [ ] Measured: main and this PR at named commits, on the same PC, scene and settings, at least 2 interleaved runs each, the median reported. - [x] No game files, dumps, build output or generated files. - [x] `third_party/` untouched (a new folder with a `PROVENANCE.md` instead). - [x] The license is OK: GPL-3.0-or-later, nothing incompatible and nothing decompiled. ### If applicable - [x] AI assistance recorded with `Assisted-by:` / `Co-Authored-By:` trailers. - [x] A game-free test added for the fix, failing on `main` and passing here. - [ ] Host code builds on both Windows (llvm-mingw) and Linux, with UTF-8 paths. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
fix(linux): the log says why the renderer did not load
All checks were successful
PR description / Description follows the template (pull_request_target) Successful in 4s
3ec509630d
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 only ERROR_FILE_NOT_FOUND, so the log said "(2)" for
that and for a missing file alike. It keeps dlerror()'s text
(hos_load_error), and load_vulkan prints it:

  rx: RX_BACKEND=vulkan, but .../longshot_vk.so did not load
  (.../longshot_vk.so: undefined symbol: mkwvk_...): WebGPU

tests/test_renderer_load_error.py: the source everywhere; on Linux,
the shim as it is loading a missing file, a library needing a missing
symbol and a good one. Part of the dev-loop proposal's B1 (#2636).

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Author
Member

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.

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.
Author
Member

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 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.
Author
Member

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).

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).
Author
Member

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.

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.
HumanBean added this to the v3.0.0 milestone 2026-11-13 17:53:36 +00:00
HumanBean left a comment

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, and last_error beside it is __thread too, so a dlopen on another thread can't change what load_vulkan reads. The buffer is cleared before the early if (!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.c wgr_start), so the line reaches longshot_log.txt. Every failure inside dlopen gets 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 _WIN32 branch 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.c and windows.h haven't changed since the fork, and main's only change to rx_backend.c (the 256-mark collect_in) is far from load_vulkan. No other check cuts LoadLibraryA or load_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

  1. A bare GitHub reference. The new test's docstring (line 6) says #2636. On Forgejo that links to Longshot's own #2636. Write https://github.com/NotRexed/Hookshot/pull/2636 (AGENTS.md).

Should change

  1. The reason given for the PR doesn't match main's load path. The description and the test's docstring say longshot_vk.so takes symbols from the executable, so one from another build fails at dlopen with undefined symbol: mkwvk_..., before the version check. On main I can't find any such link-time import:

    • The renderer reaches the host through the MkwVkHost table (vk_host) and through dlsym(RTLD_DEFAULT, "hos_posix_*").
    • The executable exports only hos_posix_* (--export-dynamic-symbol).
    • The .so has its own copy of win32_posix.c and links -Bsymbolic.
    • The hos_* it calls in gpu/vk are static 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 .so to check with nm. 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.

  2. A missing libvulkan.so.1 still logs no reason. The renderer loads the Vulkan loader itself (gpu/vk/vk_main.cpp load_global, LoadLibraryA("vulkan-1.dll")). That goes through the renderer's own copy of win32_posix.c, so after this PR the reason is in that copy's hos_load_error(). But vk_main.cpp:1594 logs only vk: load_global failed (...), and the next line is mkwvk_init returned NULL. The PR's dlerror() call also uses up glibc's message, so the renderer can't call dlerror() itself afterwards. Adding hos_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:928 already logs dlerror, and DLSS is loaded by the NGX library linked into the renderer, not by this path.

  3. 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.

  4. 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.cpp redact), 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>: from load_error when it matches path would halve the home-folder copies and make the line shorter.

  5. Conflict with #183. That PR inserts dev_dll_swap(path); between the memcpy and the LoadLibraryA in load_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 gcc but doesn't declare RUN_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 of win32_posix.c). That is negligible against the room #232 reserves, but worth knowing.

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, and `last_error` beside it is `__thread` too, so a `dlopen` on another thread can't change what `load_vulkan` reads. The buffer is cleared before the early `if (!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.c` `wgr_start`), so the line reaches `longshot_log.txt`. Every failure inside `dlopen` gets 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 https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/232 lands. Windows is unchanged: its `#ifdef _WIN32` branch 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.c` and `windows.h` haven't changed since the fork, and main's only change to `rx_backend.c` (the 256-mark `collect_in`) is far from `load_vulkan`. No other check cuts `LoadLibraryA` or `load_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 1. **A bare GitHub reference.** The new test's docstring (line 6) says `#2636`. On Forgejo that links to Longshot's own #2636. Write `https://github.com/NotRexed/Hookshot/pull/2636` (AGENTS.md). ### Should change 2. **The reason given for the PR doesn't match main's load path.** The description and the test's docstring say `longshot_vk.so` takes symbols from the executable, so one from another build fails at `dlopen` with `undefined symbol: mkwvk_...`, before the version check. On main I can't find any such link-time import: - The renderer reaches the host through the `MkwVkHost` table (`vk_host`) and through `dlsym(RTLD_DEFAULT, "hos_posix_*")`. - The executable exports only `hos_posix_*` (`--export-dynamic-symbol`). - The `.so` has its own copy of `win32_posix.c` and links `-Bsymbolic`. - The `hos_*` it calls in `gpu/vk` are `static 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 `.so` to check with `nm`. 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. 3. **A missing libvulkan.so.1 still logs no reason.** The renderer loads the Vulkan loader itself (`gpu/vk/vk_main.cpp` `load_global`, `LoadLibraryA("vulkan-1.dll")`). That goes through the renderer's own copy of `win32_posix.c`, so after this PR the reason is in that copy's `hos_load_error()`. But `vk_main.cpp:1594` logs only `vk: load_global failed (...)`, and the next line is `mkwvk_init returned NULL`. The PR's `dlerror()` call also uses up glibc's message, so the renderer can't call `dlerror()` itself afterwards. Adding `hos_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:928` already logs dlerror, and DLSS is loaded by the NGX library linked into the renderer, not by this path. 4. **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. 5. **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.cpp` `redact`), 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>: ` from `load_error` when it matches `path` would halve the home-folder copies and make the line shorter. 6. **Conflict with https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/183.** That PR inserts `dev_dll_swap(path);` between the `memcpy` and the `LoadLibraryA` in `load_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 gcc` but doesn't declare `RUN_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 of `win32_posix.c`). That is negligible against the room https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/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 : "");
Owner

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.cpp load_global could log hos_load_error() when vulkan-1.dll (libvulkan.so.1) doesn't load, since this call has already used up dlerror() for it. Today that case logs only vk: load_global failed.

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.cpp` `load_global` could log `hos_load_error()` when `vulkan-1.dll` (libvulkan.so.1) doesn't load, since this call has already used up `dlerror()` for it. Today that case logs only `vk: load_global failed`.
@ -278,1 +278,4 @@
#ifdef _WIN32
hos_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());
Owner

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 same path already 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.

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 same `path` already 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 only
Owner

On main, does longshot_vk.so import any symbol from the executable at link time? I can't find one. The host is reached through the MkwVkHost table and dlsym(RTLD_DEFAULT, "hos_posix_*"), the exe exports only hos_posix_*, and the .so has its own win32_posix.c with -Bsymbolic. A renderer from another build would then load and fail the MKWVK_API_VERSION check, which is already logged. Please check with nm -D --undefined-only on a built .so and restate the cases this catches: a missing file, GLIBC_2.xx not found, a wrong ELF class, the static TLS block.

On main, does `longshot_vk.so` import any symbol from the executable at link time? I can't find one. The host is reached through the `MkwVkHost` table and `dlsym(RTLD_DEFAULT, "hos_posix_*")`, the exe exports only `hos_posix_*`, and the `.so` has its own `win32_posix.c` with `-Bsymbolic`. A renderer from another build would then load and fail the `MKWVK_API_VERSION` check, which is already logged. Please check with `nm -D --undefined-only` on a built `.so` and 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 only
ERROR_FILE_NOT_FOUND, so the log said "(2)" for that and for a missing file alike. It now keeps dlerror()'s text, which
names the file or the symbol, and load_vulkan prints it (the dev-loop proposal's B1, #2636).
Owner

#2636 is a GitHub PR. On Forgejo, a bare #2636 links to Longshot's own number. Please use the full URL: https://github.com/NotRexed/Hookshot/pull/2636

`#2636` is a GitHub PR. On Forgejo, a bare `#2636` links to Longshot's own number. Please use the full URL: https://github.com/NotRexed/Hookshot/pull/2636
HumanBean force-pushed fix/renderer-load-error from 3ec509630d
All checks were successful
PR description / Description follows the template (pull_request_target) Successful in 4s
to eb7602580b
Some checks failed
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been skipped
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been skipped
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been skipped
Third-party parts / The same as upstream (pull_request) Has been skipped
Website / Website on Cloudflare (pull_request) Has been skipped
CI / Conventional Commit title (pull_request) Successful in 4s
CI / Workflow lint (actionlint) (pull_request) Successful in 7s
Third-party parts / As their notes say (pull_request) Successful in 14s
PR description / Description follows the template (pull_request_target) Failing after 10s
Website / Website on Cloudflare (pull_request_target) Has been skipped
2026-11-13 18:08:23 +00:00
Compare
Owner

I rebased this onto main @ fbd42024 (no conflicts) and addressed my review in one commit of mine on top of yours. It's pushed as eb760258.

My commit:

  1. The URL. The test's docstring links https://github.com/NotRexed/Hookshot/pull/2636. Your commit message still has a bare #2636; I left it, as it's yours.
  2. A missing libvulkan.so.1 is now explained. On Linux, mkwvk_init appends hos_load_error(), the reason the renderer's own LoadLibraryA kept, 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 second dlerror(). Windows is unchanged.
  3. Linux says what happens. The load_vulkan line now ends : nothing is drawn (wgr_start is a stub there). Windows keeps : WebGPU.
  4. The path is printed once. The Linux line names the file LoadLibraryA actually opened (longshot_vk.so, not .dll). It drops dlerror's leading "<that path>: " when the text starts with exactly that, for example rx: 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's version 'GLIBC_2.38' not found (required by …), is printed whole.
  5. The test. 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:

  • The stated reason ("a renderer from another build fails at dlopen with undefined symbol: mkwvk_...") likely can't happen on main. The renderer reaches the host through MkwVkHost and dlsym(RTLD_DEFAULT, "hos_posix_*"), so a mismatched renderer loads and then fails the MKWVK_API_VERSION check, which is already logged. nm -D --undefined-only on a built .so would 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.
  • Drop "falls back to WebGPU" for Linux.

It will conflict a little with #183, which inserts dev_dll_swap(path) in this hunk, whichever lands second.

I rebased this onto main @ fbd42024 (no conflicts) and addressed my review in one commit of mine on top of yours. It's pushed as eb760258. **My commit:** 1. **The URL.** The test's docstring links https://github.com/NotRexed/Hookshot/pull/2636. Your commit message still has a bare `#2636`; I left it, as it's yours. 2. **A missing libvulkan.so.1 is now explained.** On Linux, `mkwvk_init` appends `hos_load_error()`, the reason the renderer's own `LoadLibraryA` kept, 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 second `dlerror()`. Windows is unchanged. 3. **Linux says what happens.** The `load_vulkan` line now ends `: nothing is drawn` (`wgr_start` is a stub there). Windows keeps `: WebGPU`. 4. **The path is printed once.** The Linux line names the file `LoadLibraryA` actually opened (`longshot_vk.so`, not `.dll`). It drops dlerror's leading `"<that path>: "` when the text starts with exactly that, for example `rx: 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's `version 'GLIBC_2.38' not found (required by …)`, is printed whole. 5. **The test.** `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:** - **The stated reason** ("a renderer from another build fails at dlopen with `undefined symbol: mkwvk_...`") likely can't happen on main. The renderer reaches the host through `MkwVkHost` and `dlsym(RTLD_DEFAULT, "hos_posix_*")`, so a mismatched renderer loads and then fails the `MKWVK_API_VERSION` check, which is already logged. `nm -D --undefined-only` on a built `.so` would 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. - **Drop "falls back to WebGPU"** for Linux. It will conflict a little with https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/183, which inserts `dev_dll_swap(path)` in this hunk, whichever lands second.
HumanBean changed title from WIP: fix(linux): the log says why the renderer did not load to fix(linux): the log says why the renderer did not load 2026-11-14 00:46:21 +00:00
HumanBean deleted branch fix/renderer-load-error 2026-11-14 00:46:33 +00:00
Owner

Re-review after the merge. This PR landed on main as 5b81a8a1, squashed from eb760258 (the rebase onto fbd42024 plus my commit on top). I compared the squash with eb760258 for 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:

  • The bare #2636. The test's docstring now gives the full URL. dyst's commit message still says #2636, but the squash replaced it.
  • A missing libvulkan.so.1 is now explained. On Linux, mkwvk_init appends the renderer's own hos_load_error() to load_global's failure line and calls no second dlerror(). The buffer is __thread in the renderer's copy of win32_posix.c and is cleared at each LoadLibraryA, so a reason from an earlier load can't stay behind. When the loader did load and vkCreateInstance is what's missing, the reason is empty and the line is unchanged.
  • ": WebGPU" on Linux is now : nothing is drawn. Windows keeps its line word for word.
  • The path printed twice. The line names longshot_vk.so, and it drops dlerror's leading <path>: only when that is exactly what the text starts with. LoadLibraryA's .dll→.so rewrite and the \→/ change both leave the path GetModuleFileNameA gave unchanged on Linux. So the prefix matches dlopen's argument, and a message about another file (glibc's GLIBC_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):

  • The record on main is out of date. The squash commit 5b81a8a1 took the PR description as it was. It still gives "another build fails at dlopen with undefined 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.
  • The Linux test request was never opened. That's "a renderer that fails to load, expecting longshot_log.txt to name the reason". The code is tested game-free on Linux, so a run in the game would just confirm it. It's optional now.
  • #183 is still open, so the small rebase over load_vulkan (dev_dll_swap(path)) now falls to it. The two fit together.
Re-review after the merge. This PR landed on main as 5b81a8a1, squashed from eb760258 (the rebase onto fbd42024 plus my commit on top). I compared the squash with eb760258 for 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:** - **The bare `#2636`.** The test's docstring now gives the full URL. dyst's commit message still says `#2636`, but the squash replaced it. - **A missing libvulkan.so.1** is now explained. On Linux, `mkwvk_init` appends the renderer's own `hos_load_error()` to `load_global`'s failure line and calls no second `dlerror()`. The buffer is `__thread` in the renderer's copy of `win32_posix.c` and is cleared at each `LoadLibraryA`, so a reason from an earlier load can't stay behind. When the loader did load and `vkCreateInstance` is what's missing, the reason is empty and the line is unchanged. - **": WebGPU" on Linux** is now `: nothing is drawn`. Windows keeps its line word for word. - **The path printed twice.** The line names `longshot_vk.so`, and it drops dlerror's leading `<path>: ` only when that is exactly what the text starts with. LoadLibraryA's `.dll`→`.so` rewrite and the `\`→`/` change both leave the path `GetModuleFileNameA` gave unchanged on Linux. So the prefix matches dlopen's argument, and a message about another file (glibc's `GLIBC_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):** - **The record on main is out of date.** The squash commit 5b81a8a1 took the PR description as it was. It still gives "another build fails at dlopen with `undefined 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. - **The Linux test request was never opened.** That's "a renderer that fails to load, expecting longshot_log.txt to name the reason". The code is tested game-free on Linux, so a run in the game would just confirm it. It's optional now. - **https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/183** is still open, so the small rebase over `load_vulkan` (`dev_dll_swap(path)`) now falls to it. The two fit together.
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
MagicBeanDev/Longshot!34
No description provided.