Skip to content

Random 20 FPS lock and Vulkan renderer slowdowns #50

Closed

Description

@StonedEdge

What happens

I am experiencing occasional, seemingly random 20 FPS locks / severe performance slowdowns when using the Vulkan renderer.

The game can run normally at full speed, but at seemingly random times the performance suddenly drops and becomes locked around 20 FPS. The slowdown can persist even after the event that appears to trigger it has passed. It seems to happen a lot with video cutscenes.

This does not appear to be normal shader compilation stutter, as the game can be running normally and then suddenly enter this low-FPS state during gameplay.

Current behaviour

  • Game normally runs at full speed.
  • At seemingly random points during gameplay, performance suddenly drops to around 20 FPS.
  • Once this happens, the game appears to remain locked/limited around 20 FPS rather than briefly stuttering.
  • Vulkan appears to be particularly affected.
  • Restarting the game appears to restore normal performance.
  • The issue does not seem to consistently occur in the same location or during the same action.

Expected behaviour

The game should maintain a consistent frame rate during gameplay, without randomly entering a persistent ~20 FPS state.

Steps to reproduce

  1. Launch Zelda: Wind Waker HD Recompiled using the Vulkan renderer.
  2. Play normally for some time.
  3. At some seemingly random point, performance may suddenly drop to approximately 20 FPS with the GPU usage not rising or CPU usage not increasing
  4. Continue playing and observe that the low frame rate persists.
  5. Restarting the game restores normal performance.

Save state (link), or why you can't add one

No save state as not relevant

Version

v0.2.2

Platform

Linux (arm64)

Hardware

Mali G610 RK3588

Renderer

Vulkan

Logs

No response

