MOOR security

Dieser Eintrag ist auf Englisch verfasst.

Choosing the architecture with three experiments

Entry 001No full test run

Three small experiments settled how to run D3D12 games on Apple Silicon: directly on Metal 4, with an open-source shader pipeline, not through Vulkan.

What changed

I'm building an open-source Direct3D 12 implementation that runs Windows games under Wine on Apple Silicon Macs, using Metal. Today the only production-quality option is Apple's D3DMetal, which is closed source.

I considered three designs:

  • A: translate D3D12 directly to Metal 4.
  • B: reuse vkd3d-proton, the D3D12-on-Vulkan layer Proton uses on Linux and the Steam Deck, on top of a Vulkan-on-Metal driver (MoltenVK or KosmicKrisp).
  • C: a hybrid, with my own D3D12 layer and existing open-source shader translators.

Rather than argue it out, I wrote a small experiment against the riskiest assumption of each design. The decision is A, with C's shader pipeline: DXIL → dxil-spirv → SPIR-V → SPIRV-Cross → Metal Shading Language.

The three designs, each with its experiment and result.Windows game (D3D12, DXIL shaders) → Wine; Wine → A: my D3D12 layer directly on Metal 4; Wine → B: vkd3d-proton on Vulkan; Wine → C: my D3D12 layer + open shader translators; A: my D3D12 layer directly on Metal 4 → Metal 4 on Apple Silicon; B: vkd3d-proton on Vulkan → MoltenVK / KosmicKrisp (Vulkan on Metal); MoltenVK / KosmicKrisp (Vulkan on Metal) → Metal 4 on Apple Silicon; C: my D3D12 layer + open shader translators → Metal 4 on Apple Silicon; A: my D3D12 layer directly on Metal 4: spike A: 1M-entry descriptor heap, 0 mismatches in 4.2M reads ✔; B: vkd3d-proton on Vulkan: spike B: no device, transform feedback missing ✘; C: my D3D12 layer + open shader translators: spike C: 596/760 shaders accepted by Metal ✔Windows game(D3D12, DXIL shaders)WineA: my D3D12 layerdirectly on Metal 4B: vkd3d-protonon VulkanC: my D3D12 layer +open shader translatorsMetal 4 on Apple SiliconMoltenVK / KosmicKrisp(Vulkan on Metal)spike A: 1M-entry descriptor heap,0 mismatches in 4.2M reads ✔spike B: no device,transform feedback missing ✘spike C: 596/760 shadersaccepted by Metal ✔
The three designs, each with its experiment and result. (Mermaid source)

The interesting part

On paper, B looks like the obvious shortcut, since vkd3d-proton is mature. In practice it can't create a device on either Vulkan-on-Metal driver:

  • vkd3d-proton requires transform feedback, and MoltenVK doesn't have it: Lacking support for transform feedback.
  • KosmicKrisp has a lot, but it also lacks transform feedback and geometry shaders.

Both features belong to D3D12 feature level 11_0, the minimum any D3D12 device has to support. Metal has neither, so they have to be emulated whichever way I go. Under B, that emulation would live inside a forked Vulkan driver, behind two translation layers. Under A, I write it once, directly against Metal. The gap B was supposed to skip is the same work in a worse place.

A's risk was D3D12's descriptor heap, a GPU-visible table of up to a million resource descriptors that shaders index freely. A Metal 4 experiment with 1,048,576 mixed entries and 4.2 million random GPU reads returned zero mismatches.

Numbers

Descriptor heap on Metal 4, M1 Max (spike A):

NativeRosetta
4M random heap reads3.1 ms3.3 ms
Mismatches00
Creating a texture view0.2 µs0.5 µs

Rosetta matters because Wine's D3D stack runs as x86_64 under Rosetta.

Shader path (spike C): 596 of 760 test shaders (78.4%) from dxil-spirv's own test corpus produced code that Metal accepts. The remaining failures are:

  • real Metal gaps: 64-bit atomics, fp64 in buffers, geometry shaders;
  • fixable translator bugs, mostly in tessellation.
Spike C: shaders Metal accepts after DXIL -> SPIR-V -> MSL, by stage (data as CSV)
  • accepted
  • rejected

shader stage · shaders

  1. vertex33 accepted, 2 rejected of 35
  2. pixel347 accepted, 50 rejected of 397
  3. compute212 accepted, 79 rejected of 291
  4. hull4 accepted, 4 rejected of 8
  5. domain0 accepted, 11 rejected of 11
  6. geometry0 accepted, 18 rejected of 18

"Metal accepts the code" doesn't mean the shader is correct. Correctness is measured by the test suites.

vkd3d-proton on MoltenVK under Wine 11.18 (spike B): 2,445 tests ran, 675 were skipped, and every test that needs a device skipped because no device could be created.