Skip to content

[Tracking] Linux and Steam Deck: GPU hangs, crashes, low FPS and build failures #1000

Open

Description

@NotRexed

This issue collects every open report of Linux / Steam Deck GPU hangs, crashes, low fps and build failures, so there is one place to follow it. The individual reports were closed pointing here; their logs, screenshots and discussion stay in each linked issue.

Summary

Linux reports that are still open after v2.0.6:

  • RADV GPU hangs (gfx ring timeout and GPU reset);
  • low fps on RADV, limited by submits;
  • coloured noise on RADV;
  • NVIDIA driver crashes;
  • AppImage builder link failures.

Two Linux problems are fixed in v2.0.6:

Affected GPUs and versions

  • GPUs: Steam Deck (LCD / OLED), Steam Machine, RX 6600 to RX 9070 XT, Radeon 680M / 780M / 8060S on RADV; RTX 3060 / 3090 / 4090 / 5070 / 5090 on the NVIDIA driver.
  • Systems: CachyOS, Bazzite, SteamOS, Fedora, NixOS, Pop!_OS, Arch.
  • Versions: v2.0.1-v2.0.5 and main.

Evidence from the logs

RADV GPU hangs

RADV low fps

RADV rendering

NVIDIA on Linux

Start-up

Builder

Wayland

Workarounds

Reports merged here (25 issues, about 53 people)

Issue Title People GPU Version Key detail
#361 Corrupted rendering (noise / psychedelic colours) on AMD RADV (BC-250… 9 BC-250, RX 6600, RX 580, Ally X, RX 6800 XT (RADV) v2.0.1 RADV coloured-noise picture after intro: PR #425 (in v2.0.0) fixes the noise per adnanioricce (Ally Z1E Bazzite); Resolution 50% worked around it. sabogalc RX 6800 XT Ca…
#620 Crash On RX 6600 6 RX 6600 x3, RX 6900 XT (RADV) v2.0.5 Linux RADV hang ~3 min into intro/Kokiri (gfx ring timeout, MODE1 reset, KWin dies); v2.0.5 checkpoints: last finished c36a4f3f667c886a, first unfinished 89ca750f9852b7c…
#817 GPU hang on Steam Deck running CachyOS (RADV): gfx ring timeout, ring… 5 Steam Deck (CachyOS, SteamOS 3.8/3.9.5) v2.0.4-v2.0.5 GPU hang (gfx ring timeout, reset fails) in Kokiri/Deku Tree on RDNA2 Deck: hardicd traced it to one task shader, NVN code 2c56dc1006942f74 (~30 programs, nested 2D cull…
#528 [AMD RX 9070 / RADV Linux] Low FPS (22-25) in Kokiri Forest + color d… 4 RX 9070, RX 9060 XT, RX 7900 XTX, Radeon 860M (RADV) v2.0.1 RADV low fps: faster-fps batched-submit poll flushes 10x more (11.5 -> 2.5 fps on 9060 XT); RX_VK_BATCH_POLL_US=2000 restores (+65% on 7900 XTX); PR #448 udmabuf halves…
#50 Tested on CachyOS (Linux) with RX 6700 XT + Ryzen 5 5600 3 RX 6700 XT, RX 6600 (CachyOS) early Linux: 9-10 fps, black after cutscene 30 min
#756 Just unplayable on mid-high end setup (on Linux) 3 RX 9070 XT (CachyOS) v2.0.2 5-10 fps at start, CPU 35% GPU 45%, no visible shader preparation (RADV poll issue #528?)
#776 Steam Machine 3 Steam Machine (RDNA3 Navi 33) v2.0.2 10-15 fps in Kokiri with CPU/GPU ~30% (SteamOS); another Steam Machine user can't open it
#478 steam deck build error app image 2 Steam Deck device-lost-fix Steam Deck AppImage build stops in lift.py: "ADR / ADRP+ADD code addresses outside the 284387 unwind-covered functions: 14083" with romfs/exefs and .nup; another Deck us…
#873 Game freezes on loading screen and crashes OS after restarting due to… 2 RX 6600 XT (RADV), MSI Claw 8 AI+ (Arc) v2.0.0 Garbled colours at the Deku Tree/Navi scene, save load hangs, OS freeze
#490 Cachyos & AMD Testing Info (9070XT, R7 5800X) 1 RX 9070 XT (CachyOS) no-visual-studio Stutter, flashing, freezes to one frame (log)
#604 RX 6800 XT (RADV, Linux): GPU hang and full GPU reset a few seconds a… 1 RX 6800 XT (RADV) main 97e7c55 RADV RDNA2: GPU hang (comp ring then gfx ring timeout, MODE1 reset, PC reboot) a few seconds after loading a Kokiri save, also with RX_VK_KEEP_RETIRED=1; no page fault;…
#733 Build error: link FAILED (0s), 0 undefined symbols 1 Steam Deck v2.0.3 Linux link fails: v2.0.2 missing code_0xx.o objects, v2.0.3 ld.lld undefined symbols f_82647a90... (new code split)
#745 Black screen and bad renderings in hyrule castle and death montain af… 1 RTX 4090 (Linux 615.78) v2.0.2 Hyrule Castle / Death Mountain black textures and shaders after v2.0.2, fixed by restart for a while (log)
#762 Error in linux builder lastest versión 1 SteamOS v2.0.3 Linux builder compile error, only a photo of the screen
#770 RX 6700XT Ryzen 5 5600 graphic driver crash and low fps 1 RX 6700 XT (RADV CachyOS) v2.0.3 Wrong colours, PC crash when the Deku Tree appears, 23 fps (5 logs)
#789 RADV (Radeon 8060S / Linux): each vkQueueSubmit2 costs ~2 ms on the r… 1 Radeon 8060S (RADV) v2.0.2 Each vkQueueSubmit2 ~2 ms on the replay thread (userptr imports), ~42 fps cap; submit thread patch +4-8 fps but flashes; asks RX_VK_WAIT_GPU=1 default on RADV
#797 Immediate crash on Linux (Pop!_OS, NVIDIA GTX 1650) - headless graphi… 1 GTX 1650 (Pop!_OS) v2.0.4 Linux AppImage exits at once: log stops at "no graphics server yet (headless)" then "window closed, exiting" (SDL video driver; cf. #801 offscreen driver on NixOS)
#801 Linux/NVIDIA: gameplay freeze and crash in libnvidia-gpucomp.so.580.1… 1 RTX 3060 (NixOS, driver 580.142) a598b9f Segfault in libnvidia-gpucomp.so.580.142+0xe3facc on an mkw_vk worker (pipeline compile) after 2 h, and again at the next start after 27 s; 18 GB RAM + full swap; bundle…
#836 SteamOS 9070XT never launches after successful setup 1 RX 9070 XT (SteamOS) v2.0.5 SteamOS: Play does nothing, icon in taskbar, no window (log .1); maybe same as #803
#862 Steam Deck (RADV VANGOGH): process exits leaving the starting room th… 1 Steam Deck OLED (RADV VANGOGH) main 58af2c0 / 15ec28d Process exits (exit 0, no device lost) walking out of the starting room on 58af2c0 + ms-eviction patch; vanilla 15ec28d segfaults write 0x40 in libvulkan_radeon.so durin…
#890 Linux AppImage v2.0.5: link FAILED, 20 undefined symbols (f_811f3a80… 1 Linux v2.0.5 Linux link fails with 20 undefined f_81xxxxxx: lift cache (.lift_key) reused C missing the code_NNN_k parts; delete sa/gen/.lift_key; PR #918 stops reusing an incomplete…
#950 Frozen Screen when throwing bombs 1 RTX 5070 (Linux) v2.0.5 Picture freezes when throwing bombs; one log crashes reading guest memory in hookshot_vk.so; dma-buf not imported (-2) in all logs
#977 [AMD RX6600] Saturated colors, crashes after the first scene of the i… 1 RX 6600 (RADV, Fedora) v2.0.5 Oversaturated colours, crash right after the logo that takes down the GNOME session; device lost in both logs
#984 [CachyOS Linux/AMD] GPU hang (gfx ring timeout → MODE2 reset, device… 1 Radeon 680M (RADV, CachyOS) a598b9f GPU hang after the intro movie (gfx ring timeout, MODE2 reset, device lost), 4 times; fine on Windows on the same laptop (honest-eta-era build)
#992 [Wayland] Freezes after NVN window creation on hybrid AMD+NVIDIA lapt… 1 GTX 1650 + Renoir iGPU (hybrid, Pop!_OS COSMIC Wayland) v2.0.5 Wayland hybrid laptop: freezes after 'nvn: window of 3 textures', renderer never reaches vkCreateInstance, process unkillable (D state); works with SDL_VIDEODRIVER=x11 +…

Adding a report