Activity

  1. lschaupp commented on Oct 7, 2026

    @lschaupp
    Contributor

    Thanks for the detailed report, @StonedEdge. v0.2.4 changes exactly the part of the Vulkan renderer that is most likely behind this: the render thread no longer waits for the GPU after every frame on desktop, which in our load tests turned 35–40 fps drops into a steady ~59 fps.

    Could you try v0.2.4 and tell us whether the 20 fps lock still happens? If it does, two things would help a lot:

    • while it is stuck at 20 fps, open the overlay → Graphics → Copy performance report and paste it here;
    • your platform, GPU and driver, and whether it starts right after a video cutscene (that would point to a presentation mode that doesn't switch back after the video).
  2. StonedEdge commented on Oct 7, 2026

    @StonedEdge
    Author

    Still happens - tested with v0.2.5. Log at the title when drops to exactly 20fps

    Wind Waker HD performance report: renderer Vulkan, host SDL, 30 fps, internal scale 1.0x
    [prof] frame 360: 120 frames (0 hold), 39.34 ms/frame, 25.4 swaps/s, 25.4 logic steps/s; render thread CPU 23.88 ms/frame, in ops 26.73 ms/frame, idle (waiting for commands) 15.33 ms/frame
    [prof] ops ms/frame: regs 2.40 (30480/frame) draw 23.06 (2621/frame) clear 0.08 (8/frame) scan 0.03 (2/frame) invalidate 0.26 (203/frame) flush 0.16 (2/frame) drawdone 0.00 (2/frame) swap 0.36 (1/frame) other 0.38 (24/frame)
    [prof] draw phases ms/frame (1 draw in 64 sampled): shader 3.91 indices 2.17 targets 0.53 pipeline 2.68 uniforms 2.95 textures 1.42 descriptors 0.96 pass 0.66 record 0.68 vertex 5.44 submit 1.55; untracked 0.12; 8.80 us/draw
    [prof] render thread waits ms/frame: GPU 0.14 (0.04/frame) acquire 0.01 present 0.01; game thread waits for the render thread: DrawDone 12.43 ms (2.00/frame)
    [prof] uploads MiB/frame 15.96; logic/30 fps frames: other 0.02 vertex 13.91 index 1.55 ubo 0.03 uniforms 0.42 texture 0.02
    [prof] unique guest MiB/frame (8 frames sampled): all 1.75; vertex 1.51 of 13.91 copied index 0.21 of 1.47 copied ubo 0.03 of 0.03 copied
    [prof] draw classes: 4.8% same registers, 50.3% only buffer pointers/ALU constants, 44.9% other (2621 draws/frame); top other writes/frame: SQ_PGM(A225) 1030 CB_BLEND/STENCILREF(A104) 594 CB_BLEND/STENCILREF(A10E) 538 SQ_PGM(A216) 301 SQ_PGM(A210) 301 DB/CB/PA control(A202) 193 DB/CB/PA control(A205) 177 PA_SC scissor(A08E) 163
    [prof] shader translations so far 1925: 360 new programs, 26 differ from an existing variant only in unused texture units/samplers; differing words vs. nearest variant: SPI_PS_INPUT_CNTL 1021 VTX_SEMANTIC 775 SPI_VS_OUT_ID 353 tex words (unused units) 146 CB_TARGET_MASK 36 CB_COLOR_INFO 23 sampler compare (unused) 10 fetch shader 6 CB_COLOR_CONTROL 6 DB_DEPTH_CONTROL 4
    
    
  3. lschaupp commented on Oct 7, 2026

    @lschaupp
    Contributor

    Thanks for the report, and sorry I missed your hardware at first: an RK3588 with a Mali-G610. "Drops to 20 fps, CPU and GPU usage don't go up, and only a restart fixes it" points to something sticky rather than plain load. Most likely the game's render or game thread ends up on one of the RK3588's slow Cortex-A55 cores (or a clock governor drops), often right after a low-load phase like a video cutscene, and stays there. Could you try:

    1. Start the game pinned to the fast A76 cores: taskset -c 4-7 ./data/bin/wwhd (or prefix your usual launch command with taskset -c 4-7).
    2. Optionally set the CPU governor to performance for the test.
    3. If it still locks to 20 fps, post the output of cat /sys/class/devfreq/*/cur_freq and the performance report while it's stuck.

    If taskset fixes it, we'll pin the game's main threads to the fast cores automatically on big.LITTLE chips. Separately, the next update cuts shader translation and pipeline creation by about 90%, which will help on this chip too.

  4. lschaupp commented on Oct 7, 2026

    @lschaupp
    Contributor

    v0.2.6 is out with the shader change mentioned above (about 90% fewer shader translations and pipelines, fewer stutters). Its performance report also starts with the version, OS, GPU and driver. Still very interested in whether taskset -c 4-7 stops the 20 fps lock.

  5. StonedEdge commented on Oct 7, 2026

    @StonedEdge
    Author

    Did not help. Still dips in certain areas, for example, as soon as I enter this room:

    Image
    Wind Waker HD v0.2.6 (ffb7a12), Armbian-unofficial 25.11.0-trunk noble, kernel 6.1.118-vendor-rk35xx, Mali-G610, driver 29.1.0, Vulkan 1.4.305
    performance report: renderer Vulkan, host SDL, 30 fps, internal scale 1.0x, buffer cache off, gyro cemuhook
    [prof] frame 22440: 120 frames (0 hold), 50.05 ms/frame, 20.0 swaps/s, 20.0 logic steps/s; render thread CPU 39.85 ms/frame, in ops 45.36 ms/frame, idle (waiting for commands) 10.21 ms/frame
    [prof] ops ms/frame: regs 2.61 (29519/frame) draw 38.99 (5803/frame) clear 0.09 (7/frame) scan 0.05 (2/frame) invalidate 2.43 (186/frame) flush 0.19 (2/frame) drawdone 0.00 (2/frame) swap 0.53 (1/frame) other 0.48 (23/frame)
    [prof] draw phases ms/frame (1 draw in 64 sampled): shader 2.11 indices 3.74 targets 1.15 pipeline 1.80 uniforms 5.91 textures 2.22 descriptors 1.13 pass 0.84 record 1.26 vertex 14.76 submit 3.80; untracked 0.26; 6.72 us/draw
    [prof] render thread waits ms/frame: GPU 0.00 (0.00/frame) acquire 0.02 present 0.01; game thread waits for the render thread: DrawDone 26.71 ms (2.00/frame)
    [prof] uploads MiB/frame 6.85; logic/30 fps frames: other 0.12 vertex 2.91 index 2.43 ubo 0.08 uniforms 1.32 texture 0.00
    [prof] unique guest MiB/frame (8 frames sampled): all 0.57; vertex 0.46 of 2.91 copied index 0.06 of 2.43 copied ubo 0.04 of 0.08 copied
    [prof] draw classes: 0.0% same registers, 84.0% only buffer pointers/ALU constants, 16.0% other (5803 draws/frame); top other writes/frame: SQ_PGM(A225) 885 CB_BLEND/STENCILREF(A104) 588 CB_BLEND/STENCILREF(A10E) 527 SQ_PGM(A216) 491 SQ_PGM(A210) 491 SQ_PGM(A217) 350 SQ_PGM(A21A) 336 SQ_PGM(A226) 282
    [prof] shader translations so far 1162: 1129 new programs, 0 differ from an existing variant only in unused texture units/samplers; differing words vs. nearest variant: SPI_PS_INPUT_CNTL 30 VTX_SEMANTIC 29 SPI_VS_OUT_ID 28 fetch shader 20 CB_TARGET_MASK 5 CB_COLOR_CONTROL 5 alpha test 4 tex words (used units) 1 tex words (unused units) 1 sampler compare (unused) 1
    
  6. lschaupp commented on Oct 7, 2026

    @lschaupp
    Contributor

    Thanks, and the new report header is already paying off. In that room your render thread needs about 40 ms per frame (5,800 draws, 6.7 µs each), so it misses even the 33 ms of a 30 fps frame, and vsync turns that into exactly 20 fps. GPU waits are 0, so it's purely the render thread. The biggest part is copying vertex data: 14.8 ms for only 2.9 MB per frame, which is very slow and points at the Mali driver's upload memory. Of those 2.9 MB only about 0.46 MB is actually new each frame.

    That is exactly what our buffer cache avoids: it keeps unchanged vertex, index and uniform data on the GPU instead of copying it every frame. It's on by default on macOS but still opt-in on Linux, because we haven't been able to test it there yet, so you'd be the first. Could you try two runs in that same room:

    1. A correctness check (slower on purpose):
      WWHD_VK_BUFFER_CACHE_VERIFY=1 WWHD_VK_CPU_ONLY_STATS=1 ./data/bin/wwhd
      
      and paste the [vulkan buffer cache] lines from the log (especially any VERIFY MISMATCH, which should never appear, plus the page write faults / protect failures counts).
    2. A normal run with the cache on:
      WWHD_VK_BUFFER_CACHE=1 ./data/bin/wwhd
      
      and the "Copy performance report" from the moment you enter that room.

    (Use your usual launch command with the variables in front. Alternatively, put WWHD_VK_BUFFER_CACHE=1 on a line in an env.txt next to your settings if your setup uses one.) If it helps as much as we expect, we'll turn the cache on by default on Linux too.

  7. StonedEdge commented on Oct 7, 2026

    @StonedEdge
    Author

    Ah, yes, adding that env variable has fixed everything.

  8. lschaupp commented on Oct 7, 2026

    @lschaupp
    Contributor

    Great news, thanks! That confirms it was the vertex copies through the Mali driver's upload memory. Before we turn the cache on by default on Linux, could you do one correctness run in that same room?

    WWHD_VK_BUFFER_CACHE_VERIFY=1 WWHD_VK_CPU_ONLY_STATS=1 ./data/bin/wwhd
    

    and paste the [vulkan buffer cache] lines from the log (we're looking for any VERIFY MISMATCH, which should never appear, and the page write faults / protect failures counts). A "Copy performance report" from a normal run with WWHD_VK_BUFFER_CACHE=1 in that room would also be nice, so we can see the new frame time. Until then, keep the variable set.

  9. lschaupp commented on Oct 8, 2026

    @lschaupp
    Contributor

    Closed for inactivity :)

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

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions