1use api::{BorderRadius, BoxShadowClipMode, ClipMode, ColorF, ColorU, PrimitiveKeyKind, PropertyBinding};
5use api::units::*;
6use crate::border::{ensure_no_corner_overlap, BorderRadiusAu};
7use crate::clip::{ClipDataHandle, ClipInternData, ClipItemKey, ClipItemKeyKind, ClipNodeId};
8use crate::command_buffer::QuadFlags;
9use crate::intern::{Handle as InternHandle, InternDebug, Internable};
10use crate::pattern::{Pattern, PatternBuilder, PatternBuilderContext, PatternBuilderState};
11use crate::picture::calculate_uv_rect_kind;
12use crate::prim_store::{InternablePrimitive, PrimKey, PrimTemplate, PrimTemplateCommonData};
13use crate::prim_store::{PrimitiveInstanceKind, PrimitiveStore, RectangleKey};
14use crate::quad;
15use crate::render_target::RenderTargetKind;
16use crate::render_task::{BlurTask, MaskSubPass, PrimTask, RenderTask, RenderTaskKind, SubPass};
17use crate::scene_building::{SceneBuilder, IsVisible};
18use crate::segment::EdgeAaSegmentMask;
19use crate::spatial_tree::SpatialNodeIndex;
20use crate::gpu_types::{BoxShadowStretchMode, TransformPaletteId, UvRectKind};
21use crate::render_task_graph::RenderTaskId;
22use crate::internal_types::LayoutPrimitiveInfo;
23use crate::util::{extract_inner_rect_k, ScaleOffset};
24
25pub type BoxShadowKey = PrimKey<BoxShadow>;
26
27impl BoxShadowKey {
28 pub fn new(
29 info: &LayoutPrimitiveInfo,
30 shadow: BoxShadow,
31 ) -> Self {
32 BoxShadowKey {
33 common: info.into(),
34 kind: shadow,
35 }
36 }
37}
38
39impl InternDebug for BoxShadowKey {}
40
41#[cfg_attr(feature = "capture", derive(Serialize))]
42#[cfg_attr(feature = "replay", derive(Deserialize))]
43#[derive(Debug, Clone, MallocSizeOf, Hash, Eq, PartialEq)]
44pub struct BoxShadow {
45 pub color: ColorU,
46 pub blur_radius: Au,
47 pub clip_mode: BoxShadowClipMode,
48 pub inner_shadow_rect: RectangleKey,
49 pub outer_shadow_rect: RectangleKey,
50 pub shadow_radius: BorderRadiusAu,
51 pub clip: ClipDataHandle,
52}
53
54impl IsVisible for BoxShadow {
55 fn is_visible(&self) -> bool {
56 true
57 }
58}
59
60pub type BoxShadowDataHandle = InternHandle<BoxShadow>;
61
62impl PatternBuilder for BoxShadowTemplate {
63 fn build(
64 &self,
65 sub_rect: Option<DeviceRect>,
66 ctx: &PatternBuilderContext,
67 state: &mut PatternBuilderState,
68 ) -> crate::pattern::Pattern {
69
70 let raster_spatial_node_index = ctx.spatial_tree.root_reference_frame_index();
71 let pattern_rect = self.kind.outer_shadow_rect;
72
73 let (task_size, content_origin, scale_factor, uv_rect_kind) = match sub_rect {
77 Some(rect) => {
78 let expanded_rect = rect.inflate(32.0, 32.0);
79 let uv_rect_kind = calculate_uv_rect_kind(expanded_rect, pattern_rect.cast_unit());
80
81 (
82 expanded_rect.size().cast_unit().to_i32(),
83 expanded_rect.min.cast_unit(),
84 DevicePixelScale::new(1.0),
85 uv_rect_kind,
86 )
87 }
88 None => {
89 (
90 pattern_rect.size().cast_unit().to_i32(),
91 pattern_rect.min.cast_unit(),
92 DevicePixelScale::new(1.0),
93 UvRectKind::Rect,
94 )
95 }
96 };
97
98 let blur_radius = self.kind.blur_radius * scale_factor.0;
99 let clips_range = state.clip_store.push_clip_instance(self.kind.clip);
100 let color_pattern = Pattern::color(self.kind.color);
101
102 let pattern_prim_address_f = quad::write_prim_blocks(
103 &mut state.frame_gpu_data.f32,
104 pattern_rect,
105 pattern_rect,
106 color_pattern.base_color,
107 color_pattern.texture_input.task_id,
108 &[],
109 ScaleOffset::identity(),
110 );
111
112 let pattern_task_id = state.rg_builder.add().init(RenderTask::new_dynamic(
113 task_size,
114 RenderTaskKind::Prim(PrimTask {
115 pattern: color_pattern.kind,
116 pattern_input: color_pattern.shader_input,
117 raster_spatial_node_index,
118 device_pixel_scale: DevicePixelScale::new(1.0),
119 content_origin,
120 prim_address_f: pattern_prim_address_f,
121 transform_id: TransformPaletteId::IDENTITY,
122 edge_flags: EdgeAaSegmentMask::empty(),
123 quad_flags: QuadFlags::APPLY_RENDER_TASK_CLIP | QuadFlags::IGNORE_DEVICE_PIXEL_SCALE,
124 prim_needs_scissor_rect: false,
125 texture_input: color_pattern.texture_input.task_id,
126 }),
127 ));
128
129 let masks = MaskSubPass {
130 clip_node_range: clips_range,
131 prim_spatial_node_index: raster_spatial_node_index,
132 prim_address_f: pattern_prim_address_f,
133 };
134
135 let task = state.rg_builder.get_task_mut(pattern_task_id);
136 task.add_sub_pass(SubPass::Masks { masks });
137
138 let blur_task_v = state.rg_builder.add().init(RenderTask::new_dynamic(
139 task_size,
140 RenderTaskKind::VerticalBlur(BlurTask {
141 blur_std_deviation: blur_radius,
142 target_kind: RenderTargetKind::Color,
143 blur_region: task_size,
144 }),
145 ));
146 state.rg_builder.add_dependency(blur_task_v, pattern_task_id);
147
148 let blur_task_h = state.rg_builder.add().init(RenderTask::new_dynamic(
149 task_size,
150 RenderTaskKind::HorizontalBlur(BlurTask {
151 blur_std_deviation: blur_radius,
152 target_kind: RenderTargetKind::Color,
153 blur_region: task_size,
154 }),
155 ).with_uv_rect_kind(uv_rect_kind));
156 state.rg_builder.add_dependency(blur_task_h, blur_task_v);
157
158 Pattern::texture(
159 blur_task_h,
160 self.kind.color,
161 )
162 }
163
164 fn get_base_color(
165 &self,
166 _ctx: &PatternBuilderContext,
167 ) -> ColorF {
168 self.kind.color
169 }
170
171 fn use_shared_pattern(
172 &self,
173 ) -> bool {
174 false
175 }
176
177 fn can_use_nine_patch(&self) -> bool {
178 false
179 }
180}
181
182impl InternablePrimitive for BoxShadow {
183 fn into_key(
184 self,
185 info: &LayoutPrimitiveInfo,
186 ) -> BoxShadowKey {
187 BoxShadowKey::new(info, self)
188 }
189
190 fn make_instance_kind(
191 _key: BoxShadowKey,
192 data_handle: BoxShadowDataHandle,
193 _prim_store: &mut PrimitiveStore,
194 ) -> PrimitiveInstanceKind {
195 PrimitiveInstanceKind::BoxShadow {
196 data_handle,
197 }
198 }
199}
200
201#[cfg_attr(feature = "capture", derive(Serialize))]
202#[cfg_attr(feature = "replay", derive(Deserialize))]
203#[derive(Debug, MallocSizeOf)]
204pub struct BoxShadowData {
205 pub color: ColorF,
206 pub blur_radius: f32,
207 pub clip_mode: BoxShadowClipMode,
208 pub inner_shadow_rect: LayoutRect,
209 pub outer_shadow_rect: LayoutRect,
210 pub shadow_radius: BorderRadius,
211 pub clip: ClipDataHandle,
212}
213
214impl From<BoxShadow> for BoxShadowData {
215 fn from(shadow: BoxShadow) -> Self {
216 BoxShadowData {
217 color: shadow.color.into(),
218 blur_radius: shadow.blur_radius.to_f32_px(),
219 clip_mode: shadow.clip_mode,
220 inner_shadow_rect: shadow.inner_shadow_rect.into(),
221 outer_shadow_rect: shadow.outer_shadow_rect.into(),
222 shadow_radius: shadow.shadow_radius.into(),
223 clip: shadow.clip,
224 }
225 }
226}
227
228pub type BoxShadowTemplate = PrimTemplate<BoxShadowData>;
229
230impl Internable for BoxShadow {
231 type Key = BoxShadowKey;
232 type StoreData = BoxShadowTemplate;
233 type InternData = ();
234 const PROFILE_COUNTER: usize = crate::profiler::INTERNED_BOX_SHADOWS;
235}
236
237impl From<BoxShadowKey> for BoxShadowTemplate {
238 fn from(shadow: BoxShadowKey) -> Self {
239 BoxShadowTemplate {
240 common: PrimTemplateCommonData::with_key_common(shadow.common),
241 kind: shadow.kind.into(),
242 }
243 }
244}
245
246#[derive(Debug, Clone, MallocSizeOf)]
247#[cfg_attr(feature = "capture", derive(Serialize))]
248#[cfg_attr(feature = "replay", derive(Deserialize))]
249pub struct BoxShadowClipSource {
250 pub shadow_radius: BorderRadius,
252 pub blur_radius: f32,
253 pub clip_mode: BoxShadowClipMode,
254 pub stretch_mode_x: BoxShadowStretchMode,
255 pub stretch_mode_y: BoxShadowStretchMode,
256
257 pub cache_key: Option<(DeviceIntSize, BoxShadowCacheKey)>,
260 pub render_task: Option<RenderTaskId>,
261
262 pub shadow_rect_alloc_size: LayoutSize,
264
265 pub original_alloc_size: LayoutSize,
268
269 pub minimal_shadow_rect: LayoutRect,
272
273 pub prim_shadow_rect: LayoutRect,
276}
277
278pub const BLUR_SAMPLE_SCALE: f32 = 3.0;
280
281pub const MAX_BLUR_RADIUS: f32 = 300.;
284
285#[derive(Debug, Clone, Eq, Hash, MallocSizeOf, PartialEq)]
289#[cfg_attr(feature = "capture", derive(Serialize))]
290#[cfg_attr(feature = "replay", derive(Deserialize))]
291pub struct BoxShadowCacheKey {
292 pub blur_radius_dp: i32,
293 pub clip_mode: BoxShadowClipMode,
294 pub original_alloc_size: DeviceIntSize,
297 pub br_top_left: DeviceIntSize,
298 pub br_top_right: DeviceIntSize,
299 pub br_bottom_right: DeviceIntSize,
300 pub br_bottom_left: DeviceIntSize,
301 pub device_pixel_scale: Au,
302}
303
304impl<'a> SceneBuilder<'a> {
305 pub fn add_box_shadow(
306 &mut self,
307 spatial_node_index: SpatialNodeIndex,
308 clip_node_id: ClipNodeId,
309 prim_info: &LayoutPrimitiveInfo,
310 box_offset: &LayoutVector2D,
311 color: ColorF,
312 mut blur_radius: f32,
313 spread_radius: f32,
314 border_radius: BorderRadius,
315 clip_mode: BoxShadowClipMode,
316 is_root_coord_system: bool,
317 ) {
318 if color.a == 0.0 {
319 return;
320 }
321
322 let (spread_amount, prim_clip_mode) = match clip_mode {
324 BoxShadowClipMode::Outset => (spread_radius, ClipMode::ClipOut),
325 BoxShadowClipMode::Inset => (-spread_radius, ClipMode::Clip),
326 };
327
328 blur_radius = f32::min(blur_radius, MAX_BLUR_RADIUS);
330
331 let mut shadow_radius = adjust_border_radius_for_box_shadow(border_radius, spread_amount);
333
334 let shadow_rect = prim_info
337 .rect
338 .translate(*box_offset)
339 .inflate(spread_amount, spread_amount);
340
341 if blur_radius == 0.0 {
344 if box_offset.x == 0.0 && box_offset.y == 0.0 && spread_amount == 0.0 {
346 return;
347 }
348
349 let mut clips = Vec::with_capacity(2);
350 let (final_prim_rect, clip_radius) = match clip_mode {
351 BoxShadowClipMode::Outset => {
352 if shadow_rect.is_empty() {
353 return;
354 }
355
356 clips.push(ClipItemKey {
358 kind: ClipItemKeyKind::rounded_rect(
359 prim_info.rect,
360 border_radius,
361 ClipMode::ClipOut,
362 ),
363 spatial_node_index,
364 });
365
366 (shadow_rect, shadow_radius)
367 }
368 BoxShadowClipMode::Inset => {
369 if !shadow_rect.is_empty() {
370 clips.push(ClipItemKey {
371 kind: ClipItemKeyKind::rounded_rect(
372 shadow_rect,
373 shadow_radius,
374 ClipMode::ClipOut,
375 ),
376 spatial_node_index,
377 });
378 }
379
380 (prim_info.rect, border_radius)
381 }
382 };
383
384 clips.push(ClipItemKey {
385 kind: ClipItemKeyKind::rounded_rect(
386 final_prim_rect,
387 clip_radius,
388 ClipMode::Clip,
389 ),
390 spatial_node_index,
391 });
392
393 self.add_primitive(
394 spatial_node_index,
395 clip_node_id,
396 &LayoutPrimitiveInfo::with_clip_rect(final_prim_rect, prim_info.clip_rect),
397 clips,
398 PrimitiveKeyKind::Rectangle {
399 color: PropertyBinding::Value(color.into()),
400 },
401 );
402 } else {
403 if is_root_coord_system &&
407 clip_mode == BoxShadowClipMode::Outset &&
408 blur_radius < 32.0 &&
409 false {
410 ensure_no_corner_overlap(&mut shadow_radius, shadow_rect.size());
412
413 let prim_clip = ClipItemKey {
415 kind: ClipItemKeyKind::rounded_rect(
416 prim_info.rect,
417 border_radius,
418 ClipMode::ClipOut,
419 ),
420 spatial_node_index,
421 };
422
423 let blur_radius = (blur_radius * 0.5).round();
425 let sig3 = blur_radius * 3.0;
427
428 let item = ClipItemKey {
430 kind: ClipItemKeyKind::rounded_rect(
431 shadow_rect,
432 shadow_radius,
433 ClipMode::Clip,
434 ),
435 spatial_node_index: self.spatial_tree.root_reference_frame_index(),
436 };
437
438 let clip = self
439 .interners
440 .clip
441 .intern(&item, || {
442 ClipInternData {
443 key: item,
444 }
445 });
446
447 let inner_shadow_rect = shadow_rect.inflate(-sig3, -sig3);
448 let outer_shadow_rect = shadow_rect.inflate( sig3, sig3);
449 let inner_shadow_rect = extract_inner_rect_k(&inner_shadow_rect, &shadow_radius, 0.5).unwrap_or(LayoutRect::zero());
450
451 let prim = BoxShadow {
452 color: color.into(),
453 blur_radius: Au::from_f32_px(blur_radius),
454 clip_mode,
455
456 inner_shadow_rect: inner_shadow_rect.into(),
457 outer_shadow_rect: outer_shadow_rect.into(),
458 shadow_radius: shadow_radius.into(),
459 clip,
460 };
461
462 let prim_info = LayoutPrimitiveInfo::with_clip_rect(
464 outer_shadow_rect,
465 prim_info.clip_rect,
466 );
467
468 self.add_nonshadowable_primitive(
469 spatial_node_index,
470 clip_node_id,
471 &prim_info,
472 vec![prim_clip],
473 prim,
474 );
475 } else {
476 let blur_offset = (BLUR_SAMPLE_SCALE * blur_radius).ceil();
478 let mut extra_clips = vec![];
479
480 extra_clips.push(ClipItemKey {
483 kind: ClipItemKeyKind::rounded_rect(
484 prim_info.rect,
485 border_radius,
486 prim_clip_mode,
487 ),
488 spatial_node_index,
489 });
490
491 let dest_rect = shadow_rect.inflate(blur_offset, blur_offset);
494
495 let prim = PrimitiveKeyKind::Rectangle {
498 color: PropertyBinding::Value(color.into()),
499 };
500
501 let shadow_clip_source = ClipItemKey {
503 kind: ClipItemKeyKind::box_shadow(
504 shadow_rect,
505 shadow_radius,
506 dest_rect,
507 blur_radius,
508 clip_mode,
509 ),
510 spatial_node_index,
511 };
512
513 let prim_info = match clip_mode {
514 BoxShadowClipMode::Outset => {
515 if shadow_rect.is_empty() {
517 return;
518 }
519
520 extra_clips.push(shadow_clip_source);
522
523 LayoutPrimitiveInfo::with_clip_rect(dest_rect, prim_info.clip_rect)
526 }
527 BoxShadowClipMode::Inset => {
528 if border_radius.is_zero() && shadow_rect
531 .inflate(-blur_radius, -blur_radius)
532 .contains_box(&prim_info.rect)
533 {
534 return;
535 }
536
537 if !shadow_rect.is_empty() {
541 extra_clips.push(shadow_clip_source);
542 }
543
544 prim_info.clone()
546 }
547 };
548
549 self.add_primitive(
550 spatial_node_index,
551 clip_node_id,
552 &prim_info,
553 extra_clips,
554 prim,
555 );
556 }
557 }
558 }
559}
560
561fn adjust_border_radius_for_box_shadow(radius: BorderRadius, spread_amount: f32) -> BorderRadius {
562 BorderRadius {
563 top_left: adjust_corner_for_box_shadow(radius.top_left, spread_amount),
564 top_right: adjust_corner_for_box_shadow(radius.top_right, spread_amount),
565 bottom_right: adjust_corner_for_box_shadow(radius.bottom_right, spread_amount),
566 bottom_left: adjust_corner_for_box_shadow(radius.bottom_left, spread_amount),
567 }
568}
569
570fn adjust_corner_for_box_shadow(corner: LayoutSize, spread_amount: f32) -> LayoutSize {
571 LayoutSize::new(
572 adjust_radius_for_box_shadow(corner.width, spread_amount),
573 adjust_radius_for_box_shadow(corner.height, spread_amount),
574 )
575}
576
577fn adjust_radius_for_box_shadow(border_radius: f32, spread_amount: f32) -> f32 {
578 if border_radius > 0.0 {
579 (border_radius + spread_amount).max(0.0)
580 } else {
581 0.0
582 }
583}