Shaders, pipelines, and the first triangle
D3D12 shaders now run on Metal: compute dispatches and draws work, a triangle made it to the screen, and passing tests went from 82 to 347.
What changed
This stretch of 30 commits took the layer from "can clear the screen" to "can run shaders".
- Shader translation. D3D12 shaders are compiled to DXIL or DXBC, Microsoft's GPU bytecode formats. They now go through dxil-spirv (DXIL/DXBC → SPIR-V, the Vulkan shader format), then SPIRV-Cross (SPIR-V → Metal Shading Language), then Metal 4's compiler. Both are open source: dxil-spirv by Hans-Kristian Arntzen, and SPIRV-Cross, hosted by the Khronos Group. I link them as static libraries.
- Descriptors. A descriptor is D3D12's small record pointing a shader at a texture or buffer. Every descriptor here is a 32-byte record in a GPU buffer that the translated shaders index directly.
- Compute and graphics pipelines: compute dispatches, then graphics pipelines and draws. After that came depth/stencil, static samplers, MSAA resolves, queries, bundles and pipeline libraries.
The interesting part
Linking shader stages by name. D3D links a vertex shader's outputs to a pixel shader's inputs by semantic, a string name like COLOR. The translator assigns Metal locations by register number, so two shaders that agree on names can still disagree on numbers. The fix translates the pixel shader first, records the location of each named input, and gives each vertex shader output the location of the input with the same name.
The first triangle. The image below is the first frame drawn with a real draw call. It shows one triangle with a colour at each corner, blended across the face, on a 640×360 offscreen target, drawn by Metal on the Mac GPU. For honesty about provenance: the program was run by hand outside the automated tests, against that day's build plus work not yet committed. No test produced that image. The next session turned it into an automated test sample. Its output under the layer matches the image pixel for pixel.

Numbers
| Pass | Fail | Skip | Total | |
|---|---|---|---|---|
| Before (entry 004) | 82 | 467 | 220 | 769 |
| This stretch (full run at 16:33) | 347 | 191 | 233 | 771 |
The last seven commits came after that run, so it doesn't cover them.
- pass
- fail
- skip
suite · share of each row's total
- vkd3d-proton (+167)220 pass, 136 fail, 221 skip of 577
- vkd3d (+96)123 pass, 55 fail, 12 skip of 190
- api (+2)3 pass, 0 fail, 0 skip of 3
- samples1 pass, 0 fail, 0 skip of 1