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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
//! `ClearRect`: the platform-view hole punch, hoisted to the frame root and
//! lowered to destination-out coverage.
//!
//! `frust_scene`'s `ClearRect` erases an axis-aligned rectangle to full
//! transparency, colour and alpha both, so an OS view hosted behind a
//! translucent surface shows through a slot the app's own opaque backdrop
//! would otherwise seal. It is a real destination-clearing composite, not a
//! skipped paint.
//!
//! ## Hoisting
//!
//! A punch confined to the group it was recorded in would only erase that
//! group's own accumulated content — an app-root backdrop painted outside a
//! scroll view's clip survives the group composite and seals the hole again.
//! So the punch is hoisted past every enclosing clip, layer and snapshot
//! bracket to the frame root, bounded by the intersection of those brackets'
//! device-space bounds so a partially scrolled slot still clips to its
//! viewport ([`punch_rect`]). At the root there is nothing left to hoist past,
//! and the intersection is the punch itself.
//!
//! ## Destination-out, not a clear
//!
//! The lowered form is coverage drawn with `dst' = dst · (1 − src.a)`, which
//! erases colour and alpha exactly where the source covers and leaves an
//! uncovered pixel bit-identical. Deliberately not a clear op: a clear applies
//! at whole-tile granularity, which bleeds the punch up to a tile past a
//! tile-unaligned rectangle edge. Destination-out weights the erase by the
//! source's own coverage instead, so a pixel-aligned edge lands pixel-exact
//! however it falls inside a tile, and a fully covered pixel reads exactly
//! `(0, 0, 0, 0)` on a target that carries alpha.
//!
//! ## The wiring contract
//!
//! This module produces the lowered form and stops there — a
//! [`ClearPunch`](ClearPunch) per surviving punch, naming the strips that
//! carry its coverage, the device bounds they cover, and the painter-order
//! depth it was hoisted from. Issuing them is the renderer's, and is specified
//! here so the pass that grows it has one place to read:
//!
//! 1. **One pass, at the punch's own painter-order position.** A punch is
//! issued into the frame's own target once everything recorded *before* the
//! clear has been recorded, and before anything recorded after it is. The
//! renderer does that by cutting the surface round at the punch's depth —
//! the ops before it end one pass, the punch pass follows, the ops after it
//! resume in the next — so punches at one position are issued together and
//! punches at different positions each get a pass. A punch past every op of
//! the frame, which is what a `ClearRect` recorded last is, therefore lands
//! after the last round exactly as an unconditionally-trailing pass would.
//! They never touch an intermediate page: a punch inside an isolated layer
//! was hoisted out of it.
//! 2. **Fixed-function destination-out, over the strip program.** A punch is a
//! strip run like any other, so it goes through the same strip program and
//! instance layout; only the blend state differs — `src_factor: Zero`,
//! `dst_factor: OneMinusSrcAlpha`, `operation: Add`, for the colour *and*
//! the alpha component. That computes `dst · (1 − src.a)`, which is the
//! `COMPOSE_DEST_OUT` arm of `shaders/blend.wgsl` evaluated for a
//! premultiplied source, without a shader-side composite. It has to be
//! fixed-function here: the blend module reads its backdrop with
//! `textureLoad`, and the frame's own target is not readable inside the
//! pass that writes it. `shaders/blend.wgsl` remains the route for a
//! composite *between* intermediate textures.
//! 3. **Ordered by the cut, and depth-tested against the one pass the cut
//! cannot order it against.** Point 1's cut is what orders the punch against
//! everything drawn into the surface *in a round*: recorded before the
//! clear, it is erased; recorded after it, it lands on top of the erase.
//! That holds whether or not the content wrote depth, so an anti-aliased
//! fringe, a translucent paint's spans and a layer's composite all survive a
//! punch they were recorded over.
//!
//! One pass escapes the cut, and it is why the punch still carries the depth
//! it was hoisted from: the frame's opaque strips are recorded *once, ahead
//! of every round* (see [`crate::renderer`]), so they hold post-clear
//! coverage that no cut can put after the punch. The ordinary `LessEqual`
//! test against the frame's depth attachment restores exactly that ordering
//! — opaque coverage recorded after the clear wrote a nearer depth and the
//! punch fails against it, opaque coverage recorded before it is erased —
//! and it is the only ordering the test is asked for. Everything else in the
//! frame writes no depth, and needs none: the cut already put it on the
//! correct side.
//!
//! Without a depth attachment at all there is no separate opaque pass to
//! order against: every instance travels through the surface rounds in
//! painter order, so the cut alone is the whole contract and nothing is
//! erased that was recorded after the clear.
//!
//! One shape the cut cannot express is a bracket that *straddles* the clear.
//! An isolated layer reaches the surface as a single composite carrying the
//! deepest index inside it, so a layer holding content recorded both before
//! and after the clear composites after the punch as a whole — the half of
//! it recorded before the clear survives where the reference renderer, which
//! closes and reopens the bracket around the punch, erases it. That is the
//! engine's one-composite-per-layer model, not this pass's ordering.
//! 4. **Skipped whole on a target that disregards alpha.** Destination-out
//! darkens colour as well as erasing alpha, so on an opaque presentation —
//! where the erased alpha is disregarded — issuing the pass would leave a
//! black rectangle where the display list says nothing should change. The
//! punches are kept out of [`CompiledFrame::draws`](super::CompiledFrame)
//! for exactly this reason: dropping the pass restores the frame exactly,
//! which is the no-op the display list mandates there.
use Range;
use ;
use RectU16;
/// One hoisted clear, ready to be issued as destination-out coverage.
/// A punch the walk has hoisted, held until the frame's draws are done.
///
/// The rectangle is already in device space — the hoist happens where the
/// clear was recorded, because that is the only place the brackets it is
/// confined by are still open — while the coverage for it is generated at the
/// end of the frame, where it belongs in the strip storage.
/// The device-space rectangle a clear punches, confined to every bracket open
/// around it, or `None` when nothing of it survives.
///
/// `transform` is the clear's own composed transform and `groups` the
/// device-space bounds of the open brackets, in any order. Intersecting rather
/// than clipping is what makes the hoist safe: the punch leaves its brackets
/// behind, so the only thing it can keep from them is the region they admitted
/// it to.
/// A device-space rectangle on the `u16` grid the strip pipeline addresses.
///
/// Clamped rather than refused, so a punch reaching far outside the viewport
/// bounds the pass at the grid's edge instead of wrapping. Kept here rather
/// than shared with the clip stack's own clamp: that one converts a rectangle
/// already proven pixel-aligned, and this one converts an arbitrary punch.