Please update to v2.0.6 first (https://github.com/NotRexed/Hookshot/releases/tag/v2.0.6; Update next to Play in the launcher). Please add: distro, kernel, Mesa or NVIDIA driver version, GPU, version, hookshot_log.txt, and for hangs the dmesg lines around the reset.

All tracking issues

Activity

  1. marked Crash On RX 6600 #620 as a duplicate of this issue on Nov 9, 2026
  2. marked Steam Machine #776 as a duplicate of this issue on Nov 9, 2026
  3. 134 remaining items

  4. addisonbair commented on Nov 12, 2026

    @addisonbair
    Contributor

    Steam Deck performance: where the frame goes, what helps today, and what didn't

    We measured every change on a Steam Deck LCD in one deterministic scene: headless, standing in Kokiri Forest next to Saria (~2,400 draws), GPU fixed at 1200 MHz, same save, alternating A/B runs.

    fps GPU ms
    v2.0.14 (settings below) 19.4-20.9 28.2
    main eaef146, default switches 13.5 / 13.8 30.0
    main + #2422 14.3 / 14.4 29.9

    v2.0.14 is still the fastest Hookshot on the Deck, by 30-40 %. Per-thread perf traces of main show where it goes:

    The Deck's own ceiling: even on v2.0.14 the frame is ~37 ms of replay that runs in order under the order lock (on a P16 stand-in it's held 93-98 % of the time). Little of the frame is idle handoff. These were all measured and didn't help on the Deck:

    • async replay (slower, plus exposure flicker);
    • parallel replay;
    • the recorder thread;
    • per-queue fences;
    • batch sizes;
    • mirrors;
    • DCC (broke lighting);
    • thread pinning;
    • a cross-frame descriptor-set cache (replay slower);
    • deferring the present (flat).

    Faster single-thread CPUs are what move this.

    What works on the Deck today (v2.0.14):

    • RX_VK_MESH_OPT=1 RX_VK_PER_QUEUE_FENCES=0 RX_VK_WAIT_GPU=0 RX_VK_BATCH_MAX=4 RADV_PERFTEST=cswave32 in hookshot_env.txt.
    • Upscaler FSR 3.1, Render resolution Performance, Frame generation 2x.
    • Manual GPU clock 1200 MHz, and 48 Hz with a 48 fps limit.

    That's ~24 fps base in villages. Hyrule Field is CPU-bound (~10 fps): there, a lower GPU clock (800-1000 MHz) leaves more of the 15 W for the CPU.

    On v2.1.0 / main:

    The full numbers, the benchmark setup, and a v2.0.14-based branch with the merged Deck fix (#1965) are at https://github.com/addisonbair/Hookshot/blob/deck/docs/steam-deck.md. We're stepping back from Deck tuning for now; #2422 stays open.

    🤖 Generated with Claude Code

  5. naxil commented on Nov 12, 2026

    @naxil

    amvlk here (amd 5600G + amd 6600 8gb), solve all the problem, but it rebuild the shaders (instead radv, load the shaders immediatly)

  6. kuraeow commented on Nov 12, 2026

    @kuraeow

    hookshot_gpu.txt
    hookshot_log.txt

    I'm experiencing GPU hangs on my Steam Deck OLED with Hookshot v2.1.0 (source e561602e), running SteamOS.

    Even before the intro cutscene, the title screen has noticeable performance issues. FPS drops significantly during the close-up shot of the grass, and the frame rate is generally quite uneven. The frametime graph shows regular, periodic spikes, almost as if some part of the original game's 60 Hz timing or synchronization is still interfering with frame pacing. That's just a guess based on the graph, though.

    The intro cutscene itself runs at around 60 FPS, but the frame pacing isn't consistently smooth. Right at the transition into gameplay, FPS drops to ~1, then slowly recovers to ~8 FPS while the screen remains black. On my latest attempt, I briefly saw the first second of gameplay before the GPU hung and the game eventually exited.

    VRAM allocation shown by the Steam Deck overlay reached approximately 5.5–5.6 GB.

    I tried the following settings in hookshot_env.txt:

    RX_VK_MESH_NOCULL=2c56dc1006942f74
    RX_VK_CHECKPOINTS=1

    The shaders were rebuilt (over 67,000 compilation operations), but the issue persists.

    The kernel log confirms an AMDGPU hang:

    ring gfx_0.0.0 timeout
    Process hookshot pid 8957 thread Presentation Th
    Ring gfx_0.0.0 reset failed
    GPU reset begin!
    MODE2 reset
    GPU reset succeeded
    

    On one attempt, the entire Steam Deck became unresponsive and required a forced reboot. On the latest attempt, SteamOS remained responsive and the game exited after the GPU reset.

    I've attached hookshot_log.txt and hookshot_gpu.txt.

    Let me know if any additional diagnostics would help!

  7. camilobt commented on Nov 12, 2026

    @camilobt

    Steam Deck LCD, v2.1.0: GPU only ~56% busy because of readback waits (#1375); RX_VK_NO_READBACK_WAIT=1 gives +16%. Also: why RX_VK_ASYNC=1 freezes at boot, with a patch

    Hardware / build

    • Steam Deck LCD, SteamOS 3.8.28, kernel 6.18 (valve), RADV VANGOGH, driver 0x6801063, GPU 1600 MHz, 15 W cap
    • Hookshot v2.1.0 AppImage (source e561602e), built on the Deck from my own .nup (v131072)

    Method

    • Headless runs (HOS_HEADLESS=1), HOS_HANDHELD=1, HOS_OUTPUT_RES=1280x800, RX_VK_MESH_OPT=1, RX_VK_PRECOMPILE=0
    • Save loaded by an input script; Kokiri Forest, open view outside Link's house (Saria's dialogue after leaving the house). Link does not move.
    • FPS = mean of the last 18 vi: ... fps over the last 5.0 s lines (90 s), after ~170 s of warm-up. Run-to-run spread of the baseline: 21.62-21.88 fps (5 runs).

    1. Configuration and hangs (loading the save into Kokiri)

    Config Result
    default gfx_0.0.0 timeout, GPU reset while loading
    RX_VK_MESH_NOCULL=2c56dc1006942f74 (the 2.1.0 workaround) gfx_0.0.0 timeout, GPU reset while loading
    RX_VK_TASK_MESH_FALLBACK=1 standing: OK, 21.6 fps; walking: GPU reset after a few minutes
    RX_VK_MESH_OPT=1 no hang (standing, walking inside the house, ~15 min outside in Kokiri)
    RX_VK_MESH_NOCULL + RX_VK_MESH_OPT=1 no hang, slightly slower than MESH_OPT alone

    So on this Deck, NOCULL alone does not avoid the hang in 2.1.0; MESH_OPT does.

    2. CPU and GPU take turns

    Render resolution (menu row, resolution= in hookshot_options.ini) against frame time:

    resolution GPU time / frame fps frame time
    33% 19.7 ms 23.9 41.8 ms
    Auto (3D scale 0.75) 25.0 ms 21.8 45.9 ms
    100% 37.0 ms 15.5 64.7 ms

    Frame time is about GPU time + ~21 ms in every case: the GPU and the CPU work one after the other. gpu_busy_percent (amdgpu sysfs, 300 samples at 10 Hz) agrees: 55.7% busy at Auto, about half the samples near 100% and the rest near 0.

    An all-thread sample (HOS_SAMPLE_ALL, scripts/crit.py) shows the CPU is not saturated either: on average fewer than 2 game threads run at once, sead::MainThread is blocked ~86% of the time (job waits in f_8007fe60 / the 20 µs SleepThread poll at lr 0x8009a478, ~58k calls/s, and event waits), and the Presentation Thread waits in queue_get ~39% of the time.

    3. RX_VK_NO_READBACK_WAIT=1: +16%

    fps (runs) GPU busy
    default (MESH_OPT) 21.62 / 21.81 / 21.88 55.7%
    RX_VK_NO_READBACK_WAIT=1 25.33 / 25.12 / 25.37 66.9%

    No GPU hang. RX_VK_FLASH=1 with RX_VK_SHOT_EVERY=1 over ~3 minutes of frames: no flash in either configuration. Screenshots at the same presents look the same: grass, fog and lighting. I only tested this one scene, so the CPU reads that the readback waits protect (#1375) may still matter elsewhere. Maybe the waits could be limited to the reads that need them, instead of waiting for every submission that wrote the page.

    4. Things that did not change fps here (each ±0.1-0.2 of the baseline)

    • HOS_RESERVE_CORE=0, HOS_VCORES=0, HOS_CORES=8, HOS_POLL_SLEEP_NS=5000, HOS_PRESENT_POLL=1, HOS_PRESENT_NOWAIT=1, RX_VK_BATCH_IDLE=0, HOS_SPIN_US=0
    • HOS_PIN=Presentation=c0, the Presentation Thread on the reserved core, which on Linux sits idle without async because replay_pin_mask() is Windows-only: replay submit went from 30.7 to 26.7 ms, but fps stayed the same (21.7). The replay is not the frame's limit here.
    • Rebuilding hookshot_vk.so with -O3 (21.9) or -mtls-dialect=gnu2 (21.8). ThinLTO (-flto=thin) gave 22.2 alone, and nothing on top of NO_READBACK_WAIT (25.37).
    • HOS_GRAPHICS_SCALE=0.5 and HOS_RES_SCALE=0.5 did not change the 3D scale (GPU time unchanged).

    5. RX_VK_ASYNC=1 freezes at boot on Linux/RADV: cause and patch

    With RX_VK_ASYNC=1 the game stops after ~19 submits: no present, every thread idle. All-thread sample: sead::MainThread is in n_nvnSyncWait → mkwvk::wait() → vkWaitSemaphores with no timeout. The value it waits for belongs to work the replay thread has stopped at an event wait (submission 11..14 stopped at command buffer 0's wait for event ...), for the event the game signals after this wait. Or it is recorded but held in the batch (RX_VK_BATCH, on with RADV), which nothing flushes while the game waits. The 3 ms hold/drain in wait() is skipped by !async_on().

    • RX_VK_ASYNC=1 RX_VK_BATCH=0: boots and plays, but at 11 fps, because unbatched submits cost ~15 ms a frame here.
    • The patch below (~25 lines, gpu/vk/vk_replay.cpp): in wait() with async on, it bumps drain_waiters and notifies q_cv so that the replay thread runs its stopped work (drain 2, as wait_queue_empty does), and it waits in 2 ms steps, flushing the batch between them. With it, ASYNC + batching boots and plays with no freeze.
    • Async is still slower on this Deck, though: 11 fps against 21.8 sync, with ~27k forced waits in 4 min. OoT waits for the GPU several times a frame, and each wait now crosses threads. So the patch only fixes the freeze; it does not make async a win here.

    I can run more A/B tests on this Deck if useful (same scene, same script).

    The patch (gpu/vk/vk_replay.cpp, v2.1.0)
    --- a/gpu/vk/vk_replay.cpp
    +++ b/gpu/vk/vk_replay.cpp
    @@ -2134,6 +2134,7 @@
         auto it = q_last.find(queue);
         return it != q_last.end() ? it->second : fence();
     }
    +void async_wait_nudge(int d);   // (RX_VK_ASYNC: a waiter asks the replay thread for its stopped work, below)
     int wait(uint64_t f, uint64_t timeout_ns) {
         if (completed() >= f) return 1;
         queue_flush_for(FR_WAIT);
    @@ -2181,7 +2182,26 @@
         wi.pSemaphores = &timeline;
         wi.pValues = &f;
         queue_flush_for(FR_WAIT);
    -    const VkResult res = vkWaitSemaphores(dev.device, &wi, timeout_ns);
    +    VkResult res;
    +    if (async_on() && timeout_ns) {
    +        // (camilo-deck) RX_VK_ASYNC: the work this waits for can be stopped at an event wait on the replay thread (the event
    +        // the game signals after this wait), or recorded but held in the batch (RX_VK_BATCH) that nothing flushes while the
    +        // game waits: the game froze at its first frames. The replay thread lets its stopped work on (drain 2, as for
    +        // wait_queue_empty) and the held batch goes out every 2 ms until the value is reached
    +        async_wait_nudge(1);
    +        const auto t0 = std::chrono::steady_clock::now();
    +        for (;;) {
    +            const uint64_t waited = (uint64_t)std::chrono::duration_cast<std::chrono::nanoseconds>(
    +                std::chrono::steady_clock::now() - t0).count();
    +            const uint64_t left = waited >= timeout_ns ? 0 : timeout_ns - waited;
    +            res = vkWaitSemaphores(dev.device, &wi, std::min<uint64_t>(left, 2000000));
    +            if (res != VK_TIMEOUT || left <= 2000000) break;
    +            queue_flush_for(FR_WAIT);
    +        }
    +        async_wait_nudge(-1);
    +    } else {
    +        res = vkWaitSemaphores(dev.device, &wi, timeout_ns);
    +    }
         static int logged;
         if (res != VK_SUCCESS && timeout_ns > 1000000000ull && logged++ < 8)
             logf("vk: wait for fence %llu (done %llu, pushed %llu): %d", (unsigned long long)f,
    @@ -3840,6 +3860,11 @@
     };
     std::mutex q_mu;
     std::condition_variable q_cv, q_space, q_idle;
    +void async_wait_nudge(int d) {   // (camilo-deck: wait's, RX_VK_ASYNC)
    +    std::lock_guard lk(q_mu);
    +    drain_waiters += d;
    +    if (d > 0) q_cv.notify_all();
    +}
     std::deque<Job> q_jobs;
     int q_presents = 0;
     bool q_busy = false;
  8. ieocin commented on Nov 12, 2026

    @ieocin

    Blackscreen into crash, GPU reset (KDE plasmashell, steam, etc. crash & visual bugs)

    GPU: RX 6600
    OS: Linux (Bazzite)
    hookshot version: 2.1.0
    where it happens: after the intro cutscene title card cuts into black

    hookshot_log.txt

  9. camilobt commented on Nov 12, 2026

    @camilobt

    Follow-up on the Deck readback waits: which readback costs the time

    Same Steam Deck LCD and v2.1.0 setup as my comment above. I logged every readback wait (RX_VK_LOG_READBACK_WAITS=1, RX_VK_LOG_READBACK=4000). Then I built a diagnostic hookshot_vk.so that reads the range before and after each wait in invalidate() and counts how often the bytes changed. Scene: Kokiri Forest, standing.

    Wait time by range (300 logged waits):

    range waits time bytes changed by the wait
    0x113b623000 / 0x113b628000 (+0x3980, alternating pair) 86 482 ms, 94% of all wait time, 5.6 ms avg, up to 13.7 ms ~48% of waits
    10 other ranges, in 5 adjacent pairs (0x115bcde000/0x115bcf9580 +0x1b580, 0x117cf04000/0x117cf9f040 +0x9b040, ...) ~215 33 ms total, 0.01-0.5 ms each never (0 of ~2,000 waits per range)
    • The waits on the 10 large ranges look like false dependencies. The data is already final, maybe because the write-back marks both halves of a double-buffered binding. But they are cheap, so skipping only those would not change fps.
    • The pair that costs the time is a real dependency. It is read right after the submission that writes it (value == fence, the latest one), and its data changes. The first words are 0.1, 25000, 1.7778, ~1.0, 0, 0, 24999.9: near, far and 16:9 aspect, so it looks like a camera-related GPU result (visibility/culling?).
    • So the +16% from RX_VK_NO_READBACK_WAIT=1 comes almost entirely from letting the game read that pair about 2 frames late.

    Backport of #2125 + #2222 + #2422 onto v2.1.0 (readback waits on, held submits sent during readback waits): 21.0 fps, GPU 53.9% busy, the same as plain 2.1.0. RX_VK_NO_READBACK_WAIT=1 on top of the backport: 25.4 fps. On this Deck the cost is the wait itself, not when the held work goes out.

    Camera test (RX_VK_NO_READBACK_WAIT=1): Saria's dialogue, where the scripted camera cuts between shots. HOS_REALTIME_STEP=0, HOS_INPUT_FRAME_CLOCK=1, a shot every 20 presents. Without waits the scene ran at 26.4 fps, against 22.8 / 20.9 with them. Comparing shots of the same moment, I saw no missing or late geometry: grass, trees, fairies and houses are all there. But two runs with waits already diverged (dialogue progress differs), so this is not a frame-exact comparison. I can't rule out a one- or two-frame pop-in at cuts.

    If that pair is something the game can read one frame late, special-casing it would give the Deck about +16% while keeping the wait for the exposure meter and everything else. I can test a build that skips the wait for one range if you tell me what it is.

  10. mooki000 commented on Nov 12, 2026

    @mooki000
    Contributor

    Tracker upkeep note (merged pull requests and code checked against main 51da0464; no new test run was made for it).

    Several old summary entries can be marked completed on main 51da0464: #1012 packages the missing AppImage header, #1839 fixes SDL2's COSMIC/libdecor startup deadlock, #1936 supplies the supported container build on non-Ubuntu hosts, #2111 explains denied udmabuf access, #2340 stops refused NVIDIA dma-buf import attempts, and #1965 enables the task/mesh optimizer on RADV. #2362 additionally makes Linux AMD Automatic choose full Compute. These are bounded fixes, not proof that every Deck/RADV hang, memory-pressure exit or NVIDIA crash is gone. In particular #2411's memfd-discard fault remains separate, and #2431 deliberately waits behind it. Please update those stale subcases without closing this tracker; no new game run was made for this note.

  11. hclaria commented on Nov 12, 2026

    @hclaria
    Contributor

    Steam Deck: three system settings that removed most of the long frame-rate drops for me (swap order, a memory protection, huge pages)

    Edit, same day: use MemoryLow=6G, not 8G. With 8G, loading a save took 80 s on this Deck; with 6G, same game and same renderer, 12 s (12 to 15 s before the protection was set). The game grows to about 10 GB while a save loads and the GPU driver asks for memory at the same time; at 8G the kernel had almost nothing it could take quickly: a 64 MB allocation took 3.4 s, and 180,000 to 240,000 pages went to swap per 30 s against 650,000 to 980,000 without the protection. With 6G the protection still holds in play: the game keeps 5.7 to 8.2 GB resident, and swap-outs fall to 6,000 to 7,000 pages per 30 s once the save is loaded. The commands below now say 6G; the numbers in section 2 were measured with 8G.

    Sharing measurements from one Steam Deck, because the default SteamOS memory setup works against this game and the fix is outside Hookshot. Related to the memory reports in #993.

    Setup. Steam Deck OLED (Van Gogh 0x1435, 16 GB), SteamOS 3.9.5, kernel 7.2.7-valve2, Mesa 26.2.0-devel (RADV). Game built from main at c544828f. Play sessions used a renderer built from c544828f with #2789, #2644, #2774, #2731, #2767 and #2649 merged on top; the headless A/B below used the unchanged c544828f renderer. Not default switches: HOS_HANDHELD=1, RX_VK_LOW_SPEC=1, RX_VK_VRAM_MB=2000, RX_VK_TEX_MAX_MB=128, RX_VK_TEX_MAX_DIM=1024, RX_VK_TEX_SKIP_MB=4, RX_VK_FRAMEGEN_MULT=2, RX_VK_FSR_FG=0. The swap file had already been enlarged from SteamOS's 1 GB to 16 GB.

    The game's footprint here is about 15.6 GB (its cgroup: ~4 GB resident plus ~11.6 GB swapped at one point), so several GB always live in swap on a 16 GB Deck. What matters is how.

    1. zram before the disk swap file costs RAM instead of saving it. SteamOS puts a 7.2 GB zram device at priority 100 and /home/swapfile at -1. The game's cold pages compress badly. After 20 hours of uptime (/sys/block/zram0/mm_stat, /proc/vmstat): zram's peak memory use was 4.48 GB for at most 7.2 GB stored, 6.7 million pages had been stored uncompressed (huge_pages_since), 192 GB had been swapped out and 46 GB back in.

    Swap file first (swapon -p 200 /home/swapfile), same scene, headless, standing still in Hyrule Field, 110 s measured:

    zram first swap file first
    game resident 4.07 GB 8.25 GB
    game in swap 6.27 GB 0.09 GB
    pages swapped out / in during the run 1,704,818 / 80,321 31,664 / 622
    major faults 79,134 2,441
    fps 21.77 21.70

    The frame rate of a still scene does not change. In play the long collapses went away.

    2. The kernel then still evicted the game with gigabytes free. In play, with the swap file first: game resident 2.7 GB, 11.1 GB in swap, 4.0 GB of RAM free. /proc/buddyinfo showed 3.4 GB free and no free block of order 7 or more. Over 4 minutes kswapd ran 1,861 times (pageoutrun) and reclaimed 4.14 million pages (pgsteal_kswapd): 2,225 pages a run, which is compact_gap for an order-10 request (2,048). So something asks for 4 MB blocks about 8 times a second, memory is too fragmented to give them, and each try evicts ~8 MB. I did not trace the caller; the GPU driver's page pool is my guess, and the pinned dma-buf imports may be what keeps memory fragmented. The cost was swap-in storms: 339,000 pages read back in 30 s and 5.5 to 6.2 fps for about 35 s at the Kakariko windmill, in a scene of 1,100 to 1,450 draws with nothing compiling.

    systemctl set-property --runtime user.slice MemoryLow=8G stopped it (8G is what I measured here; 6G is the value to use, see the edit at the top): pages swapped out went from 100,000 to 580,000 per 30 s to 9,000 to 17,000, then 0 for the next minutes; memory pressure (/proc/pressure/memory, full avg10) from 5.5 % to 0.0 to 0.5 %.

    3. Transparent huge pages off (always to madvise, khugepaged/defrag 0). The amount evicted did not change, but windows of 120 presents under 12 fps went from 7 in 9 minutes to 2 in 6.5 minutes. Weaker evidence: one session each, not exactly the same route.

    All three together, 4 minutes through Castle Town market, 30 windows of 120 presents at ~2,150 draws: mean 16.8 fps, one window under 12 fps (8.5, on arrival in an area), 96,000 pages swapped out in total, against 4.1 million in 4 minutes before.

    Commands (root; the last three do not survive a reboot as written):

    swapoff /home/swapfile && swapon -p 200 /home/swapfile
    systemctl set-property --runtime user.slice MemoryLow=6G
    echo madvise > /sys/kernel/mm/transparent_hugepage/enabled
    echo 0 > /sys/kernel/mm/transparent_hugepage/khugepaged/defrag
    

    The swap priority can be kept across reboots with a drop-in for home-swapfile.swap holding [Swap] and Priority=200, and the protection by leaving --runtime out.

    To check a Deck: swapon --show, cat /sys/block/zram0/mm_stat (third field: bytes of RAM zram uses now, fifth: its peak), cat /proc/pressure/memory.

    Limits. One Deck, short sessions, a renderer with open PRs in play. The settings are system-wide. Swap on the SSD means more writes to it. The 6 GB figure fits 16 GB of RAM and this GPU memory use; 8 GB was too tight (the edit at the top), and I did not try other values.

    If it is useful I can send a troubleshooting row for the README, and a Linux line for the boot memory notice when a zram device outranks a disk swap. Huge pages could also be turned off for the game's process alone (prctl(PR_SET_THP_DISABLE)), which I have not tried yet.

  12. eleyoy commented on Nov 12, 2026

    @eleyoy

    A report for the RADV GPU hang section: VK_ERROR_DEVICE_LOST on a Van Gogh APU (ROG Xbox Ally, Ryzen Z2 A), with the kernel side of it.

    Same signature as #2245, on a different Van Gogh device. Three things here may be new: the kernel's own verdict on the hang, a measurement that rules out the lockup_timeout theory, and the reason the #1300 Steam Deck workaround does not reach this title even on the same GPU.

    I no longer have hookshot_log.txt — the install was removed before I thought to keep it — but the kernel log of the crashed boot survived and is attached. The game lines below are the ones I recorded at the time.

    System

    Device ASUS ROG Xbox Ally (RC73YA)
    APU AMD Ryzen Z2 A — RADV VANGOGH, 6144 MB VRAM fixed in BIOS
    OS Bazzite, kernel 7.3.3-ogc1.1.fc45
    Mesa 26.2.6
    Hookshot v2.1.0 (source e561602e)
    Game OoT, language es-419, built locally from the AppImage
    vk: AMD Ryzen Z2 A (RADV VANGOGH), Vulkan 1.4, driver 0x6802006; mesh shaders yes, host memory import yes, pipeline libraries yes
    vk: mesh shaders row: Automatic (native)
    

    Symptom

    Exactly as in #2245: the game starts, the file-select works, the opening cutscene plays, and the handover to gameplay loses the device. Reproduced three times out of three.

    radv/amdgpu: The CS has been cancelled because the context is lost. This context is innocent.
    vk: batched submit 9638 failed (-4)
    vk: device lost: reading the fault info (RX_VK_CHECKPOINTS=1 for markers)
    vk: FATAL: the GPU device was lost (VK_ERROR_DEVICE_LOST)
    

    The last frame before the loss was a heavy upload frame:

    vk: slow upload 2727.2 ms: #524 0x1142540000 512x512x1 x1 levels 1 NVN 0x29, 2048 of 2048 KB
    vk: slow submit: 2739.6 ms: texture lookups 2728.9 uploads 2728.6 dispatch 2737.4
    vk: a frame's uploads took 2878 ms: ASTC on the GPU 37 (128.0 MB, 133 ms); on the CPU 69 (40.1 MB, 2745 ms)
    vk: a capture's wait for write-back 66677 timed out (done 66673)
    

    What the kernel says

    This is the part I have not seen posted in the Linux reports. journalctl -k for the crashed boot:

    amdgpu 0000:04:00.0: Dumping IP State
    amdgpu 0000:04:00.0: [drm] AMDGPU device coredump file has been created
    amdgpu 0000:04:00.0: ring gfx_0.0.0 timeout, signaled seq=504077, emitted seq=504079
    amdgpu 0000:04:00.0:  Process hookshot pid 37113 thread sead::MainThrea pid 37119
    amdgpu 0000:04:00.0: Starting gfx_0.0.0 ring reset
    amdgpu 0000:04:00.0: Ring gfx_0.0.0 reset failed
    amdgpu 0000:04:00.0: GPU reset begin!. Source:  1
    amdgpu 0000:04:00.0: MODE2 reset
    amdgpu 0000:04:00.0: GPU reset succeeded, trying to resume
    amdgpu 0000:04:00.0: GPU reset(2) succeeded!
    amdgpu 0000:04:00.0: [drm] device wedged, but no recovery needed
    

    Two details worth noting:

    • It is a gfx ring timeout with no VM fault and no page fault anywhere in the log. The job simply never signalled: emitted seq=504079, signaled seq=504077. That is consistent with a shader that does not terminate, not with an out-of-bounds access.
    • The ring reset failed, so amdgpu escalated to a full MODE2 device reset. On this handheld that takes the session down with it; the machine had to be power-cycled.

    The lockup_timeout theory does not hold

    Some reports suggest the kernel may be giving up too early on a long submission. On this system that is not what happened:

    • amdgpu's bare-metal default for non-compute rings is 10000 ms. The parameter is untouched here (/proc/cmdline carries only amdgpu.ppfeaturemask=0xfff7ffff).
    • The slowest submit Hookshot logged in the entire run was 2739 ms, well inside that budget.

    So the hung job did not merely overrun a tight deadline — it never completed at all. Raising lockup_timeout would delay the reset, not prevent it.

    RX_VK_MESH_NOCULL=1 does not reach this title's task shader

    #1300 added RX_VK_MESH_NOCULL for a Steam Deck task-shader hang. The Steam Deck and this device are both Van Gogh, so this looked like the same bug. It is not reachable from here:

    vk: task code 1813f9ceef04a63f: culling pattern not found, left as it is (RX_VK_MESH_NOCULL)
    

    The whole run compiles exactly one task code, and the pattern the workaround looks for is not present in it. With the variable set, the build still produced the unmodified shader and the configuration is byte-for-byte the one that hangs.

    If the culling pattern is title-specific, OoT's task shader appears to use a different shape than the one #1300 targets, and the workaround silently does nothing rather than reporting that it cannot help. The log line is clear enough once you look for it, but nothing surfaces it to the user.

    mesh_shaders=2 (Compute) does avoid the hang

    Confirmed over a long session: file select, cutscene, handover, and extended gameplay with zero device losses. Two costs worth recording:

    • Pipeline count triples. Native: 20463 pipelines. Compute: 67099 predicted. On this hardware the first build of the compute path is a one-to-two hour job, and it is easy to mistake for a hang — the ETA climbs for a long while before it falls.
    • Large performance cost, though on this APU the port is CPU-bound either way (below).

    Performance notes on the Z2 A, in case they are useful

    Steady ~19-20 fps in normal outdoor areas, with the GPU idle:

    vk: 10200 presents, 20.2 fps; a frame: 2413 draws (0 skipped), 118 dispatches, 205 passes,
    141 barriers, 6 submits, GPU-bound wait 0.3 ms; 10898 pipelines, 1054 textures (2416 MB)
    

    GPU-bound wait 0.3 ms with the CPU pinned at its 3799 MHz ceiling: the limit is draw submission on four Zen 2 cores, not the GPU. Lowering resolution or quality preset changes nothing measurable, and the power limit is not the constraint either.

    One option made a large difference and is off by default:

    sampler_cache=0 sampler_cache=1
    descriptors 49.8 ms 12.6 ms
    texture lookups 43.8 ms 9.1 ms

    Same scene class, from slow submit lines. If there is no correctness reason to keep it off, it may be worth defaulting on — or at least worth mentioning in the handheld guidance.

    Suggestions

    1. When RX_VK_MESH_NOCULL is requested but the pattern is not found, consider surfacing that to the user rather than only to the log — it currently looks like the workaround is active when it is not.
    2. The wiki's device-lost steps (more VRAM, newer Adrenalin, RX_VK_UNEVEN_BC=0) are Windows-shaped. For Linux/RADV, mesh_shaders=2 is the one thing that worked here, and the kernel ring-timeout signature above is a quick way for a reporter to confirm they have this bug rather than a different one.
    3. The compute path's first build being 3.3x the pipelines is worth a note next to the setting, so handheld users do not abandon it mid-build.

    Happy to run further tests on this hardware, including with RX_VK_CHECKPOINTS=1 or a debug build. The install has been removed since, but I can rebuild it if a fresh hookshot_log.txt or a devcoredump would help.

    Appendix: the kernel log around the hang

    journalctl -k for the crashed boot, from the first amdgpu message to the end of the recovery. Pasted rather than attached because it is short.

    Nov 11 22:43:32.968156 bazzite kernel: amdgpu 0000:04:00.0: Dumping IP State
    Nov 11 22:43:32.969330 bazzite kernel: amdgpu 0000:04:00.0: Dumping IP State Completed
    Nov 11 22:43:32.969670 bazzite kernel: amdgpu 0000:04:00.0: [drm] AMDGPU device coredump file has been created
    Nov 11 22:43:32.969913 bazzite kernel: amdgpu 0000:04:00.0: [drm] Check your /sys/class/drm/card1/device/devcoredump/data
    Nov 11 22:43:32.970182 bazzite kernel: amdgpu 0000:04:00.0: ring gfx_0.0.0 timeout, signaled seq=504077, emitted seq=504079
    Nov 11 22:43:32.970457 bazzite kernel: amdgpu 0000:04:00.0:  Process hookshot pid 37113 thread sead::MainThrea pid 37119
    Nov 11 22:43:32.970701 bazzite kernel: amdgpu 0000:04:00.0: Starting gfx_0.0.0 ring reset
    Nov 11 22:43:33.143226 bazzite kernel: amdgpu 0000:04:00.0: Ring gfx_0.0.0 reset failed
    Nov 11 22:43:33.143687 bazzite kernel: amdgpu 0000:04:00.0: GPU reset begin!. Source:  1
    Nov 11 22:43:33.274266 bazzite kernel: amdgpu 0000:04:00.0: MODE2 reset
    Nov 11 22:43:33.284168 bazzite kernel: amdgpu 0000:04:00.0: GPU reset succeeded, trying to resume
    Nov 11 22:43:33.284603 bazzite kernel: amdgpu 0000:04:00.0: [drm] PCIE GART of 1024M enabled (table at 0x000000F57FC00000).
    Nov 11 22:43:33.284832 bazzite kernel: amdgpu 0000:04:00.0: PSP is resuming...
    Nov 11 22:43:33.307185 bazzite kernel: amdgpu 0000:04:00.0: reserve 0xa00000 from 0xf57e000000 for PSP TMR
    Nov 11 22:43:34.145324 bazzite kernel: amdgpu 0000:04:00.0: SMU is resuming...
    Nov 11 22:43:34.145882 bazzite kernel: amdgpu 0000:04:00.0: SMU is resumed successfully!
    Nov 11 22:43:34.146248 bazzite kernel: amdgpu 0000:04:00.0: kiq ring mec 2 pipe 1 q 0
    Nov 11 22:43:34.159528 bazzite kernel: amdgpu 0000:04:00.0: [drm] DMUB hardware initialized: version=0x03000013
    Nov 11 22:43:34.279156 bazzite kernel: amdgpu 0000:04:00.0: [drm] Failed to enable ASSR, DTM TA is not initialized.
    Nov 11 22:43:34.334154 bazzite kernel: [drm] Failed to add display topology, DTM TA is not initialized.
    Nov 11 22:43:34.334240 bazzite kernel: amdgpu 0000:04:00.0: ring gfx_0.0.0 uses VM inv eng 0 on hub 0
    Nov 11 22:43:34.334669 bazzite kernel: amdgpu 0000:04:00.0: ring gfx_0.1.0 uses VM inv eng 1 on hub 0
    Nov 11 22:43:34.334917 bazzite kernel: amdgpu 0000:04:00.0: ring comp_1.0.0 uses VM inv eng 4 on hub 0
    Nov 11 22:43:34.335174 bazzite kernel: amdgpu 0000:04:00.0: ring comp_1.1.0 uses VM inv eng 5 on hub 0
    Nov 11 22:43:34.335413 bazzite kernel: amdgpu 0000:04:00.0: ring comp_1.2.0 uses VM inv eng 6 on hub 0
    Nov 11 22:43:34.335649 bazzite kernel: amdgpu 0000:04:00.0: ring comp_1.3.0 uses VM inv eng 7 on hub 0
    Nov 11 22:43:34.335882 bazzite kernel: amdgpu 0000:04:00.0: ring comp_1.0.1 uses VM inv eng 8 on hub 0
    Nov 11 22:43:34.336118 bazzite kernel: amdgpu 0000:04:00.0: ring comp_1.1.1 uses VM inv eng 9 on hub 0
    Nov 11 22:43:34.336373 bazzite kernel: amdgpu 0000:04:00.0: ring comp_1.2.1 uses VM inv eng 10 on hub 0
    Nov 11 22:43:34.336605 bazzite kernel: amdgpu 0000:04:00.0: ring comp_1.3.1 uses VM inv eng 11 on hub 0
    Nov 11 22:43:34.336893 bazzite kernel: amdgpu 0000:04:00.0: ring kiq_0.2.1.0 uses VM inv eng 12 on hub 0
    Nov 11 22:43:34.337204 bazzite kernel: amdgpu 0000:04:00.0: ring sdma0 uses VM inv eng 13 on hub 0
    Nov 11 22:43:34.337432 bazzite kernel: amdgpu 0000:04:00.0: ring vcn_dec_0 uses VM inv eng 0 on hub 8
    Nov 11 22:43:34.337663 bazzite kernel: amdgpu 0000:04:00.0: ring vcn_enc_0.0 uses VM inv eng 1 on hub 8
    Nov 11 22:43:34.337876 bazzite kernel: amdgpu 0000:04:00.0: ring vcn_enc_0.1 uses VM inv eng 4 on hub 8
    Nov 11 22:43:34.338099 bazzite kernel: amdgpu 0000:04:00.0: ring jpeg_dec uses VM inv eng 5 on hub 8
    Nov 11 22:43:34.338349 bazzite kernel: amdgpu 0000:04:00.0: GPU reset(2) succeeded!
    Nov 11 22:43:34.338635 bazzite kernel: amdgpu 0000:04:00.0: [drm] device wedged, but no recovery needed
    Nov 11 22:43:36.246151 bazzite kernel: input: Microsoft X-Box 360 pad 0 as /devices/virtual/input/input35
    Nov 11 22:44:05.145208 bazzite kernel: show_signal_msg: 71 callbacks suppressed
    Nov 11 22:44:05.145253 bazzite kernel: _run[35189]: segfault at 3bd9dfa0 ip 000000003bd9dfa0 sp 00007fc335335408 error 15 likely on CPU 2 (core 2, socket 0)
    Nov 11 22:44:05.145267 bazzite kernel: Code: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 80 00 00 00 00 00 00 00 81 00 00 00 00 00 00 00 <40> 13 dc 3b 00 00 00 00 38 eb 0d 82 c3 7f 00 00 80 a1 a2 04 00 00
    Nov 11 22:44:06.560561 bazzite kernel: fbcon: Taking over console
    Nov 11 22:44:07.144166 bazzite kernel: amdgpu 0000:04:00.0: [drm] Failed to enable ASSR, DTM TA is not initialized.
    Nov 11 22:44:07.211172 bazzite kernel: [drm] Failed to add display topology, DTM TA is not initialized.
    Nov 11 22:44:07.211242 bazzite kernel: Console: switching to colour frame buffer device 240x67
    Nov 11 22:44:08.662404 bazzite kernel: audit: type=1305 audit(1794451448.660:4890): op=set audit_pid=0 old=1189 auid=4294967295 ses=4294967295 subj=system_u:system_r:auditd_t:s0 res=1
    Nov 11 22:44:08.668295 bazzite kernel: audit: type=1131 audit(1794451448.666:4891): pid=1 uid=0 auid=4294967295 ses=4294967295 subj=system_u:system_r:init_t:s0 msg='unit=auditd comm="systemd" exe="/usr/lib/systemd/systemd" hostname=? addr=? terminal=? res=success'
    Nov 11 22:44:08.668411 bazzite kernel: audit: type=1131 audit(1794451448.666:4892): pid=1 uid=0 auid=4294967295 ses=4294967295 subj=system_u:system_r:init_t:s0 msg='unit=systemd-tmpfiles-setup comm="systemd" exe="/usr/lib/systemd/systemd" hostname=? addr=? terminal=? res=success'
    
  13. hclaria commented on Nov 12, 2026

    @hclaria
    Contributor

    Steam Deck OLED: @camilobt's readback finding tested in play. It holds, and the game is clearly better with it

    @camilobt I tested your lead on a second Deck, in play with the camera moving. It is solid: this is the biggest frame-rate improvement I have had on the Deck so far.

    Setup. Steam Deck OLED (Van Gogh 0x1435, 16 GB), SteamOS 3.9.5, kernel 7.2.7-valve2, Mesa 26.2.0-devel (RADV). Game built from main at c544828f. Renderer: c544828f with #2789, #2644, #2774, #2731, #2767 and #2649 merged, with and without the test patch below. Switches and memory settings as in my comment above.

    The same pair is here. Two ranges of 0x3980 bytes, 0x5000 apart, read in turn (0x113b1c3000 / 0x113b1c8000 in one area). Without the patch, 444 of 3,000 readbacks waited for their write-back, 4.42 ms each.

    What I ran. A small test patch around invalidate() in gpu/vk/vk_replay.cpp: RX_VK_READBACK_LATE=newest skips the wait when the range's write-back value is the newest value handed out (v >= fence() on entry), which is your "read right after the submission that writes it". Unlike RX_VK_NO_READBACK_WAIT=1 it keeps wb_pages_note, memory_note_readback and the catch-up, and a range whose writer is an older submission still waits.

    One difference from your v2.1.0 numbers: on this build the large ranges (+0x9b040, +0x8b040, +0x1b580) also have the newest submission as their writer (75 of 75 calls each), so the rule lets them through as well. Between 440 and 1,100 of 3,000 readbacks, depending on the area, are then read without their wait, and what is left waits 0.02 to 0.2 ms.

    In play, not headless. One session of 12 minutes with the patch (several areas, camera moving), against the session before it on the same renderer without the patch. Windows of 120 presents, grouped by draws a frame:

    draws a frame without (windows) RX_VK_READBACK_LATE=newest (windows)
    1,000 to 1,500 21.3 fps (3) 29.8 fps (91)
    1,500 to 2,000 17.8 fps (16) 21.7 fps (15)
    2,000 to 2,400 18.2 fps (34) 18.9 fps (9)
    2,400 to 2,700 17.4 fps (10) 19.8 fps (11)
    over 2,700 15.2 fps (9) 14.7 fps (8)
    • I saw no exposure flash and no flicker, with the camera moving. No GPU hang, no device lost.
    • The gain is in light and medium scenes. Over 2,000 draws it is small, and over 2,700 there is none: the GPU is the limit there.
    • Four windows under 12 fps against one, all on arriving in an area while pages came back from swap (the memory side of my comment above). I cannot tell yet whether the patch plays a part in that.

    Limits. One Deck, one session each, two different routes, few windows in some rows. I do not know what the pair holds either. The texture half of invalidate() (the 1x1 luminance textures of #2462) is not touched.

    The finding is yours, so the PR is yours to open if you want one. Take the patch below as a starting point or leave it. I am glad to run any build of yours on this OLED and report the numbers.

    The test patch (gpu/vk/vk_replay.cpp, on main c544828 with the six PRs above)
    diff --git a/gpu/vk/vk_replay.cpp b/gpu/vk/vk_replay.cpp
    index f1e94d13..d98a1c58 100644
    --- a/gpu/vk/vk_replay.cpp
    +++ b/gpu/vk/vk_replay.cpp
    @@ -2470,6 +2470,78 @@ bool readback_wait_value(uint64_t v) {
                  (unsigned long long)q_values.load());
         return false;
     }
    +/// (deck test) RX_VK_READBACK_LATE=newest[,<hex size>...]: a readback that does not wait for its write-back. `newest`:
    +/// the range is read right after the submission that writes it (its value is the newest one handed out), so the game
    +/// cannot count on that work being done; a size: the ranges of that many bytes. The CPU reads what guest memory holds.
    +/// OoT on a Steam Deck: one pair of 0x3980-byte ranges is 94 % of the readbacks' wait (issue 1000, camilobt)
    +bool readback_late(uint64_t size, bool newest) {
    +    struct Cfg {
    +        bool newest = false;
    +        std::vector<uint64_t> sizes;
    +    };
    +    static const Cfg cfg = [] {
    +        Cfg c;
    +        const char* e = getenv("RX_VK_READBACK_LATE");
    +        for (const char* p = e; p && *p;) {
    +            if (!strncmp(p, "newest", 6)) c.newest = true;
    +            else if (const uint64_t n = strtoull(p, nullptr, 16)) c.sizes.push_back(n);
    +            p = strchr(p, ',');
    +            if (p) p++;
    +        }
    +        if (e) logf("vk: RX_VK_READBACK_LATE=%s: newest %d, %d sizes", e, (int)c.newest, (int)c.sizes.size());
    +        return c;
    +    }();
    +    if (newest && cfg.newest) return true;
    +    for (const uint64_t s : cfg.sizes)
    +        if (s == size) return true;
    +    return false;
    +}
    +/// (deck test) the readbacks by range, for the log every 3000 calls: how many waited and for how long, how many had the
    +/// newest submission as their writer, how many were read without their wait
    +struct RbRange {
    +    uint64_t gpu, size;
    +    int64_t calls, waits, ns, newest, late;
    +};
    +std::mutex rb_ranges_mu;
    +std::vector<RbRange> rb_ranges;
    +void rb_range_note(uint64_t gpu, uint64_t size, bool waited, int64_t ns, bool newest, bool late) {
    +    std::lock_guard lk(rb_ranges_mu);
    +    for (auto& r : rb_ranges)
    +        if (r.gpu == gpu && r.size == size) {
    +            r.calls++;
    +            r.waits += waited;
    +            r.ns += ns;
    +            r.newest += newest;
    +            r.late += late;
    +            return;
    +        }
    +    if (rb_ranges.size() < 256) rb_ranges.push_back({gpu, size, 1, waited, ns, newest, late});
    +}
    +void rb_ranges_log() {
    +    static std::atomic<int> logged{0};
    +    std::vector<RbRange> top;
    +    {
    +        std::lock_guard lk(rb_ranges_mu);
    +        top = rb_ranges;
    +        rb_ranges.clear();
    +    }
    +    if (logged++ >= 400) return;
    +    std::sort(top.begin(), top.end(), [](const RbRange& a, const RbRange& b) {
    +        return a.ns + a.late * 5000000 > b.ns + b.late * 5000000;
    +    });
    +    int64_t late = 0;
    +    for (const auto& r : top) late += r.late;
    +    std::string s;
    +    for (size_t i = 0; i < top.size() && i < 8; i++) {
    +        char b[200];
    +        snprintf(b, sizeof b, " | %#llx +%#llx: %lld calls, %lld waited %.1f ms, %lld newest, %lld late",
    +                 (unsigned long long)top[i].gpu, (unsigned long long)top[i].size, (long long)top[i].calls,
    +                 (long long)top[i].waits, top[i].ns / 1e6, (long long)top[i].newest, (long long)top[i].late);
    +        s += b;
    +    }
    +    logf("vk: readback ranges at present %lld: %d ranges, %lld read late%s", (long long)presents_now, (int)top.size(),
    +         (long long)late, s.c_str());
    +}
     void invalidate(uint64_t gpu, uint64_t size) {
         static const bool log_inv = env_on("RX_VK_LOG_INVALIDATE");
         static std::atomic<int> logged{0};
    @@ -2551,7 +2623,9 @@ void invalidate(uint64_t gpu, uint64_t size) {
                     if (!part_rec.empty()) l_sent = part_rec.rbegin()->second;
                     vkGetSemaphoreCounterValue(dev.device, rec_timeline, &l_done);
                 }
    -            if (v > completed()) {
    +            const bool pending = v > completed(), newest = pending && v >= f0;
    +            const bool late = pending && readback_late(size, newest);   // (deck test: read without the wait)
    +            if (pending && !late) {
                     int ok = 0;
                     // (and not when a part after it reads guest memory in place, on a card whose pools are not all mirrored
                     // or whose mirrors take pages no capture holds: the game, let go after the first part, would write what
    @@ -2579,7 +2653,11 @@ void invalidate(uint64_t gpu, uint64_t size) {
                              (unsigned long long)f0, (unsigned long long)c0, (int)behind0, (unsigned long long)l_part,
                              (unsigned long long)l_last, (unsigned long long)whole, (unsigned long long)l_sent, (unsigned long long)l_done);
                 }
    +            rb_range_note(gpu, size, w,
    +                          w ? std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::steady_clock::now() - w0).count() : 0,
    +                          newest, late);
                 if (++calls % 3000 == 0) {
    +                rb_ranges_log();
                     const int64_t n = waited.exchange(0);
                     logf("vk: readbacks of GPU results: %lld of 3000 waited for their write-back, %.2f ms each; %lld looked at the "
                          "stopped queues first, %lld waited for their submission's last part (guest memory read in place after theirs)",
  14. zackfairccff-bot commented on Nov 12, 2026

    @zackfairccff-bot

    Steam Deck LCD — ~35 configuration tests, v2.1.0 results, memory exhaustion and comparison with v2.0.14

    I'm sharing my results after approximately 35 configuration attempts on a Steam Deck LCD, hoping to help other users avoid repeating experiments that haven't produced sustainable gameplay on my device.

    Hardware and software

    • Steam Deck LCD, SteamOS / Linux
    • AMD Custom GPU 0405, RADV VANGOGH
    • Vulkan 1.4, driver "0x6801063"
    • 14,798 MiB system RAM reported by Linux
    • 8,422 MiB swap
    • Hookshot v2.1.0, source "e561602e"
    • Logged swapchain: 1280×720
    • Mesh shader mode: Automatic (native)

    General configuration tested

    One of my main configurations was:

    HOS_HANDHELD=1
    RX_VK_MESH_OPT=1
    RX_VK_PER_QUEUE_FENCES=0
    RX_VK_WAIT_GPU=0
    RX_VK_BATCH_MAX=4
    RX_VK_CHECKPOINTS=1

    I also experimented with "RADV_PERFTEST=cswave32", "RX_VK_NO_READBACK_WAIT=1", texture/VRAM limits, LOW_SPEC, mesh fallback and related Vulkan settings across different runs.

    These were exploratory tests, not controlled single-variable A/B benchmarks. I cannot attribute every FPS change to a specific flag.

    An important detail: one log explicitly reports that "RADV_PERFTEST" was ignored when placed inside "hookshot_env.txt". This variable must be supplied through the process environment to be effective.

    Results — v2.1.0

    Test| Observations
    33| Gameplay fell below 20 FPS and eventually recorded approximately 0.3 FPS
    34| Approximately 25 FPS in a lighter scene (~800 draws/frame), followed by major degradation and eventually near-zero FPS
    35| Short periods above 20 FPS in lighter scenes; gameplay later around 5–12 FPS

    Test 34 was particularly revealing.

    The external memory monitor showed:

    • Available RAM dropping to approximately 90–115 MiB
    • Swap reaching 8,422 / 8,422 MiB
    • Severe and sustained FPS deterioration

    The game became effectively unplayable. The log ended after window closure, with exit code 0; this should not be described as a confirmed Vulkan device-loss crash.

    In test 35, available RAM dropped from 6,283 MB to 1,409 MB. An external safety script terminated the process at its threshold. The script's swap-reading field was broken, so this test does not establish swap exhaustion.

    The game also suffered from inconsistent frame pacing and graphics problems, including an incorrectly rendered Link in the equipment menu under some experimental configurations.

    Comparison: v2.0.14 versus v2.1.0

    I also installed and tested v2.0.14.

    On my specific Steam Deck LCD, v2.1.0 gave me a better overall experience than v2.0.14.

    Version 2.0.14 displayed Link correctly in the equipment menu, but several other graphics issues returned, and the overall gameplay experience was worse for me.

    This is my direct experience, not a controlled same-scene FPS comparison. I'm aware that other Steam Deck LCD users have measured better results on v2.0.14, so I do not claim that v2.1.0 is universally faster.

    Regarding camilobt's readback findings

    The recent tests showing approximately +16% with "RX_VK_NO_READBACK_WAIT=1" are particularly interesting because I have also experimented with this setting.

    However, my runs did not isolate it in a reliable A/B comparison. Moreover, the severe system-memory pressure remained a separate obstacle.

    I would be cautious about presenting this flag as a universal fix, given the evidence that some of the skipped readbacks contain changing data.

    What I think other Steam Deck users should know

    • Menu FPS and brief peaks of 25–30 FPS do not establish sustained playable performance.
    • Reducing rendering costs may help, but it has not solved my severe memory-pressure problem.
    • Changing many flags simultaneously makes it difficult to determine what actually improved performance.
    • A configuration that avoids a GPU crash may still have missing or incorrect geometry.
    • My results do not prove a memory leak, but the RAM/swap pressure is directly documented.
    • The most valuable next step would be controlled testing of newer Vulkan synchronization fixes and memory-management changes on the Steam Deck LCD.

    I'm attaching the original logs from tests 33–35, including memory monitoring for tests 34 and 35.

    I hope these results help narrow down the remaining problems and avoid redundant testing.

    My current conclusion: Hookshot v2.1.0 can enter gameplay on my Steam Deck LCD and occasionally achieve around 25 FPS in less demanding scenes, but I have not yet reproduced stable, visually correct gameplay at - consistently acceptable FPS.!

    Hookshot_SteamDeck_LCD_logs_33_35.zip

  15. dm451 commented on Nov 12, 2026

    @dm451

    Linux data point on a 16 GB AMD handheld: crashes and stutter in Hyrule Field, plus a frame-rate finding.

    Setup: ROG Ally (Z1 Extreme, Radeon 780M), 16 GB with a 4 GB UMA frame buffer (11.6 GB usable), Mesa RADV, kernel 7.2, Hookshot v2.1.0 built from source in a container. My build carries a few local renderer changes; see the caveat at the end.

    Crashes (out of memory)

    • With only the distribution's zram swap (5.7 GB), the kernel's OOM killer ended the game in Hyrule Field, 3 times in one session. Texture data compresses poorly, so zram fills up without freeing much.
    • Loading the field, the guest memory peaks around 7.3 GB. On top of that are the renderer (1–2 GB) and the imported GPU pools (2.2–2.7 GB of GTT).
    • A 16 GB swap file behind zram (zram priority 100, file 50) stopped the crashes.

    Stutter (what is left)

    • Each load pushes 4–8 GB to swap, and the game stalls when it touches it again. The stalls show in the log as a frame's uploads took N ms; one 5 MB texture took 19.5 s.
    • In a 3-minute scripted walk from the same Hyrule Field save, zram first with the disk only as overflow stalled far less than disk first: lowest fps 24–27 vs 6–18, longest upload stall about 0.2 s vs about 1 s.
    • Did not help:
      • lower RX_VK_IMPORT_HIGH/RX_VK_IMPORT_LOW: there were no idle imports to give back;
      • HOS_MEMORY_MB below 9809: every value from 6656 to 9216 fails at boot ("memory pool without storage", or an indirect call into nothing).

    Frame rate: the readback wait in invalidate()

    • On v2.1.0 the exposure-meter readback often arrived mid-replay and waited about 15 ms for the GPU to finish the frame just submitted. That put frames in series with the GPU: about 32 fps in Hyrule Field.
    • With RX_VK_NO_READBACK_WAIT=1 it ran at 42–45 fps. Every-frame captures in Hyrule Field (500 frames) and Kokiri Forest (600) showed no white flashes or other glitches.
    • Caveat: my build submits the replay from a separate ordered submit thread, which can change when the readback lands. I have not reproduced this on stock v2.1.0, and I see main has since reworked the readback settle and added RX_VK_READBACK_WAIT_MS. I'll retest on the next release and report back.
  16. jspenaq commented on Nov 12, 2026

    @jspenaq

    Steam Deck LCD / SteamOS 3.9.5 — Persistent 60 → 6 FPS degradation during Great Deku Tree intro (Compute fallback)

    I'd like to contribute some instrumented Steam Deck LCD results to complement the recent investigations into RADV hangs, memory pressure, and sustained performance degradation.

    Hardware / environment

    • Steam Deck LCD — AMD Custom GPU 0405 / RADV VANGOGH
    • SteamOS 3.9.5, KDE Wayland
    • Kernel 7.2.7-valve2, Mesa 26.2.0-devel
    • Hookshot v2.1.0 executable with a Vulkan backend containing the fix(vk): RX_VK_FRAME_LOG writes its frame times in a headless run too #2354 frame-logging backport
    • Windowed at 960×600, frame generation disabled
    • RX_VK_PRECOMPILE=0
    • RX_VK_MESH_NOCULL=2c56dc1006942f74
    • RX_VK_FORCE_MESH_FALLBACK=1

    Compute was explicitly confirmed in the startup log:

    mesh shaders row: Compute (the compute path)
    

    Executable SHA-256:
    6269ad78e3e97cdda953d3612b2c99b5c71d328812c97f234ae3561d7d7ceb51

    Vulkan backend SHA-256:
    b63023499958a40436bbc1276e17ef3ab2155434bc9aec9afc79bac28da534b0

    1. Observed behavior

    During a manually controlled, windowed session, the Zelda title/logo sequence rendered smoothly at approximately 60 FPS.

    The severe slowdown began around the transition into the Great Deku Tree opening scene. This was observed directly on the Steam Deck display.

    Unlike occasional shader-compilation stutters, the performance degradation was persistent: once it dropped to approximately 6 FPS, it did not recover during the remaining five-plus minutes of observation.

    2. Frametime evidence

    The session produced 21,023 frame-log records with consecutive frame indices and monotonic timestamps.

    Frame-log interval Effective FPS Frametime p99
    30–320 s 59.80 22.17 ms
    320–330 s ~10.2 ~1,055 ms
    330–643 s 6.19 ~281 ms

    The major transition is visible around frame 19032:

    frame=19032
    time_s=322.6075
    frametime_ms=1465.284
    

    This was followed by additional long intervals, including approximately 907 ms and 1,056 ms, before settling into sustained frame times around 150–180 ms.

    The frame-log clock started approximately 8.47 seconds after process launch.

    These measurements are CPU-side present intervals, not GPU execution or physical scanout timings.

    3. Memory pressure and termination

    The QA supervisor monitored process and system resources throughout the session.

    After 652.8 seconds, it stopped Hookshot because system MemAvailable dropped below the predefined 1 GiB safety threshold.

    • Compute fallback remained enabled.
    • No new GPU reset was reported during this execution.
    • Hookshot exited with code 0 following the controlled supervisor stop.
    • This was not an OOM kill or a confirmed Vulkan crash.

    The persistent slowdown and low available memory are correlated observations; I have not established whether memory pressure is the cause, a consequence, or a separate symptom.

    4. Separate GPU reset observation

    In an earlier windowed attract/demo test using the same executable/backend, RADV reported:

    ring gfx_0.0.0 timeout
    GPU reset (MODE2)
    VK_ERROR_DEVICE_LOST
    

    That run also exhibited severe frametime degradation and increasing memory usage.

    However, the effective mesh execution path was not independently confirmed in that earlier run, and the two sessions followed different scene sequences and cache conditions.

    These are not controlled Native-vs-Compute A/B results.

    5. What might be worth investigating

    Given the recent findings in this thread, I am particularly interested in distinguishing between:

    • Excessive renderer allocations or memory reclaim/swap activity during scene loading.
    • A Compute workload becoming unexpectedly expensive after the scene transition.
    • Readback/synchronization stalls or related pipeline behavior.
    • CPU/GPU contention under the Steam Deck's shared memory and power constraints.

    The Great Deku Tree transition is a promising reproduction point, but the visual observation was not independently timestamped by the supervisor, so the correlation with frame 19032 is approximate.

    Question for maintainers/contributors: Are there specific program IDs, Vulkan timing options, memory counters, or recent main changes you would recommend checking first to distinguish these mechanisms on RADV Van Gogh?

    I can provide sanitized frame logs, memory/system telemetry, and kernel excerpts. I can also test a pinned revision in a separate installation, keeping the existing reproducible baseline intact.

    Evidence: frame-pacing chart and archived CSV/run metadata attached.

    frames.csv

    hookshot-deck-lcd-frame-pacing

    Thanks for the ongoing Linux and Steam Deck work!

  17. zackfairccff-bot commented on Nov 13, 2026

    @zackfairccff-bot

    Steam Deck LCD / RADV VANGOGH — Hookshot v2.2.0 RC: Testing Session (Tests 1–6)

    Hi @NotRexed and everyone,

    Following up on my previous Steam Deck LCD reports, here are the results from my latest testing session using the v2.2.0 release candidate.

    Good news: I can launch the game, explore Kokiri Forest and control Link. My latest run, Test 6, produced the best results of this session, including several gameplay intervals around 25–30 FPS.

    Unfortunately, severe stuttering remains, particularly in demanding scenes, indoor areas and transitions. The game is not consistently playable yet.

    System

    • Steam Deck LCD / SteamOS
    • AMD Custom GPU 0405 — RADV VANGOGH
    • Vulkan 1.4, Mesa 26.2.0-devel
    • 14,798 MiB RAM / 8,422 MiB swap
    • AppImage: "Hookshot-v2.2.0-rc.1-5-g4dd972a8-x86_64.appimage"
    • Runtime: "v2.2.0-rc.1-6-ge0e60920" (source "13f4191b")
    • Mesh shader mode: compute fallback

    Test results — This session only

    Test| Configuration / experiment| Result
    1| "NO_READBACK_WAIT=1" plus GPU synchronization adjustments| Severe gameplay slowdowns, sometimes approaching 0.1 FPS.
    2| Reduced graphics scale, parallel processing and batch polling| Unsuccessful. No comparable log captured.
    3| Mesh optimization, NOCULL and texture/VRAM limits| Kokiri rendered, but gameplay remained slow and unstable.
    4| Added "FRAMEGEN=0" and resolution override, using FSR 3.1| Best subjective result among the initial tests. Outdoor areas improved, but interiors remained very slow.
    5| Disabled FSR and switched the upscaler to Off| Significantly worse, including drops to 0.1 FPS.
    6| Returned to Test 4 configuration and recorded a longer session| Best documented result: several gameplay intervals around 25–30 FPS, but major slowdowns still occur.

    These were exploratory tests, not identical-scene A/B benchmarks.

    Best configuration — Tests 4 and 6

    cd "/run/media/deck/f9b867d3-571f-493b-a22e-91206d74cdbc/GAMES/hookshot atualizado" &&
    HOS_HANDHELD=1
    HOS_OUTPUT_RES=1280x800
    RX_VK_FRAMEGEN=0
    RX_VK_MESH_OPT=1
    RX_VK_MESH_NOCULL=2c56dc1006942f74
    RX_VK_VRAM_MB=2000
    RX_VK_TEX_MAX_MB=128
    RX_VK_TEX_MAX_DIM=1024
    ./Hookshot-v2.2.0-rc.1-5-g4dd972a8-x86_64.appimage

    FSR 3.1 upscaling was enabled. The logs do not independently establish that "HOS_OUTPUT_RES" changed the game's internal picture resolution.

    Test 6 — Performance measurements

    Log: "hookshot_log(20261113-004346).txt"

    This run provides the strongest evidence of improvement so far.

    • Approximately 15,877 frames presented before the end of the captured session.
    • Median 23.8 FPS across 93 sampled windows with at least 500 draws per frame.
    • Approximately 25–30 FPS in multiple moderately demanding gameplay samples.
    • Heavier samples with at least 1,000 draws per frame had a median of 19.4 FPS.
    • Significant drops remained, including 4.5 FPS and 9.1 FPS in demanding samples.
    • Title/menu performance could reach approximately 60 FPS.
    • Shader preparation still required 77.8 seconds, despite existing caches.

    The draw-count filters are approximate workload indicators, not controlled scene comparisons.

    Representative log excerpts:

    vk: start-up: the shaders took 77.8 s

    vk: 14520 presents, 27.5 fps; a frame: 654 draws

    vk: 14880 presents, 30.4 fps; a frame: 601 draws

    vk: 15000 presents, 29.6 fps; a frame: 601 draws

    vk: 2520 presents, 4.5 fps; a frame: 1303 draws

    vk: 9960 presents, 9.1 fps; a frame: 2610 draws

    display: window closed, exiting

    The session ended after the window was closed normally. No "VK_ERROR_DEVICE_LOST" was recorded in this log.

    However, multi-second stalls, expensive texture uploads, texture retirement and virtual-core scheduling delays remain visible.

    Questions for developers

    1. Are there recent fixes targeting CPU-side rendering overhead or compute-mesh fallback performance on RADV VANGOGH?
    2. Could synchronization or texture uploads explain the major FPS differences between lighter and heavier areas?
    3. Is approximately 78 seconds of shader preparation expected when populated caches already exist?
    4. Is the difference between the AppImage filename and runtime build ID expected?

    I can share the complete logs and additional footage from Kokiri Forest.

    Thanks to @NotRexed, @KewinBR and everyone investigating Steam Deck compatibility.

    This is meaningful progress, but sustained gameplay performance is still the main issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions