box_shadow.rs (21642B)
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 use api::{BorderRadius, BoxShadowClipMode, ClipMode, ColorF, ColorU, PrimitiveKeyKind, PropertyBinding}; 5 use api::units::*; 6 use crate::border::{ensure_no_corner_overlap, BorderRadiusAu}; 7 use crate::clip::{ClipDataHandle, ClipInternData, ClipItemKey, ClipItemKeyKind, ClipNodeId}; 8 use crate::command_buffer::QuadFlags; 9 use crate::intern::{Handle as InternHandle, InternDebug, Internable}; 10 use crate::pattern::{Pattern, PatternBuilder, PatternBuilderContext, PatternBuilderState}; 11 use crate::picture::calculate_uv_rect_kind; 12 use crate::prim_store::{InternablePrimitive, PrimKey, PrimTemplate, PrimTemplateCommonData}; 13 use crate::prim_store::{PrimitiveInstanceKind, PrimitiveStore, RectangleKey}; 14 use crate::quad; 15 use crate::render_target::RenderTargetKind; 16 use crate::render_task::{BlurTask, MaskSubPass, PrimTask, RenderTask, RenderTaskKind, SubPass}; 17 use crate::scene_building::{SceneBuilder, IsVisible}; 18 use crate::segment::EdgeAaSegmentMask; 19 use crate::spatial_tree::SpatialNodeIndex; 20 use crate::gpu_types::{BoxShadowStretchMode, TransformPaletteId, UvRectKind, BlurEdgeMode}; 21 use crate::render_task_graph::RenderTaskId; 22 use crate::internal_types::LayoutPrimitiveInfo; 23 use crate::util::{extract_inner_rect_k, ScaleOffset}; 24 25 pub type BoxShadowKey = PrimKey<BoxShadow>; 26 27 impl 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 39 impl 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)] 44 pub 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 54 impl IsVisible for BoxShadow { 55 fn is_visible(&self) -> bool { 56 true 57 } 58 } 59 60 pub type BoxShadowDataHandle = InternHandle<BoxShadow>; 61 62 impl 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 // TODO(gw): Correctly account for scaled blur radius inflation, and device 74 // pixel scale here. 75 76 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.to_untyped(), 105 pattern_rect.to_untyped(), 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 184 impl 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)] 206 pub 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 216 impl 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 230 pub type BoxShadowTemplate = PrimTemplate<BoxShadowData>; 231 232 impl 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 239 impl 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))] 251 pub struct BoxShadowClipSource { 252 // Parameters that define the shadow and are constant. 253 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 // The current cache key (in device-pixels), and handles 260 // to the cached clip region and blurred texture. 261 pub cache_key: Option<(DeviceIntSize, BoxShadowCacheKey)>, 262 pub render_task: Option<RenderTaskId>, 263 264 // Local-space size of the required render task size. 265 pub shadow_rect_alloc_size: LayoutSize, 266 267 // Local-space size of the required render task size without any downscaling 268 // applied. This is needed to stretch the shadow properly. 269 pub original_alloc_size: LayoutSize, 270 271 // The minimal shadow rect for the parameters above, 272 // used when drawing the shadow rect to be blurred. 273 pub minimal_shadow_rect: LayoutRect, 274 275 // Local space rect for the shadow to be drawn or 276 // stretched in the shadow primitive. 277 pub prim_shadow_rect: LayoutRect, 278 } 279 280 // The blur shader samples BLUR_SAMPLE_SCALE * blur_radius surrounding texels. 281 pub const BLUR_SAMPLE_SCALE: f32 = 3.0; 282 283 // Maximum blur radius for box-shadows (different than blur filters). 284 // Taken from nsCSSRendering.cpp in Gecko. 285 pub const MAX_BLUR_RADIUS: f32 = 300.; 286 287 // A cache key that uniquely identifies a minimally sized 288 // and blurred box-shadow rect that can be stored in the 289 // texture cache and applied to clip-masks. 290 #[derive(Debug, Clone, Eq, Hash, MallocSizeOf, PartialEq)] 291 #[cfg_attr(feature = "capture", derive(Serialize))] 292 #[cfg_attr(feature = "replay", derive(Deserialize))] 293 pub struct BoxShadowCacheKey { 294 pub blur_radius_dp: i32, 295 pub clip_mode: BoxShadowClipMode, 296 // NOTE(emilio): Only the original allocation size needs to be in the cache 297 // key, since the actual size is derived from that. 298 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 306 impl<'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 // Inset shadows get smaller as spread radius increases. 325 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 // Ensure the blur radius is somewhat sensible. 331 blur_radius = f32::min(blur_radius, MAX_BLUR_RADIUS); 332 333 // Adjust the border radius of the box shadow per CSS-spec. 334 let mut shadow_radius = adjust_border_radius_for_box_shadow(border_radius, spread_amount); 335 336 // Apply parameters that affect where the shadow rect 337 // exists in the local space of the primitive. 338 let shadow_rect = prim_info 339 .rect 340 .translate(*box_offset) 341 .inflate(spread_amount, spread_amount); 342 343 // If blur radius is zero, we can use a fast path with 344 // no blur applied. 345 if blur_radius == 0.0 { 346 // Trivial reject of box-shadows that are not visible. 347 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 // TODO(gw): Add a fast path for ClipOut + zero border radius! 359 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 we know that this is an axis-aligned (root-coord) outset box-shadow, 406 // enable the new quad based render path. Complex transformed and inset 407 // box-shadow support will be added to this path as a follow up. 408 if is_root_coord_system && 409 clip_mode == BoxShadowClipMode::Outset && 410 blur_radius < 32.0 && 411 false { 412 // Make sure corners don't overlap. 413 ensure_no_corner_overlap(&mut shadow_radius, shadow_rect.size()); 414 415 // Create clip that gets applied to the primitive 416 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 // Per https://drafts.csswg.org/css-backgrounds/#shadow-blur 426 let blur_radius = (blur_radius * 0.5).round(); 427 // Range over which blue radius affects pixels (~99% within 3 * sigma) 428 let sig3 = blur_radius * 3.0; 429 430 // Clip for the pattern primitive 431 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 // Out rect is the shadow rect + extent of blur 465 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 // Normal path for box-shadows with a valid blur radius. 479 let blur_offset = (BLUR_SAMPLE_SCALE * blur_radius).ceil(); 480 let mut extra_clips = vec![]; 481 482 // Add a normal clip mask to clip out the contents 483 // of the surrounding primitive. 484 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 // Get the local rect of where the shadow will be drawn, 494 // expanded to include room for the blurred region. 495 let dest_rect = shadow_rect.inflate(blur_offset, blur_offset); 496 497 // Draw the box-shadow as a solid rect, using a box-shadow 498 // clip mask item. 499 let prim = PrimitiveKeyKind::Rectangle { 500 color: PropertyBinding::Value(color.into()), 501 }; 502 503 // Create the box-shadow clip item. 504 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 // Certain spread-radii make the shadow invalid. 518 if shadow_rect.is_empty() { 519 return; 520 } 521 522 // Add the box-shadow clip source. 523 extra_clips.push(shadow_clip_source); 524 525 // Outset shadows are expanded by the shadow 526 // region from the original primitive. 527 LayoutPrimitiveInfo::with_clip_rect(dest_rect, prim_info.clip_rect) 528 } 529 BoxShadowClipMode::Inset => { 530 // If the inner shadow rect contains the prim 531 // rect, no pixels will be shadowed. 532 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 // Inset shadows are still visible, even if the 540 // inset shadow rect becomes invalid (they will 541 // just look like a solid rectangle). 542 if !shadow_rect.is_empty() { 543 extra_clips.push(shadow_clip_source); 544 } 545 546 // Inset shadows draw inside the original primitive. 547 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 563 fn 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 572 fn 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 579 fn 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 }