A GPU clock that counted in the wrong unit, and samplers that take pixel coordinates
Under Rosetta, mach_
What changed
mtl12 translates Direct3D 12 to Apple's Metal so that Windows games can run under Wine on Apple Silicon. This session added two features:
- ID3D12Device15 and CPU-resolved query heaps. A query heap created with
D3D12_QUERY_HEAP_FLAG_CPU_RESOLVEcan be read withResolveQueryData, with no command list involved. Timestamps come fromMTL4CounterHeap resolveCounterRange, and other heap types from the results they already keep in CPU-visible memory. The new api test query_heap_cpu_resolve was written first and failed with E_NOINTERFACE. - Non-normalized sampler coordinates for heap samplers. D3D12 lets a sampler take texel coordinates instead of 0–1 coordinates, and
D3D12_FEATURE_DATA_D3D12_OPTIONS17reports it. Metal supports it for samplers in argument buffers too, but its validation layer rejects some combinations. mtl12 now cleans up descriptions that also break D3D12's own rules. A review found a padding hole in the sampler cache key.
The interesting part
GetClockCalibration returns a GPU timestamp and a CPU timestamp taken at the same moment. Applications use the pair to place GPU timings on the CPU timeline. mtl12 returned mach_absolute_time() for both, and an earlier arm64 spike showed that Metal's counter-heap timestamps use the same timebase.
vkd3d-proton's test_query_heap_cpu_resolve_timestamp compares a resolved timestamp with the calibration value, and the two were about 41.7 times apart. The cause: mtl12's Unix side is x86_64 and runs under Rosetta, and there the process timebase changes. A probe built for both architectures showed this:
| x86_64 (Rosetta) | arm64 | |
|---|---|---|
| mach_timebase_info | 1/1 | 125/3 |
| mach_absolute_time() | nanoseconds | 24 MHz ticks |
| MTL4CounterHeap timestamp | 24 MHz ticks | 24 MHz ticks |
Rosetta rescales mach_absolute_time() to nanoseconds so x86 code that assumes a 1/1 timebase keeps working. The GPU doesn't know about Rosetta, so its counters stay in ticks. 41.7 is 1e9 / 24e6. The spike had run arm64-native, where the two clocks happen to match.
The fix converts the CPU value into ticks through the timebase, using 128-bit arithmetic. On arm64 the conversion does nothing. -[MTLDevice sampleTimestamps:gpuTimestamp:] is no help here, because it reports nanoseconds for both values.
clock · counts per millisecond
- mach_absolute_time, x86_64 (Rosetta)1,000,000
- mach_absolute_time, arm6424,000
- MTL4CounterHeap, both24,000
Numbers
- No full run this session.
--fastat the start: 61 pass, 5 fail, 23 skip of 89, 0 newly failing. - Newly passing: vkd3d-proton test_query_heap_cpu_resolve_timestamp and test_query_heap_cpu_resolve_occlusion, test_sampler_non_normalized_coordinates, and two new api tests (query_heap_cpu_resolve, sampler_non_normalized).