Source code

Revision control

Other Tools

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/. */
4
5
use api::{BorderRadius, BoxShadowClipMode, ClipMode, ColorF, PrimitiveKeyKind};
6
use api::PropertyBinding;
7
use api::units::*;
8
use crate::clip::{ClipItemKey, ClipItemKeyKind};
9
use crate::scene_building::SceneBuilder;
10
use crate::gpu_cache::GpuCacheHandle;
11
use crate::gpu_types::BoxShadowStretchMode;
12
use crate::prim_store::ScrollNodeAndClipChain;
13
use crate::render_task_cache::RenderTaskCacheEntryHandle;
14
use crate::util::RectHelpers;
15
use crate::internal_types::LayoutPrimitiveInfo;
16
17
#[derive(Debug, Clone, MallocSizeOf)]
18
#[cfg_attr(feature = "capture", derive(Serialize))]
19
#[cfg_attr(feature = "replay", derive(Deserialize))]
20
pub struct BoxShadowClipSource {
21
// Parameters that define the shadow and are constant.
22
pub shadow_radius: BorderRadius,
23
pub blur_radius: f32,
24
pub clip_mode: BoxShadowClipMode,
25
pub stretch_mode_x: BoxShadowStretchMode,
26
pub stretch_mode_y: BoxShadowStretchMode,
27
28
// The current cache key (in device-pixels), and handles
29
// to the cached clip region and blurred texture.
30
pub cache_key: Option<(DeviceIntSize, BoxShadowCacheKey)>,
31
pub cache_handle: Option<RenderTaskCacheEntryHandle>,
32
pub clip_data_handle: GpuCacheHandle,
33
34
// Local-space size of the required render task size.
35
pub shadow_rect_alloc_size: LayoutSize,
36
37
// Local-space size of the required render task size without any downscaling
38
// applied. This is needed to stretch the shadow properly.
39
pub original_alloc_size: LayoutSize,
40
41
// The minimal shadow rect for the parameters above,
42
// used when drawing the shadow rect to be blurred.
43
pub minimal_shadow_rect: LayoutRect,
44
45
// Local space rect for the shadow to be drawn or
46
// stretched in the shadow primitive.
47
pub prim_shadow_rect: LayoutRect,
48
}
49
50
// The blur shader samples BLUR_SAMPLE_SCALE * blur_radius surrounding texels.
51
pub const BLUR_SAMPLE_SCALE: f32 = 3.0;
52
53
// Maximum blur radius for box-shadows (different than blur filters).
54
// Taken from nsCSSRendering.cpp in Gecko.
55
pub const MAX_BLUR_RADIUS: f32 = 300.;
56
57
// A cache key that uniquely identifies a minimally sized
58
// and blurred box-shadow rect that can be stored in the
59
// texture cache and applied to clip-masks.
60
#[derive(Debug, Clone, Eq, Hash, MallocSizeOf, PartialEq)]
61
#[cfg_attr(feature = "capture", derive(Serialize))]
62
#[cfg_attr(feature = "replay", derive(Deserialize))]
63
pub struct BoxShadowCacheKey {
64
pub blur_radius_dp: i32,
65
pub clip_mode: BoxShadowClipMode,
66
// NOTE(emilio): Only the original allocation size needs to be in the cache
67
// key, since the actual size is derived from that.
68
pub original_alloc_size: DeviceIntSize,
69
pub br_top_left: DeviceIntSize,
70
pub br_top_right: DeviceIntSize,
71
pub br_bottom_right: DeviceIntSize,
72
pub br_bottom_left: DeviceIntSize,
73
}
74
75
impl<'a> SceneBuilder<'a> {
76
pub fn add_box_shadow(
77
&mut self,
78
clip_and_scroll: ScrollNodeAndClipChain,
79
prim_info: &LayoutPrimitiveInfo,
80
box_offset: &LayoutVector2D,
81
color: ColorF,
82
mut blur_radius: f32,
83
spread_radius: f32,
84
border_radius: BorderRadius,
85
clip_mode: BoxShadowClipMode,
86
) {
87
if color.a == 0.0 {
88
return;
89
}
90
91
// Inset shadows get smaller as spread radius increases.
92
let (spread_amount, prim_clip_mode) = match clip_mode {
93
BoxShadowClipMode::Outset => (spread_radius, ClipMode::ClipOut),
94
BoxShadowClipMode::Inset => (-spread_radius, ClipMode::Clip),
95
};
96
97
// Ensure the blur radius is somewhat sensible.
98
blur_radius = f32::min(blur_radius, MAX_BLUR_RADIUS);
99
100
// Adjust the border radius of the box shadow per CSS-spec.
101
let shadow_radius = adjust_border_radius_for_box_shadow(border_radius, spread_amount);
102
103
// Apply parameters that affect where the shadow rect
104
// exists in the local space of the primitive.
105
let shadow_rect = self.snap_rect(
106
&prim_info
107
.rect
108
.translate(*box_offset)
109
.inflate(spread_amount, spread_amount),
110
clip_and_scroll.spatial_node_index,
111
);
112
113
// If blur radius is zero, we can use a fast path with
114
// no blur applied.
115
if blur_radius == 0.0 {
116
// Trivial reject of box-shadows that are not visible.
117
if box_offset.x == 0.0 && box_offset.y == 0.0 && spread_amount == 0.0 {
118
return;
119
}
120
121
let mut clips = Vec::with_capacity(2);
122
let (final_prim_rect, clip_radius) = match clip_mode {
123
BoxShadowClipMode::Outset => {
124
if !shadow_rect.is_well_formed_and_nonempty() {
125
return;
126
}
127
128
// TODO(gw): Add a fast path for ClipOut + zero border radius!
129
clips.push(ClipItemKey {
130
kind: ClipItemKeyKind::rounded_rect(
131
prim_info.rect,
132
border_radius,
133
ClipMode::ClipOut,
134
),
135
spatial_node_index: clip_and_scroll.spatial_node_index,
136
});
137
138
(shadow_rect, shadow_radius)
139
}
140
BoxShadowClipMode::Inset => {
141
if shadow_rect.is_well_formed_and_nonempty() {
142
clips.push(ClipItemKey {
143
kind: ClipItemKeyKind::rounded_rect(
144
shadow_rect,
145
shadow_radius,
146
ClipMode::ClipOut,
147
),
148
spatial_node_index: clip_and_scroll.spatial_node_index,
149
});
150
}
151
152
(prim_info.rect, border_radius)
153
}
154
};
155
156
clips.push(ClipItemKey {
157
kind: ClipItemKeyKind::rounded_rect(
158
final_prim_rect,
159
clip_radius,
160
ClipMode::Clip,
161
),
162
spatial_node_index: clip_and_scroll.spatial_node_index,
163
});
164
165
self.add_primitive(
166
clip_and_scroll,
167
&LayoutPrimitiveInfo::with_clip_rect(final_prim_rect, prim_info.clip_rect),
168
clips,
169
PrimitiveKeyKind::Rectangle {
170
color: PropertyBinding::Value(color.into()),
171
},
172
);
173
} else {
174
// Normal path for box-shadows with a valid blur radius.
175
let blur_offset = (BLUR_SAMPLE_SCALE * blur_radius).ceil();
176
let mut extra_clips = vec![];
177
178
// Add a normal clip mask to clip out the contents
179
// of the surrounding primitive.
180
extra_clips.push(ClipItemKey {
181
kind: ClipItemKeyKind::rounded_rect(
182
prim_info.rect,
183
border_radius,
184
prim_clip_mode,
185
),
186
spatial_node_index: clip_and_scroll.spatial_node_index,
187
});
188
189
// Get the local rect of where the shadow will be drawn,
190
// expanded to include room for the blurred region.
191
let dest_rect = shadow_rect.inflate(blur_offset, blur_offset);
192
193
// Draw the box-shadow as a solid rect, using a box-shadow
194
// clip mask item.
195
let prim = PrimitiveKeyKind::Rectangle {
196
color: PropertyBinding::Value(color.into()),
197
};
198
199
// Create the box-shadow clip item.
200
let shadow_clip_source = ClipItemKey {
201
kind: ClipItemKeyKind::box_shadow(
202
shadow_rect,
203
shadow_radius,
204
dest_rect,
205
blur_radius,
206
clip_mode,
207
),
208
spatial_node_index: clip_and_scroll.spatial_node_index,
209
};
210
211
let prim_info = match clip_mode {
212
BoxShadowClipMode::Outset => {
213
// Certain spread-radii make the shadow invalid.
214
if !shadow_rect.is_well_formed_and_nonempty() {
215
return;
216
}
217
218
// Add the box-shadow clip source.
219
extra_clips.push(shadow_clip_source);
220
221
// Outset shadows are expanded by the shadow
222
// region from the original primitive.
223
LayoutPrimitiveInfo::with_clip_rect(dest_rect, prim_info.clip_rect)
224
}
225
BoxShadowClipMode::Inset => {
226
// If the inner shadow rect contains the prim
227
// rect, no pixels will be shadowed.
228
if border_radius.is_zero() && shadow_rect
229
.inflate(-blur_radius, -blur_radius)
230
.contains_rect(&prim_info.rect)
231
{
232
return;
233
}
234
235
// Inset shadows are still visible, even if the
236
// inset shadow rect becomes invalid (they will
237
// just look like a solid rectangle).
238
if shadow_rect.is_well_formed_and_nonempty() {
239
extra_clips.push(shadow_clip_source);
240
}
241
242
// Inset shadows draw inside the original primitive.
243
prim_info.clone()
244
}
245
};
246
247
self.add_primitive(
248
clip_and_scroll,
249
&prim_info,
250
extra_clips,
251
prim,
252
);
253
}
254
}
255
}
256
257
fn adjust_border_radius_for_box_shadow(radius: BorderRadius, spread_amount: f32) -> BorderRadius {
258
BorderRadius {
259
top_left: adjust_corner_for_box_shadow(radius.top_left, spread_amount),
260
top_right: adjust_corner_for_box_shadow(radius.top_right, spread_amount),
261
bottom_right: adjust_corner_for_box_shadow(radius.bottom_right, spread_amount),
262
bottom_left: adjust_corner_for_box_shadow(radius.bottom_left, spread_amount),
263
}
264
}
265
266
fn adjust_corner_for_box_shadow(corner: LayoutSize, spread_amount: f32) -> LayoutSize {
267
LayoutSize::new(
268
adjust_radius_for_box_shadow(corner.width, spread_amount),
269
adjust_radius_for_box_shadow(corner.height, spread_amount),
270
)
271
}
272
273
fn adjust_radius_for_box_shadow(border_radius: f32, spread_amount: f32) -> f32 {
274
if border_radius > 0.0 {
275
(border_radius + spread_amount).max(0.0)
276
} else {
277
0.0
278
}
279
}