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
//! The drop-shadow filter: its GPU parameter block, and the pass sequence a
//! shadowed layer renders as.
//!
//! Ported from the sparse-strip reference renderer's `vello_hybrid` 0.2.0
//! `filter.rs` (the `GpuDropShadow` half) and `shaders/filters/drop_shadow.wesl`
//! — narrowed the same way [`crate::filters::blur`] narrows the reference's
//! Gaussian blur: this engine serves only the *shadow-only* shape
//! (`vello_common`'s `DropShadow::new_shadow_only`, `composite_original` always
//! `false`). The reference's other shape composites the layer's own unfiltered
//! content back over the shadow, which needs a third live texture beyond the
//! two the scheduler's ping-pong ever hands a layer (see
//! [`crate::schedule`]'s *Filter rounds* section) — a shape nothing in frust's
//! widget set asks for yet, so it is left unserved rather than half-built.
//!
//! ## What a shadow costs
//!
//! A shadow is the same blur pass sequence [`crate::filters::blur::blur_passes`]
//! already plans, plus one pass before it and one after: a shift by the
//! shadow's own device-space offset ([`FilterPassKind::Offset`]), and a
//! recolour of the blurred, offset alpha mask into the shadow's own
//! premultiplied colour ([`FilterPassKind::Colorize`]). Both cost one pass each
//! and neither changes extent, so wrapping the blur's own even-length sequence
//! in one more pass on each side keeps the whole thing even — the same
//! even-sequence invariant [`crate::filters::blur::blur_passes`] documents,
//! checked here rather than assumed.
//!
//! ## The kernel these passes read
//!
//! The taps and weights [`GpuDropShadow`] packs are read by [`filter.wgsl`]'s
//! existing `PASS_BLUR_H`/`PASS_BLUR_V` cases unmodified: [`GpuDropShadow`]
//! places its header, centre weight, linear weights and linear offsets at the
//! identical texel offsets [`crate::filters::blur::GpuGaussianBlur`] does, so a
//! drop shadow's blur passes are indistinguishable, to the fragment stage, from
//! a plain blur's. Only the offset and the colour — carried in the block's
//! third texel, which a blur's own leaves as padding — are read by code this
//! module's own [`shaders/filters_drop_shadow.wgsl`] prelude adds.
//!
//! ## Two shadow paths, and which one to reach for
//!
//! Frust now has two ways to paint a drop shadow, and they are not
//! interchangeable — each is the right tool for a different shape of caller.
//!
//! [`frust_scene::Command::BlurredRoundedRect`] is a *scene command*: a widget
//! records one, the engine compiler (`compile::blur_rrect`) encodes it as a
//! `vello_common` blurred-rounded-rect paint the strip shader evaluates per
//! pixel (the CPU oracle mirrors it through vello_cpu's
//! `fill_blurred_rounded_rect`), and the shadow of a rectangle with a single
//! corner radius (per-corner radii collapse to
//! [`frust_scene::CornerRadii::largest`] at encode time — see that command's
//! own doc) is painted analytically, in one draw, with no intermediate
//! texture and no extra render pass. That is the whole of what it can shadow:
//! one rectangle, gaussian-blurred by an error-function approximation
//! (`erf7`) baked into the strip shader's own kernel, never arbitrary content.
//!
//! A drop-shadow [filter](crate::filters) layer is the opposite trade. It
//! shadows *whatever a layer's contents turn out to be* — text, an image, a
//! stack of widgets, anything the layer's own draws paint — because it works
//! from the layer's rasterized alpha rather than from one rectangle's
//! geometry. That generality costs a page for the layer's own contents, a
//! second page for the pass sequence to ping-pong into, and `2 +
//! 2·n_decimations` render passes ([`drop_shadow_passes`]) instead of the one
//! draw a `BlurredRoundedRect` costs — and, on this tier, it is reachable only
//! through [`push_filter_layer`](crate::filters::push_filter_layer), since
//! `frust_scene` carries no filter command yet (see [`crate::filters`]'s own
//! doc for why).
//!
//! **The recommendation this module exists to let a docs card cite:** a
//! widget shadowing its own rectangle — a card, a button, a sheet — keeps
//! using `BlurredRoundedRect`; nothing here should replace it, and nothing
//! about this filter makes that command obsolete. A drop-shadow filter layer
//! is for the case `BlurredRoundedRect` cannot serve at all: shadowing
//! content whose shape is not a single rectangle, or is not known until the
//! layer's own contents are rasterized.
//!
//! [`filter.wgsl`]: ../../../shaders/filter.wgsl
//! [`shaders/filters_drop_shadow.wgsl`]: ../../../shaders/filters_drop_shadow.wgsl
use ;
use DropShadow;
use GaussianBlur;
use SizeU16;
use crate;
use crate;
const _: = assert!;
/// A drop shadow's parameter block, as the fragment stage reads it.
///
/// `header`, `center_weight`, `linear_weights` and `linear_offsets` sit at the
/// same offsets [`crate::filters::blur::GpuGaussianBlur`] gives them, so the
/// blur passes of a shadow's own sequence read this block through the exact
/// accessors a plain blur's does; `dx`, `dy` and `color` are what a blur's own
/// block leaves as padding, and only the offset and colourize passes this
/// module adds ever read them.
/// The packed header of a drop shadow's parameter block.
///
/// See the bit layout documented in `shaders/filter.wgsl`; bit 13
/// (`composite_original`) is always left `0` here — this engine never composes
/// the reference's original-compositing shape (see this module's own doc).
const
/// The passes a drop shadow of `shadow` over a layer of `size` renders as, in
/// execution order.
///
/// The shape is the reference's own drop-shadow plan narrowed to the
/// shadow-only case: an [`FilterPassKind::Offset`] shifting the layer by the
/// shadow's own device-space `(dx, dy)`, then exactly the pass sequence
/// [`blur::blur_passes`] plans for the shadow's blur, then a
/// [`FilterPassKind::Colorize`] recolouring the result into the shadow's own
/// premultiplied colour.
///
/// Neither the offset nor the colourize pass rescales, so both read and write
/// `size` — the layer's own, undecimated extent, exactly as the blur
/// sequence's own first and last steps do. Wrapping an always-even sequence in
/// one more pass on each side keeps the whole thing even, so the result lands
/// back in the page the layer's own contents were rendered into; the check
/// below is what keeps that a checked property rather than an assumed one, the
/// same way [`blur::blur_passes`] checks its own.