Files
NX2navi/runtime/shaders/wb_merge.comp
chrg6 f740e95a62 Fix the black 3D world under shadow storage on Linux and Proton, and copy only what each side wrote.
Shadow storage noted a GPU write with the whole range its binding names: the copy-in skipped that range and
the fence copied all of it back, so CPU writes to other fields of the same records never reached the GPU.
Ocarina's lighting parameter is such a field: the lighting came out NaN and the fog pass drew the scene
black. Each page a noted range covers now keeps a base copy, the copy-in moves only the bytes the CPU
changed and the write-back only those the GPU changed (vk_memory.c merge_page). The mirror's merge shader
marks the 4 KiB pages it changed, and one job per submission copies only those back (shadow_wb_cb); the
copy-in reads the CPU-side generation tables through the shared write-watch snapshots; and a caller that
reads guest memory itself resolves with vk_mem_lookup, which does not queue the pool for the copy-in.

Pull request #394 by jacc. Its changes are taken as they are; one hunk met the lazy write-back that came
later (vk_gpu_written_back) and is merged by hand. Added here: the lazy write-back stays off with shadow
storage, because its fetch copies the mirror into the pool's buffer, which is guest memory only for an
imported pool. Files other sessions are changing: runtime/vk_memory.c (the shadow section, the merge
record, vk_gpu_written_back, wb_lazy) and three one-word call changes in runtime/vk_usnap.c.

Without shadow storage (Windows outside Wine) the merge pipeline gains an unused fifth binding and two
push constants and nothing else changes. The Windows files compile; nothing was run, on Windows, Wine or
Linux.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 06:24:18 -05:00

35 lines
1.7 KiB
Plaintext

#version 450
// Write-back merge of a VRAM mirror (vk_memory.c, wb_merge_record): copies a 32-bit word of what the GPU left in the
// mirror into the pool's imported buffer (guest memory) only where it differs from the snapshot taken before the
// submission's GPU work, so words the GPU did not change keep what the CPU wrote meanwhile.
// One invocation per word of the listed ranges; ranges are sorted by their first invocation (x: word offset in the
// mirror and imported bindings, y: word offset in the snapshot binding, z: words, w: first invocation).
// With track set (shadow storage) a word that changed also marks its 4 KiB page of guest memory in Pages: page0 is the
// bindings' start within its page, so the CPU's write-back at the fence reads only the marked pages of the shadow
// (vk_memory.c, shadow_wb_cb).
layout(local_size_x = 64) in;
layout(std430, set = 0, binding = 0) readonly buffer Post { uint post[]; };
layout(std430, set = 0, binding = 1) readonly buffer Pre { uint pre[]; };
layout(std430, set = 0, binding = 2) buffer Dst { uint dst[]; };
layout(std430, set = 0, binding = 3) readonly buffer Ranges { uvec4 rng[]; };
layout(std430, set = 0, binding = 4) writeonly buffer Pages { uint pages[]; };
layout(push_constant) uniform Push { uint nranges, total, page0, track; } pc;
void main() {
uint i = gl_GlobalInvocationID.x;
if (i >= pc.total) return;
uint lo = 0, hi = pc.nranges;
while (hi - lo > 1) {
uint m = (lo + hi) >> 1;
if (rng[m].w <= i) lo = m;
else hi = m;
}
uvec4 r = rng[lo];
uint k = i - r.w;
uint v = post[r.x + k];
if (v != pre[r.y + k]) {
dst[r.x + k] = v;
if (pc.track != 0u) pages[(pc.page0 + (r.x + k) * 4u) >> 12] = 1u;
}
}