Avoid ~63 ms Vulkan host memory imports on first texture upload #766

Closed
opened 2026-11-08 20:31:16 +00:00 by lwkalis · 1 comment
lwkalis commented 2026-11-08 20:31:16 +00:00 (Migrated from github.com)

The idea

Reuse an existing host memory import when one already covers a texture. For eligible first uploads with contiguous backing, use Hookshot’s existing staging arena and keep the GPU unswizzle. This would avoid pinning new host memory on the presentation thread for that upload.

I noticed frame drops when jumping after climbing the vines inside the Great Deku Tree. In a Windows play session on an RTX 3070 with honest-eta (bd9bcd71), two 2000×2000 uploads spent about 63 ms almost entirely in the import. The log does not mark the exact frame of the jump.

vk: pool 0x101ee76c00+0xfa0000 imported in 62.7 ms (129 imports, 1956 ms in all)
vk: slow upload 62.8 ms: #55947 0x1001125fc00 2000x2000x1 x1 levels 1 NVN 0x25, 16000 of 16000 KB
vk: pool 0x101dc98200+0xfa0000 imported in 62.8 ms (133 imports, 2131 ms in all)
vk: slow upload 62.9 ms: #57594 0x10010081200 2000x2000x1 x1 levels 1 NVN 0x25, 16000 of 16000 KB

Test settings were RX_VK_WAIT_GPU=0, RX_VK_FRAMEGEN_MULT=2, and RX_VK_ASTC_BC_ALL=1. With the local staging experiment, the renderer logged that it took the new path for textures of the same size.

vk: staging first texture upload #1155 (2000x2000, 16000 KB) without a new Vulkan memory import
vk: staging first texture upload #1830 (2000x2000, 16000 KB) without a new Vulkan memory import

The patched upload duration and before/after FPS were not measured. I have not playtested v2.0.2. The tagged source still calls gpu_buffer directly in the GPU unswizzle path. v2.0.2 upload code

Anything else

The local experiment added gpu_buffer_if_imported, which checks the imported range under pools_mu and advances its last-use value. With the existing 8 MiB lower cutoff, the experiment opted into staging only for full first uploads from non-sparse virtual texture storage up to 16 MiB, after the existing format and geometry checks. The core change in upload_unswizzle_gpu was:

const bool stage_first = !tex.uploaded && !tex.gpu_written && !tex.sparse && !dirty &&
                         tex.gpu >= (1ull << 40) && total <= (16u << 20);
BufferRef src = stage_first ? gpu_buffer_if_imported(tex.gpu, total) : gpu_buffer(tex.gpu, total);
// After the existing format, alignment, and level bounds checks,
// stage eligible uploads whose backing storage has no existing import.
if (stage_first && !src) {
    uint64_t avail = 0;
    const uint8_t* data = host->gpu_ptr(tex.gpu, &avail);
    if (!data || avail < total) return false;
    const Staging st = staging(total, align);
    if (!st.buffer) return false;
    std::memcpy(st.data, data, total);
    src.buffer = st.buffer;
    src.offset = st.offset;
}

This excerpt illustrates the local experiment. An upstream version should stage the submit-time bytes so it respects replay ordering when the game rewrites or remaps the backing storage.

### The idea Reuse an existing host memory import when one already covers a texture. For eligible first uploads with contiguous backing, use Hookshot’s existing staging arena and keep the GPU unswizzle. This would avoid pinning new host memory on the presentation thread for that upload. I noticed frame drops when jumping after climbing the vines inside the Great Deku Tree. In a Windows play session on an RTX 3070 with `honest-eta` (`bd9bcd71`), two 2000×2000 uploads spent about 63 ms almost entirely in the import. The log does not mark the exact frame of the jump. ```text vk: pool 0x101ee76c00+0xfa0000 imported in 62.7 ms (129 imports, 1956 ms in all) vk: slow upload 62.8 ms: #55947 0x1001125fc00 2000x2000x1 x1 levels 1 NVN 0x25, 16000 of 16000 KB vk: pool 0x101dc98200+0xfa0000 imported in 62.8 ms (133 imports, 2131 ms in all) vk: slow upload 62.9 ms: #57594 0x10010081200 2000x2000x1 x1 levels 1 NVN 0x25, 16000 of 16000 KB ``` Test settings were `RX_VK_WAIT_GPU=0`, `RX_VK_FRAMEGEN_MULT=2`, and `RX_VK_ASTC_BC_ALL=1`. With the local staging experiment, the renderer logged that it took the new path for textures of the same size. ```text vk: staging first texture upload #1155 (2000x2000, 16000 KB) without a new Vulkan memory import vk: staging first texture upload #1830 (2000x2000, 16000 KB) without a new Vulkan memory import ``` The patched upload duration and before/after FPS were not measured. I have not playtested v2.0.2. The tagged source still calls `gpu_buffer` directly in the GPU unswizzle path. [v2.0.2 upload code](https://git.magicbean.dev/MagicBeanDev/Hookshot/src/branch/v2.0.2/gpu/vk/vk_texture.cpp#L697-L717) ### Anything else The local experiment added `gpu_buffer_if_imported`, which checks the imported range under `pools_mu` and advances its last-use value. With the existing 8 MiB lower cutoff, the experiment opted into staging only for full first uploads from non-sparse virtual texture storage up to 16 MiB, after the existing format and geometry checks. The core change in `upload_unswizzle_gpu` was: ```cpp const bool stage_first = !tex.uploaded && !tex.gpu_written && !tex.sparse && !dirty && tex.gpu >= (1ull << 40) && total <= (16u << 20); BufferRef src = stage_first ? gpu_buffer_if_imported(tex.gpu, total) : gpu_buffer(tex.gpu, total); // After the existing format, alignment, and level bounds checks, // stage eligible uploads whose backing storage has no existing import. if (stage_first && !src) { uint64_t avail = 0; const uint8_t* data = host->gpu_ptr(tex.gpu, &avail); if (!data || avail < total) return false; const Staging st = staging(total, align); if (!st.buffer) return false; std::memcpy(st.data, data, total); src.buffer = st.buffer; src.offset = st.offset; } ``` This excerpt illustrates the local experiment. An upstream version should stage the submit-time bytes so it respects replay ordering when the game rewrites or remaps the backing storage.
NotRexed commented 2026-11-09 05:56:11 +00:00 (Migrated from github.com)

Tracked in #995, follow there. That issue collects every report of low FPS while the GPU is not fully used, and shader stutter, and this one is linked from it with its GPU, version and log details. If it still happens on v2.0.6 (https://github.com/NotRexed/Hookshot/releases/tag/v2.0.6; Update next to Play in the launcher), please add a comment there with your hookshot_log.txt.

Tracked in #995, follow there. That issue collects every report of low FPS while the GPU is not fully used, and shader stutter, and this one is linked from it with its GPU, version and log details. If it still happens on v2.0.6 (https://github.com/NotRexed/Hookshot/releases/tag/v2.0.6; Update next to Play in the launcher), please add a comment there with your `hookshot_log.txt`.
Commenting is not possible because the repository is archived.
No milestone
No assignees
1 participant
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/Hookshot#766
No description provided.