D989 - A queued dispatch's image stores are bounded by their descriptors

Status: decided Date: 2026-10-25 known_by: measured

Amends D876 and D982. A queued compute dispatch that stores to an image, through a flat address that cannot reach guest memory, or device-addressed through buffers the host resolves, is no longer queued as writing everything; and an indirect dispatch's writes are queued at all. Its queued writes are worked out as D876 works out its buffers', before it runs, without translating it.

Images. A storage image's whole range is known from its descriptor. When a dispatch is prepared, the pipeline keeps where its translation found each stored image's descriptor (the module's stored TextureSources), keyed by the program's address and a hash of its bytes. The queued writes read each descriptor from the dispatch's user data or table, as the preparation does, and add:

A program no translation has named yet - its first dispatch - is still unbounded, and so is a descriptor that does not place the image. Those are counted as unbounded: image store.

Flat stores. A flat store whose address's high word is at least 0x8000 reaches no guest memory. The guest runs in the host's user address space, which ends below 2^47, so no guest mapping lies there; the store reaches the local data share or the private aperture (D964), or nothing. draw_buffers::flat_stores_beyond_guest proves the bound for a store whose high word a v_med3_u32 set in the same straight run, after the last change of the execution mask, with two operands that are constants or scalar registers the trace holds as constants: a median is at least the smaller of any two of its operands. Grand Theft Auto V's ray-traversal passes push their stack this way, the high word clamped between 0x70000000 and 0x80000000. Any other flat store is unbounded, counted as unbounded: flat store.

The flat-encoded family's mnemonics name no segment, so a flat store is told by its SEG field (draw_buffers::flat_segment_store). A store in the scratch segment is the lane's own private memory and writes no guest memory. A flat store not known to stay off guest memory is taken as a global one: bounded where the trace serves it (D961), else unbounded, counted as unbounded: flat store.

Device-addressed dispatches. Where the host cannot trace a dispatch and the backend offers device addressing (D982), the queued writes trace it as its preparation will, device-addressed: the buffers the host resolves are still bound (D964), so a store through one writes that buffer alone. Only a store through a buffer the host does not resolve, or an untraced global or flat store, reaches guest memory by device address, and leaves the dispatch unbounded, counted as unbounded: device-addressed. A program that the command processor prepares with host runs instead (D982) binds a superset of these buffers, so the bound still holds.

Indirect dispatches. A DISPATCH_INDIRECT was left out of the queued writes altogether: only DISPATCH_DIRECT packets gave a dispatch state, so what an indirect dispatch wrote was read by later preparations as it stood before. Its state is now recorded too (Submission::indirect_dispatch_states), one group standing in for the grid the command processor reads from memory as it carries it out. Its writes are bounded where the grid does not decide them (Pipeline::indirect_dispatch_write_spans): a buffer a descriptor names, and an image. A store through the window, the global buffer's pages or a buffer placed from a sample of the waves, or a trace that would run the groups on the host, leaves it unbounded, counted as unbounded: indirect grid. Grand Theft Auto V dispatches six programs indirectly each cycle (0x74005b750e00 and its neighbours), most of them with a grid of no groups.

What stays unbounded. A device-addressed store through a buffer the host does not resolve, an untraced global store, an image store before the program's first preparation, and a draw that streams out. The first time a program is unbounded for a reason, the log names it and where.

Why: in Grand Theft Auto V's world about 17 of 88 queued submissions a flip had no write bound, all for an image or a flat store, and every later preparation waited for each of them. The technique is the usual one for tracking device writes ahead of a run: each written binding's guest range, from its descriptor, a storage image's from the surface the descriptor describes and its metadata, and no bound at all where the shader addresses memory directly.

Tests: a_stored_image_writes_the_level_its_descriptor_places, a_queued_image_store_is_bounded_once_its_program_is_translated, a_flat_store_clamped_to_the_apertures_reaches_no_guest_memory, a_device_addressed_dispatch_writes_the_buffers_it_resolves, an_indirect_dispatch_s_state_is_recorded.

Rejected: