1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
//! Display-timed dual presentation for MetalFX frame interpolation — macOS only.
//!
//! # Why this module exists
//!
//! Frame interpolation only buys frame rate if the synthesised frame is
//! *displayed*. A Bevy render graph presents its swapchain exactly once per
//! `App::update()`, so an interpolated frame computed inside a render node has
//! nowhere to go — it is correct, and invisible.
//!
//! Presenting twice per update cannot be done through `wgpu`: its only
//! presentation entry point is `SurfaceTexture::present()`, which bottoms out
//! in `[MTLCommandBuffer presentDrawable:]` with no time argument and no way to
//! present a second drawable. So this module reaches past `wgpu` to
//! CoreAnimation directly.
//!
//! # Shape of the solution
//!
//! A `CAMetalLayer` of our own, on its own `NSView`, stacked above the one
//! `wgpu` renders into. Both frames are presented from it; Bevy's swapchain
//! image still carries the real frame and is simply covered.
//!
//! Three properties of that layer are load-bearing, and each was a separate
//! silent failure before it was set (see `shadow-work-shb1`):
//!
//! - `framebufferOnly = false`, so a drawable is a legal blit destination.
//! `wgpu`'s own layer sets this true, which is why a drawable taken from
//! *its* layer has to be drawn into rather than copied into.
//! - `pixelFormat` pinned to `BGRA8Unorm_sRGB`. `CAMetalLayer` supports BGRA
//! channel order only; give it the view's RGBA format and CoreAnimation
//! accepts every present and then skips it, with no error anywhere.
//! - `displaySyncEnabled = true` and `maximumDrawableCount = 3`.
//!
//! # Per-frame sequence
//!
//! ```text
//! in the render node each frame is drawn into a BGRA staging texture
//! by a fullscreen blit pass (a blit *copy* cannot
//! convert channel order, a render pass can)
//!
//! on graph cmdbuf completion acquire two drawables fresh
//! blit staging -> drawable, on OUR queue
//! present interp untimed
//! present real +1 refresh interval
//! commit
//! ```
//!
//! The presents deliberately do **not** happen inside the node. Encoding them
//! on the graph's own command buffer yields zero accepted presents: a drawable
//! acquired mid-graph has been recycled by the time that buffer commits, and
//! the present is discarded silently. Moving acquire-copy-present-commit into
//! the graph buffer's completion handler — where the graph's work is finished,
//! so the staging textures are final and ordering is free — took accepted
//! presents from 0/758 to 757/758.
//!
//! Interpolated frame first: it depicts the moment *between* the previous real
//! frame and this one. The real frame is held back one refresh interval with
//! `presentDrawable:afterMinimumDuration:`, because two untimed presents issued
//! microseconds apart collapse onto the same vsync and CoreAnimation keeps only
//! the later one. The cost is one display interval of latency on the real
//! frame — the standard frame-generation trade, and unavoidable, since the
//! interpolated frame cannot be built until the frame after it exists.
//!
//! # Status: presents accepted, display unverified
//!
//! Measured, with both arms through the same layer and the same telemetry so
//! only the present count differs:
//!
//! ```text
//! baseline (single present) 403 presents 403 callbacks 26.9 fps render
//! dual present 802 presents 801 callbacks 26.7 fps render
//! ```
//!
//! That is 1.99x the accepted-present rate at an unchanged render rate.
//!
//! What is **not** established is that any of it reaches the panel.
//! `MTLDrawable.presentedTime` never populates on the development machine —
//! not for this crate, and not for a minimal, maximally visible Metal window
//! either (`crates/sw-renderer/scripts/present-probe-visible.swift` reports
//! `encoded=881 callbacks=881 presented=0`). Presented frame rate is therefore
//! unmeasurable there by any implementation, correct or not, and the
//! accepted-present rate above is a proxy for it, not a substitute.
//!
//! [`PresentSink`] records real `presentedTime` values and reports presented
//! rate, interval spread (judder), ordering inversions and drops, so the
//! measurement needs only hardware where that signal works. See
//! `shadow-work-au59`.
//!
//! # Drawable budget
//!
//! Our layer allows three drawables and we take two per frame, so
//! `nextDrawable` can legitimately return nil under pressure. That is counted
//! as a drop and skipped — never waited on, because blocking here would stall
//! the render thread behind the display.
pub use ;
pub use ;
pub use ;
pub use ;
extern "C"
/// Whether the main display is awake enough for presentation to mean anything.
///
/// This exists because presentation telemetry is silently meaningless on a
/// sleeping or locked machine: the compositor sends nothing to a panel, so
/// `MTLDrawable.presentedTime` stays 0 and presented-handlers never fire for
/// *any* drawable — the engine's own included. Every configuration then measures
/// identically zero, which reads exactly like a code defect and is not one.
///
/// Uniform null results across independent mechanisms are the tell. Check this
/// before believing any of them.