D964 - Ray traversal passes address guest memory directly

Status: decided Date: 2026-10-25 known_by: guest-observed

A compute dispatch whose memory ranges the host cannot know before it runs is run device-addressed instead of being refused. Every guest mapping is mapped whole into the device's address space, and the translated module turns each guest address it forms into a device address and reaches the guest's bytes where they lie. This relaxes D900 for such dispatches: the host does not run their waves to find what they reach.

Mapping. Option A: each reservation's readable runs, the pages the guest last left readable, are mapped whole (VK_EXT_external_memory_host, bufferDeviceAddress). Each run is committed first, because the driver touches every page as it maps it. The write-watch's read-only guards over it are released then too, its pages noted written (D824). Only the pieces of a run whose host pages are then committed, private and read-write are mapped; a page the guest may not write is left out, and what reaches it is an escape. Run 335 of Grand Theft Auto V gave the driver a run whole, and the driver faulted inside vkAllocateMemory; how the import treats any other page is the driver's, and not promised. orbistoun-mem keeps the runs and a generation that moves when they change (mappings). The backend maps again only when the generation has moved, and drops a mapping before its reservation's host memory is released. Amended by D968: a piece left out is looked at again whenever a host page is committed or given another protection, and what has become committed and read-write in it is mapped. The release waits for any dispatch still reading it. A table keyed by chunk turns a guest address into a device address. A chunk is at most 64 KiB and never coarser than any mapping's alignment, so one chunk lies in one mapping. The slots form a bounded-probe hash table, as D961's pages do. The push block names the table at DEVICE_TABLE_PUSH_WORD. An address no mapping holds reads zero and stores nothing, and is noted as an escape. Amended by D969: the table lies in device-local memory where the device has room for it, and its probes are consecutive sixteen-byte slots. The lookup is one helper function every access calls, and an access's words share two lookups.

What goes by device address. The dispatch's global and flat accesses, and its scalar and untyped buffer accesses whose descriptor or base the host could not resolve without running the program (D753, D758). Buffers the host does resolve stay bound as before (D746), so a format access still converts by the format the host read from its descriptor. A format access through a descriptor the host cannot resolve is still refused. Amended by D967: a format load through one converts by the format the descriptor names, decoded as the module runs, and a descriptor's swizzled and lane-indexed addressing is carried out rather than refused. A ray intersection reads its node by device address. Amended by D970: a scalar load of an image descriptor from a table is read by device address too. The image still binds from the load that reaches it, and the program may read the words as values. Amended by D982: a dispatch whose buffer only a host run of its groups or waves would find (D914, D950, D951) is prepared device-addressed too, without the run.

Apertures. A flat access routes by its address's high half before it reaches guest memory: 0x80000000 reaches the workgroup's local data share, and 0x70000000 reaches the lane's private memory as a scratch-segment access names it (D954). Any other value reaches guest memory. The three programs keep their traversal stacks this way. They clamp a stack pointer's high half between those two values, and read the entries back with ds_read_b32 and scratch_load_dword respectively.

Caches. A device store passes the host's write-watch and page protection by. Each store therefore marks its chunk's slot. Amended by D969: the marks lie apart from the table, in host memory, one word for each slot. After the dispatch, the backend hands the marked chunks to the command processor. The command processor treats each chunk as a host write: a protected target's trust is dropped (D720), and the write-watch notes the chunk's pages written (D824). Stale textures and draw buffers over them are therefore not reused. Before the dispatch, the frame pending on the device and every deferred copy are written into guest memory, because the dispatch may read anywhere.

Known limit. A buffer the host resolved is still bound and written back after the dispatch (D746). Where the dispatch also stores to the same bytes by device address, the write-back lands last. No program met so far does both.

Why: Grand Theft Auto V's three full-screen ray-traversal passes, the compute programs at 0x74005b854a00, 0x74005b857200 and 0x74005b7e8600, walk a two-level hierarchy. The instance a ray enters names, through s_load_dwordx16 from an address it computes, the bottom level's base. That base forms the descriptor of the next image_bvh_intersect_ray and of a buffer_load_dwordx2. What they read depends on the intersections, so the host cannot work it out. Their preparation was refused under D758 (a branch on an execution mask the host cannot know) and D753 (a descriptor computed from what the host cannot know), and the passes were skipped.

Rejected: