Repository navigation
Conversation
…nce textures Games frequently render tiny GPU-written textures (such as 1x1 RGBA32F auto-exposure luminance) and read them back every frame via texture_flush_to_guest(). Previously, every readback triggered a synchronous flush_texture() that drained the GPU queue or stalled the timeline. Implement a 4-slot host-cached ring buffer (AutoReadback) for small GPU-written textures (<= 64 KB, 1 level, 1 layer, 2D). When dirtied during a recording, texture_end_recording() records an asynchronous copy into the next ring slot with a transfer-to-host barrier. When texture_flush_to_guest() polls the texture, it reads directly from host-cached memory, waiting only on the slot timeline value when necessary rather than draining the queue. Co-Authored-By: erfboom <gabrielh.ltd@gmail.com>
Joey-Magnus
left a comment
There was a problem hiding this comment.
I ran the suite on this merged into main, since CI hasn't run here. Blocking issues:
- Crash: the fast path builds a
Flushwithout settingf.alloc(vk_texture.cpp:3274).flush_finishthen callsinvalidate_mapped(f.alloc, total)(:3056), and VMA dereferences a null allocation on the first readback served from the ring, with default settings. - Two tests fail:
test_fsr_image_retirement.py(no stub fortexture_end_recording) andtest_env_on.py(RX_VK_NO_AUTO_READBACKat:3261usesgetenv, notenv_on). - After the 250 ms timeout,
vk_timeline_wait(:3269) returns and the slot is read anyway, so the game can silently get a stale value. - The
texture_markedchange (:3439) also changes when parallel segments take the lock. - The description refers to TotK and BotW. Please include a measurement in OoT.
|
Thanks for the thorough review and catch! I've addressed all points in
|
…and add source test # Conflicts: # gpu/vk/vk_internal.h
|
Thanks @hckrman101 for the thorough follow-up review! 1. Code Fixes in
|
Joey-Magnus
left a comment
There was a problem hiding this comment.
Thanks for tracing it through and saying so plainly. Your 2,760-frame run shows texture_flush_to_guest is never called in OoT: the exposure meter is a buffer read through memory_writeback_value, which #2458 (merged) covers. So this ring has no call site in the game we ship, and I'd take your offer and close it in favor of #2458.
If you'd rather keep it open, three things first:
- The description. It still says OoT polls 1x1 luminance textures through
texture_flush_to_guest, and its "Measurements in OoT" section (0.5-1.1 ms before, 0.02 ms after) can't come from a path that runs 0 times. Please remove those numbers. Only post measurements from a run you made, with the log line they came from. - A call site. Show a scene in OoT where the path runs, with the
vk: virtual pages ... remapped:line with and withoutRX_VK_NO_AUTO_READBACK=1, as hckrman101 suggested. - Conflicts. It now conflicts with main in
vk_replay.cpp,vk_texture.cppandtests/fsr_image_retirement_test.cpp. (At its headtest_env_on.pyalso fails, onHOS_FPS_LOG, but that file isn't yours and main has fixed it.)
The fixes for the review points (the f.alloc crash, the timeout fallback, slot-range invalidation, ring expiry and the source test) look right to me. It's the case for carrying the code that's missing.
|
RTX 5090, i7-13700K, Windows 11, Kokiri Forest U1152 uncapped. I rebased this PR onto current main and screened AutoReadback on against the same build with |
|
Thanks @Joey-Magnus and @hckrman101 for the thorough reviews! Since Ocarina of Time reads its exposure meter via buffer write-back ( |
Problem
The Legend of Zelda: Ocarina of Time frequently renders 1x1 RGBA32F luminance / exposure metering and occlusion textures on the GPU and polls them on the CPU through
nvnMemoryPoolInvalidateMappedRange/texture_flush_to_guest().Previously, every poll triggered
flush_texture(), forcing a synchronous copy and waiting on the entire submission queue to idle or drain, stalling both CPU and GPU execution each frame (~0.5ms–1.2ms queue stall per readback in outdoor scenes).Solution
Texture::AutoReadback) backed by host-cached VMA memory (create_buffer_cached) for small GPU-written textures (guest_size <= 64 KB, 1 level, 1 layer, 2D).texture_end_recording(), if anAutoReadback-enabled texture was dirtied in the recording, append an asynchronousvkCmdCopyImageToBufferinto the next ring slot and issue aVK_PIPELINE_STAGE_2_HOST_BITmemory barrier.texture_flush_to_guest(), if the ring slot is ready and not dirty in the currently open recording, wait on the slot's timeline semaphore value. If the timeline wait completes, finish the flush directly out of the host-cached mapping (f.alloc = rb.allocfor mapped cache invalidation); otherwise, fall back cleanly toflush_texture()to prevent stale data.flush_texture()if readback is disabled viaRX_VK_NO_AUTO_READBACK=1(read viaenv_on) or if the texture is written outside normal recording flow.test_fsr_image_retirement.pyandtest_env_on.py.Measurements in OoT
Tested on Linux (x86-64-v3, Vulkan / RADV):
flush_texture()wait: ~0.5ms–1.1ms per readback event (4–6 events per frame during lighting transitions).