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, BlurEdgeMode};
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 edge_mode: BlurEdgeMode::Duplicate,
145 }),
146 ));
147 state.rg_builder.add_dependency(blur_task_v, pattern_task_id);
148
149 let blur_task_h = state.rg_builder.add().init(RenderTask::new_dynamic(
150 task_size,
151 RenderTaskKind::HorizontalBlur(BlurTask {
152 blur_std_deviation: blur_radius,
153 target_kind: RenderTargetKind::Color,
154 blur_region: task_size,
155 edge_mode: BlurEdgeMode::Duplicate,
156 }),
157 ).with_uv_rect_kind(uv_rect_kind));
158 state.rg_builder.add_dependency(blur_task_h, blur_task_v);
159
160 Pattern::texture(
161 blur_task_h,
162 self.kind.color,
163 )
164 }
165
166 fn get_base_color(
167 &self,
168 _ctx: &PatternBuilderContext,
169 ) -> ColorF {
170 self.kind.color
171 }
172
173 fn use_shared_pattern(
174 &self,
175 ) -> bool {
176 false
177 }
178
179 fn can_use_nine_patch(&self) -> bool {
180 false
181 }
182}
183
184impl InternablePrimitive for BoxShadow {
185 fn into_key(
186 self,
187 info: &LayoutPrimitiveInfo,
188 ) -> BoxShadowKey {
189 BoxShadowKey::new(info, self)
190 }
191
192 fn make_instance_kind(
193 _key: BoxShadowKey,
194 data_handle: BoxShadowDataHandle,
195 _prim_store: &mut PrimitiveStore,
196 ) -> PrimitiveInstanceKind {
197 PrimitiveInstanceKind::BoxShadow {
198 data_handle,
199 }
200 }
201}
202
203#[cfg_attr(feature = "capture", derive(Serialize))]
204#[cfg_attr(feature = "replay", derive(Deserialize))]
205#[derive(Debug, MallocSizeOf)]
206pub struct BoxShadowData {
207 pub color: ColorF,
208 pub blur_radius: f32,
209 pub clip_mode: BoxShadowClipMode,
210 pub inner_shadow_rect: LayoutRect,
211 pub outer_shadow_rect: LayoutRect,
212 pub shadow_radius: BorderRadius,
213 pub clip: ClipDataHandle,
214}
215
216impl From<BoxShadow> for BoxShadowData {
217 fn from(shadow: BoxShadow) -> Self {
218 BoxShadowData {
219 color: shadow.color.into(),
220 blur_radius: shadow.blur_radius.to_f32_px(),
221 clip_mode: shadow.clip_mode,
222 inner_shadow_rect: shadow.inner_shadow_rect.into(),
223 outer_shadow_rect: shadow.outer_shadow_rect.into(),
224 shadow_radius: shadow.shadow_radius.into(),
225 clip: shadow.clip,
226 }
227 }
228}
229
230pub type BoxShadowTemplate = PrimTemplate<BoxShadowData>;
231
232impl Internable for BoxShadow {
233 type Key = BoxShadowKey;
234 type StoreData = BoxShadowTemplate;
235 type InternData = ();
236 const PROFILE_COUNTER: usize = crate::profiler::INTERNED_BOX_SHADOWS;
237}
238
239impl From<BoxShadowKey> for BoxShadowTemplate {
240 fn from(shadow: BoxShadowKey) -> Self {
241 BoxShadowTemplate {
242 common: PrimTemplateCommonData::with_key_common(shadow.common),
243 kind: shadow.kind.into(),
244 }
245 }
246}
247
248#[derive(Debug, Clone, MallocSizeOf)]
249#[cfg_attr(feature = "capture", derive(Serialize))]
250#[cfg_attr(feature = "replay", derive(Deserialize))]
251pub struct BoxShadowClipSource {
252 pub shadow_radius: BorderRadius,
254 pub blur_radius: f32,
255 pub clip_mode: BoxShadowClipMode,
256 pub stretch_mode_x: BoxShadowStretchMode,
257 pub stretch_mode_y: BoxShadowStretchMode,
258
259 pub cache_key: Option<(DeviceIntSize, BoxShadowCacheKey)>,
262 pub render_task: Option<RenderTaskId>,
263
264 pub shadow_rect_alloc_size: LayoutSize,
266
267 pub original_alloc_size: LayoutSize,
270
271 pub minimal_shadow_rect: LayoutRect,
274
275 pub prim_shadow_rect: LayoutRect,
278}
279
280pub const BLUR_SAMPLE_SCALE: f32 = 3.0;
282
283pub const MAX_BLUR_RADIUS: f32 = 300.;
286
287#[derive(Debug, Clone, Eq, Hash, MallocSizeOf, PartialEq)]
291#[cfg_attr(feature = "capture", derive(Serialize))]
292#[cfg_attr(feature = "replay", derive(Deserialize))]
293pub struct BoxShadowCacheKey {
294 pub blur_radius_dp: i32,
295 pub clip_mode: BoxShadowClipMode,
296 pub original_alloc_size: DeviceIntSize,
299 pub br_top_left: DeviceIntSize,
300 pub br_top_right: DeviceIntSize,
301 pub br_bottom_right: DeviceIntSize,
302 pub br_bottom_left: DeviceIntSize,
303 pub device_pixel_scale: Au,
304}
305
306impl<'a> SceneBuilder<'a> {
307 pub fn add_box_shadow(
308 &mut self,
309 spatial_node_index: SpatialNodeIndex,
310 clip_node_id: ClipNodeId,
311 prim_info: &LayoutPrimitiveInfo,
312 box_offset: &LayoutVector2D,
313 color: ColorF,
314 mut blur_radius: f32,
315 spread_radius: f32,
316 border_radius: BorderRadius,
317 clip_mode: BoxShadowClipMode,
318 is_root_coord_system: bool,
319 ) {
320 if color.a == 0.0 {
321 return;
322 }
323
324 let (spread_amount, prim_clip_mode) = match clip_mode {
326 BoxShadowClipMode::Outset => (spread_radius, ClipMode::ClipOut),
327 BoxShadowClipMode::Inset => (-spread_radius, ClipMode::Clip),
328 };
329
330 blur_radius = f32::min(blur_radius, MAX_BLUR_RADIUS);
332
333 let mut shadow_radius = adjust_border_radius_for_box_shadow(border_radius, spread_amount);
335
336 let shadow_rect = prim_info
339 .rect
340 .translate(*box_offset)
341 .inflate(spread_amount, spread_amount);
342
343 if blur_radius == 0.0 {
346 if box_offset.x == 0.0 && box_offset.y == 0.0 && spread_amount == 0.0 {
348 return;
349 }
350
351 let mut clips = Vec::with_capacity(2);
352 let (final_prim_rect, clip_radius) = match clip_mode {
353 BoxShadowClipMode::Outset => {
354 if shadow_rect.is_empty() {
355 return;
356 }
357
358 clips.push(ClipItemKey {
360 kind: ClipItemKeyKind::rounded_rect(
361 prim_info.rect,
362 border_radius,
363 ClipMode::ClipOut,
364 ),
365 spatial_node_index,
366 });
367
368 (shadow_rect, shadow_radius)
369 }
370 BoxShadowClipMode::Inset => {
371 if !shadow_rect.is_empty() {
372 clips.push(ClipItemKey {
373 kind: ClipItemKeyKind::rounded_rect(
374 shadow_rect,
375 shadow_radius,
376 ClipMode::ClipOut,
377 ),
378 spatial_node_index,
379 });
380 }
381
382 (prim_info.rect, border_radius)
383 }
384 };
385
386 clips.push(ClipItemKey {
387 kind: ClipItemKeyKind::rounded_rect(
388 final_prim_rect,
389 clip_radius,
390 ClipMode::Clip,
391 ),
392 spatial_node_index,
393 });
394
395 self.add_primitive(
396 spatial_node_index,
397 clip_node_id,
398 &LayoutPrimitiveInfo::with_clip_rect(final_prim_rect, prim_info.clip_rect),
399 clips,
400 PrimitiveKeyKind::Rectangle {
401 color: PropertyBinding::Value(color.into()),
402 },
403 );
404 } else {
405 if is_root_coord_system &&
409 clip_mode == BoxShadowClipMode::Outset &&
410 blur_radius < 32.0 &&
411 false {
412 ensure_no_corner_overlap(&mut shadow_radius, shadow_rect.size());
414
415 let prim_clip = ClipItemKey {
417 kind: ClipItemKeyKind::rounded_rect(
418 prim_info.rect,
419 border_radius,
420 ClipMode::ClipOut,
421 ),
422 spatial_node_index,
423 };
424
425 let blur_radius = (blur_radius * 0.5).round();
427 let sig3 = blur_radius * 3.0;
429
430 let item = ClipItemKey {
432 kind: ClipItemKeyKind::rounded_rect(
433 shadow_rect,
434 shadow_radius,
435 ClipMode::Clip,
436 ),
437 spatial_node_index: self.spatial_tree.root_reference_frame_index(),
438 };
439
440 let clip = self
441 .interners
442 .clip
443 .intern(&item, || {
444 ClipInternData {
445 key: item,
446 }
447 });
448
449 let inner_shadow_rect = shadow_rect.inflate(-sig3, -sig3);
450 let outer_shadow_rect = shadow_rect.inflate( sig3, sig3);
451 let inner_shadow_rect = extract_inner_rect_k(&inner_shadow_rect, &shadow_radius, 0.5).unwrap_or(LayoutRect::zero());
452
453 let prim = BoxShadow {
454 color: color.into(),
455 blur_radius: Au::from_f32_px(blur_radius),
456 clip_mode,
457
458 inner_shadow_rect: inner_shadow_rect.into(),
459 outer_shadow_rect: outer_shadow_rect.into(),
460 shadow_radius: shadow_radius.into(),
461 clip,
462 };
463
464 let prim_info = LayoutPrimitiveInfo::with_clip_rect(
466 outer_shadow_rect,
467 prim_info.clip_rect,
468 );
469
470 self.add_nonshadowable_primitive(
471 spatial_node_index,
472 clip_node_id,
473 &prim_info,
474 vec![prim_clip],
475 prim,
476 );
477 } else {
478 let blur_offset = (BLUR_SAMPLE_SCALE * blur_radius).ceil();
480 let mut extra_clips = vec![];
481
482 extra_clips.push(ClipItemKey {
485 kind: ClipItemKeyKind::rounded_rect(
486 prim_info.rect,
487 border_radius,
488 prim_clip_mode,
489 ),
490 spatial_node_index,
491 });
492
493 let dest_rect = shadow_rect.inflate(blur_offset, blur_offset);
496
497 let prim = PrimitiveKeyKind::Rectangle {
500 color: PropertyBinding::Value(color.into()),
501 };
502
503 let shadow_clip_source = ClipItemKey {
505 kind: ClipItemKeyKind::box_shadow(
506 shadow_rect,
507 shadow_radius,
508 dest_rect,
509 blur_radius,
510 clip_mode,
511 ),
512 spatial_node_index,
513 };
514
515 let prim_info = match clip_mode {
516 BoxShadowClipMode::Outset => {
517 if shadow_rect.is_empty() {
519 return;
520 }
521
522 extra_clips.push(shadow_clip_source);
524
525 LayoutPrimitiveInfo::with_clip_rect(dest_rect, prim_info.clip_rect)
528 }
529 BoxShadowClipMode::Inset => {
530 if border_radius.is_zero() && shadow_rect
533 .inflate(-blur_radius, -blur_radius)
534 .contains_box(&prim_info.rect)
535 {
536 return;
537 }
538
539 if !shadow_rect.is_empty() {
543 extra_clips.push(shadow_clip_source);
544 }
545
546 prim_info.clone()
548 }
549 };
550
551 self.add_primitive(
552 spatial_node_index,
553 clip_node_id,
554 &prim_info,
555 extra_clips,
556 prim,
557 );
558 }
559 }
560 }
561}
562
563fn adjust_border_radius_for_box_shadow(radius: BorderRadius, spread_amount: f32) -> BorderRadius {
564 BorderRadius {
565 top_left: adjust_corner_for_box_shadow(radius.top_left, spread_amount),
566 top_right: adjust_corner_for_box_shadow(radius.top_right, spread_amount),
567 bottom_right: adjust_corner_for_box_shadow(radius.bottom_right, spread_amount),
568 bottom_left: adjust_corner_for_box_shadow(radius.bottom_left, spread_amount),
569 }
570}
571
572fn adjust_corner_for_box_shadow(corner: LayoutSize, spread_amount: f32) -> LayoutSize {
573 LayoutSize::new(
574 adjust_radius_for_box_shadow(corner.width, spread_amount),
575 adjust_radius_for_box_shadow(corner.height, spread_amount),
576 )
577}
578
579fn adjust_radius_for_box_shadow(border_radius: f32, spread_amount: f32) -> f32 {
580 if border_radius > 0.0 {
581 (border_radius + spread_amount).max(0.0)
582 } else {
583 0.0
584 }
585}