Skip to main content

webrender/
box_shadow.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4use api::{BorderRadius, BoxShadowClipMode, ClipMode, ColorF, ColorU, PropertyBinding};
5use api::units::*;
6use crate::border::{BorderRadiusAu};
7use crate::clip::{ClipItemEntry, ClipItemKey, ClipItemKeyKind, ClipNodeId};
8use crate::intern::{Handle as InternHandle, InternDebug, Internable};
9use crate::prim_store::{InternablePrimitive, PrimKey, PrimTemplate, PrimTemplateCommonData};
10use crate::prim_store::{PrimitiveKind, PrimitiveStore, VectorKey};
11use crate::prim_store::rectangle::RectanglePrim;
12use crate::scene_building::{SceneBuilder, IsVisible};
13use crate::spatial_tree::SpatialNodeIndex;
14use crate::internal_types::LayoutPrimitiveInfo;
15
16pub type BoxShadowKey = PrimKey<BoxShadow>;
17
18impl BoxShadowKey {
19    pub fn new(
20        info: &LayoutPrimitiveInfo,
21        shadow: BoxShadow,
22    ) -> Self {
23        BoxShadowKey {
24            common: info.into(),
25            kind: shadow,
26        }
27    }
28}
29
30impl InternDebug for BoxShadowKey {}
31
32#[cfg_attr(feature = "capture", derive(Serialize))]
33#[cfg_attr(feature = "replay", derive(Deserialize))]
34#[derive(Debug, Clone, MallocSizeOf, Hash, Eq, PartialEq)]
35pub struct BoxShadow {
36    pub color: ColorU,
37    pub blur_radius: Au,
38    pub clip_mode: BoxShadowClipMode,
39    pub shadow_radius: BorderRadiusAu,
40    pub element_radius: BorderRadiusAu,
41    /// `box-shadow` offset of the shadow relative to the element, in
42    /// local space.
43    pub box_offset: VectorKey,
44    /// Signed spread radius. Positive for Outset, negative for Inset
45    /// (matches the convention in `add_box_shadow`).
46    pub spread_amount: Au,
47}
48
49impl IsVisible for BoxShadow {
50    fn is_visible(&self) -> bool {
51        true
52    }
53}
54
55pub type BoxShadowDataHandle = InternHandle<BoxShadow>;
56
57impl InternablePrimitive for BoxShadow {
58    fn into_key(
59        self,
60        info: &LayoutPrimitiveInfo,
61    ) -> BoxShadowKey {
62        BoxShadowKey::new(info, self)
63    }
64
65    fn make_instance_kind(
66        _key: BoxShadowKey,
67        data_handle: BoxShadowDataHandle,
68        _prim_store: &mut PrimitiveStore,
69    ) -> PrimitiveKind {
70        PrimitiveKind::BoxShadow {
71            data_handle,
72        }
73    }
74}
75
76#[cfg_attr(feature = "capture", derive(Serialize))]
77#[cfg_attr(feature = "replay", derive(Deserialize))]
78#[derive(Debug, MallocSizeOf)]
79pub struct BoxShadowData {
80    pub color: ColorF,
81    pub blur_radius: f32,
82    pub clip_mode: BoxShadowClipMode,
83    pub shadow_radius: BorderRadius,
84    pub element_radius: BorderRadius,
85    pub box_offset: LayoutVector2D,
86    pub spread_amount: f32,
87}
88
89impl From<BoxShadow> for BoxShadowData {
90    fn from(shadow: BoxShadow) -> Self {
91        BoxShadowData {
92            color: shadow.color.into(),
93            blur_radius: shadow.blur_radius.to_f32_px(),
94            clip_mode: shadow.clip_mode,
95            shadow_radius: shadow.shadow_radius.into(),
96            element_radius: shadow.element_radius.into(),
97            box_offset: shadow.box_offset.into(),
98            spread_amount: shadow.spread_amount.to_f32_px(),
99        }
100    }
101}
102
103pub type BoxShadowTemplate = PrimTemplate<BoxShadowData>;
104
105impl Internable for BoxShadow {
106    type Key = BoxShadowKey;
107    type StoreData = BoxShadowTemplate;
108    type InternData = ();
109    const PROFILE_COUNTER: usize = crate::profiler::INTERNED_BOX_SHADOWS;
110}
111
112impl From<BoxShadowKey> for BoxShadowTemplate {
113    fn from(shadow: BoxShadowKey) -> Self {
114        BoxShadowTemplate {
115            common: PrimTemplateCommonData::with_key_common(shadow.common),
116            kind: shadow.kind.into(),
117        }
118    }
119}
120
121// The blur shader samples BLUR_SAMPLE_SCALE * blur_radius surrounding texels.
122pub const BLUR_SAMPLE_SCALE: f32 = 3.0;
123
124// Maximum blur radius for box-shadows (different than blur filters).
125// Taken from nsCSSRendering.cpp in Gecko.
126pub const MAX_BLUR_RADIUS: f32 = 300.;
127
128// A cache key that uniquely identifies a minimally sized
129// and blurred box-shadow rect that can be stored in the
130// texture cache and applied to clip-masks.
131#[derive(Debug, Clone, Eq, Hash, MallocSizeOf, PartialEq)]
132#[cfg_attr(feature = "capture", derive(Serialize))]
133#[cfg_attr(feature = "replay", derive(Deserialize))]
134pub struct BoxShadowCacheKey {
135    /// Blur sigma in device pixels at the mask resolution (≤ MAX_BLUR_STD_DEVIATION after Opt B).
136    /// Stored as Au for sub-pixel precision; using i32 would round small sigmas to 0.
137    pub blur_radius_dp: Au,
138    pub clip_mode: BoxShadowClipMode,
139    // NOTE(emilio): Only the original allocation size needs to be in the cache
140    // key, since the actual size is derived from that.
141    pub original_alloc_size: DeviceIntSize,
142    pub br_top_left: DeviceIntSize,
143    pub br_top_right: DeviceIntSize,
144    pub br_bottom_right: DeviceIntSize,
145    pub br_bottom_left: DeviceIntSize,
146    pub device_pixel_scale: Au,
147}
148
149impl<'a> SceneBuilder<'a> {
150    pub fn add_box_shadow(
151        &mut self,
152        spatial_node_index: SpatialNodeIndex,
153        clip_node_id: ClipNodeId,
154        prim_info: &LayoutPrimitiveInfo,
155        box_offset: &LayoutVector2D,
156        color: ColorF,
157        mut blur_radius: f32,
158        spread_radius: f32,
159        border_radius: BorderRadius,
160        shadow_radius: BorderRadius,
161        clip_mode: BoxShadowClipMode,
162    ) {
163        if color.a == 0.0 {
164            return;
165        }
166
167        // Inset shadows get smaller as spread radius increases.
168        let spread_amount = match clip_mode {
169            BoxShadowClipMode::Outset => spread_radius,
170            BoxShadowClipMode::Inset => -spread_radius,
171        };
172
173        // Ensure the blur radius is somewhat sensible.
174        blur_radius = f32::min(blur_radius, MAX_BLUR_RADIUS);
175
176        // Apply parameters that affect where the shadow rect
177        // exists in the local space of the primitive.
178        let shadow_rect = prim_info
179            .rect
180            .translate(*box_offset)
181            .inflate(spread_amount, spread_amount);
182
183        // If blur radius is zero, we can use a fast path with
184        // no blur applied.
185        if blur_radius == 0.0 {
186            // Trivial reject of box-shadows that are not visible.
187            if box_offset.x == 0.0 && box_offset.y == 0.0 && spread_amount == 0.0 {
188                return;
189            }
190
191            let mut clips = Vec::with_capacity(2);
192            let (final_prim_rect, clip_radius) = match clip_mode {
193                BoxShadowClipMode::Outset => {
194                    if shadow_rect.is_empty() {
195                        return;
196                    }
197
198                    // TODO(gw): Add a fast path for ClipOut + zero border radius!
199                    clips.push(ClipItemEntry {
200                        key: ClipItemKey {
201                            kind: ClipItemKeyKind::rounded_rect(
202                                border_radius,
203                                ClipMode::ClipOut,
204                            ),
205                        },
206                        spatial_node_index,
207                        clip_rect: prim_info.rect,
208                    });
209
210                    (shadow_rect, shadow_radius)
211                }
212                BoxShadowClipMode::Inset => {
213                    if !shadow_rect.is_empty() {
214                        clips.push(ClipItemEntry {
215                            key: ClipItemKey {
216                                kind: ClipItemKeyKind::rounded_rect(
217                                    shadow_radius,
218                                    ClipMode::ClipOut,
219                                ),
220                            },
221                            spatial_node_index,
222                            clip_rect: shadow_rect,
223                        });
224                    }
225
226                    (prim_info.rect, border_radius)
227                }
228            };
229
230            clips.push(ClipItemEntry {
231                key: ClipItemKey {
232                    kind: ClipItemKeyKind::rounded_rect(
233                        clip_radius,
234                        ClipMode::Clip,
235                    ),
236                },
237                spatial_node_index,
238                clip_rect: final_prim_rect,
239            });
240
241            self.add_primitive(
242                spatial_node_index,
243                clip_node_id,
244                &LayoutPrimitiveInfo::with_clip_rect(final_prim_rect, prim_info.clip_rect),
245                clips,
246                RectanglePrim {
247                    color: PropertyBinding::Value(color.into()),
248                },
249            );
250        } else {
251            // Box-shadows with a valid blur radius use the quad primitive
252            // path; element clipping is handled analytically in the shader.
253            let blur_offset = (BLUR_SAMPLE_SCALE * blur_radius).ceil();
254
255            // Get the local rect of where the shadow will be drawn,
256            // expanded to include room for the blurred region.
257            let dest_rect = shadow_rect.inflate(blur_offset, blur_offset);
258
259            match clip_mode {
260                BoxShadowClipMode::Outset => {
261                    // Certain spread-radii make the shadow invalid.
262                    if shadow_rect.is_empty() {
263                        return;
264                    }
265
266                    // Element clip is handled analytically in the shader.
267                    self.add_nonshadowable_primitive(
268                        spatial_node_index,
269                        clip_node_id,
270                        &LayoutPrimitiveInfo::with_clip_rect(dest_rect, prim_info.clip_rect),
271                        vec![],
272                        BoxShadow {
273                            color: color.into(),
274                            blur_radius: Au::from_f32_px(blur_radius),
275                            clip_mode,
276                            shadow_radius: shadow_radius.into(),
277                            element_radius: border_radius.into(),
278                            box_offset: (*box_offset).into(),
279                            spread_amount: Au::from_f32_px(spread_amount),
280                        },
281                    );
282                }
283                BoxShadowClipMode::Inset => {
284                    // If the inner shadow rect contains the prim
285                    // rect, no pixels will be shadowed.
286                    if border_radius.is_zero() && shadow_rect
287                        .inflate(-blur_radius, -blur_radius)
288                        .contains_box(&prim_info.rect)
289                    {
290                        return;
291                    }
292
293                    // Element clip is handled analytically in the shader.
294                    self.add_nonshadowable_primitive(
295                        spatial_node_index,
296                        clip_node_id,
297                        &prim_info.clone(),
298                        vec![],
299                        BoxShadow {
300                            color: color.into(),
301                            blur_radius: Au::from_f32_px(blur_radius),
302                            clip_mode,
303                            shadow_radius: shadow_radius.into(),
304                            element_radius: border_radius.into(),
305                            box_offset: (*box_offset).into(),
306                            spread_amount: Au::from_f32_px(spread_amount),
307                        },
308                    );
309                }
310            }
311        }
312    }
313}