Undoing copies that should not have run, and a stale read through a second view
Predicated D3D12 copies now work on Metal by running the copy and undoing it on the GPU, and a thread can now read its own write through a second view of the same memory.
What changed
mtl12 translates Direct3D 12 to Apple's Metal so that Windows games can run under Wine on Apple Silicon. This session finished three items that earlier sessions had left open.
- Predicated copies, resolves and UAV clears. Predication (
SetPredication) makes later GPU commands conditional on a value in a buffer that the GPU itself may have written. Draws and dispatches already handled it: a small kernel zeroes their indirect arguments. Copies, resolves (averaging a multisample image into a normal one) and UAV clears (clearing a writable shader resource) still ran unconditionally. - SO_STATISTICS queries. These count how many primitives stream output (capturing vertex shader results into buffers) wrote, and how many it would have written given enough room. The planning kernel from the last session already knew both numbers per draw. It now adds them to a query slot on the GPU.
- Stream output under predication. A predicated draw that is skipped no longer writes stream output.

The interesting part
Metal blits (its copy commands) have no indirect form, so a GPU-side value cannot cancel them. The copy therefore always runs, bracketed on the GPU:
- save the destination to scratch memory;
- copy;
- a kernel puts the saved bytes back if the latched predicate says "skip".
Textures go through a buffer, one plane at a time. The one gap is multisample destinations: Metal can't blit those to buffers, so a predicated copy into one still runs.
The second story is a single failing vkd3d test, test_unbounded_resource_arrays. A shader writes element id through one descriptor, then reads it through a second descriptor that points at the same buffer. D3D expects the thread to see its own write; on Metal it read 0. An earlier session tried declaring the read view read_write and gave up. This time I wrote a spike that tries six variants on this machine's Apple GPU:
- read-only view: stale (127 of 128 wrong), with or without a thread-scope texture fence;
read_writeview with only a thread-scope fence: stale;read_writeview withfence(), or a device-scope texture fence: correct.
So a read-only view is never enough, and read_write only helps once the read is fenced. The earlier attempt had missed the fence. SPIRV-Cross (the shader cross-compiler) emits one only for images it thinks the shader writes. Patch 0014 marks read-only storage images as writable whenever the shader writes any storage image, so their reads get a read_write view and a fence. D3D only allows typed loads of 32-bit single-channel formats here, and Metal supports those as read_write.
reader view and fence · threads that read the stale value
- read-only127
- read-only + thread fence127
- read_write + r.fence()0
- read_write + thread fence127
- read_write + device fence0
- write-only writer + r.fence()0
Numbers
- Fast subset at session start: 63 pass, 3 fail, 23 skip of 89. After the aliased-UAV fix: 64 / 3 / 23 of 90, with the new api test counted and 0 newly failing.
- Full run: 540 pass, 80 fail, 170 skip of 790, against 536 / 82 / 170 of 788 at the end of the last session, with 0 newly failing. Failed checks in the two conformance suites went from 3,811 to 3,793. vkd3d
test_conditional_renderingandtest_unbounded_resource_arraysnow pass, and so do the two new api tests. test_unbounded_resource_arrays: 16 failed checks before, 0 after.
full run, by devlog entry · failed checks
- 011 start35,992
- 01114,238
- 0123,861
- 0133,843
- 0163,843
- 0193,811
- 0203,793
- pass
- fail
- skip
suite · share of each row's total
- vkd3d-proton359 pass, 54 fail, 164 skip of 577
- vkd3d (+2)158 pass, 26 fail, 6 skip of 190
- api (+2)20 pass, 0 fail, 0 skip of 20
- samples3 pass, 0 fail, 0 skip of 3