diff options
Diffstat (limited to '')
-rw-r--r-- | gfx/wr/webrender/src/box_shadow.rs | 275 |
1 files changed, 275 insertions, 0 deletions
diff --git a/gfx/wr/webrender/src/box_shadow.rs b/gfx/wr/webrender/src/box_shadow.rs new file mode 100644 index 0000000000..ddc4d90854 --- /dev/null +++ b/gfx/wr/webrender/src/box_shadow.rs @@ -0,0 +1,275 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use api::{BorderRadius, BoxShadowClipMode, ClipMode, ColorF, PrimitiveKeyKind}; +use api::PropertyBinding; +use api::units::*; +use crate::clip::{ClipItemKey, ClipItemKeyKind, ClipChainId}; +use crate::scene_building::SceneBuilder; +use crate::spatial_tree::SpatialNodeIndex; +use crate::gpu_types::BoxShadowStretchMode; +use crate::render_task_cache::RenderTaskCacheEntryHandle; +use crate::internal_types::LayoutPrimitiveInfo; + +#[derive(Debug, Clone, MallocSizeOf)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct BoxShadowClipSource { + // Parameters that define the shadow and are constant. + pub shadow_radius: BorderRadius, + pub blur_radius: f32, + pub clip_mode: BoxShadowClipMode, + pub stretch_mode_x: BoxShadowStretchMode, + pub stretch_mode_y: BoxShadowStretchMode, + + // The current cache key (in device-pixels), and handles + // to the cached clip region and blurred texture. + pub cache_key: Option<(DeviceIntSize, BoxShadowCacheKey)>, + pub cache_handle: Option<RenderTaskCacheEntryHandle>, + + // Local-space size of the required render task size. + pub shadow_rect_alloc_size: LayoutSize, + + // Local-space size of the required render task size without any downscaling + // applied. This is needed to stretch the shadow properly. + pub original_alloc_size: LayoutSize, + + // The minimal shadow rect for the parameters above, + // used when drawing the shadow rect to be blurred. + pub minimal_shadow_rect: LayoutRect, + + // Local space rect for the shadow to be drawn or + // stretched in the shadow primitive. + pub prim_shadow_rect: LayoutRect, +} + +// The blur shader samples BLUR_SAMPLE_SCALE * blur_radius surrounding texels. +pub const BLUR_SAMPLE_SCALE: f32 = 3.0; + +// Maximum blur radius for box-shadows (different than blur filters). +// Taken from nsCSSRendering.cpp in Gecko. +pub const MAX_BLUR_RADIUS: f32 = 300.; + +// A cache key that uniquely identifies a minimally sized +// and blurred box-shadow rect that can be stored in the +// texture cache and applied to clip-masks. +#[derive(Debug, Clone, Eq, Hash, MallocSizeOf, PartialEq)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct BoxShadowCacheKey { + pub blur_radius_dp: i32, + pub clip_mode: BoxShadowClipMode, + // NOTE(emilio): Only the original allocation size needs to be in the cache + // key, since the actual size is derived from that. + pub original_alloc_size: DeviceIntSize, + pub br_top_left: DeviceIntSize, + pub br_top_right: DeviceIntSize, + pub br_bottom_right: DeviceIntSize, + pub br_bottom_left: DeviceIntSize, + pub device_pixel_scale: Au, +} + +impl<'a> SceneBuilder<'a> { + pub fn add_box_shadow( + &mut self, + spatial_node_index: SpatialNodeIndex, + clip_chain_id: ClipChainId, + prim_info: &LayoutPrimitiveInfo, + box_offset: &LayoutVector2D, + color: ColorF, + mut blur_radius: f32, + spread_radius: f32, + border_radius: BorderRadius, + clip_mode: BoxShadowClipMode, + ) { + if color.a == 0.0 { + return; + } + + // Inset shadows get smaller as spread radius increases. + let (spread_amount, prim_clip_mode) = match clip_mode { + BoxShadowClipMode::Outset => (spread_radius, ClipMode::ClipOut), + BoxShadowClipMode::Inset => (-spread_radius, ClipMode::Clip), + }; + + // Ensure the blur radius is somewhat sensible. + blur_radius = f32::min(blur_radius, MAX_BLUR_RADIUS); + + // Adjust the border radius of the box shadow per CSS-spec. + let shadow_radius = adjust_border_radius_for_box_shadow(border_radius, spread_amount); + + // Apply parameters that affect where the shadow rect + // exists in the local space of the primitive. + let shadow_rect = self.snap_rect( + &prim_info + .rect + .translate(*box_offset) + .inflate(spread_amount, spread_amount), + spatial_node_index, + ); + + // If blur radius is zero, we can use a fast path with + // no blur applied. + if blur_radius == 0.0 { + // Trivial reject of box-shadows that are not visible. + if box_offset.x == 0.0 && box_offset.y == 0.0 && spread_amount == 0.0 { + return; + } + + let mut clips = Vec::with_capacity(2); + let (final_prim_rect, clip_radius) = match clip_mode { + BoxShadowClipMode::Outset => { + if shadow_rect.is_empty() { + return; + } + + // TODO(gw): Add a fast path for ClipOut + zero border radius! + clips.push(ClipItemKey { + kind: ClipItemKeyKind::rounded_rect( + prim_info.rect, + border_radius, + ClipMode::ClipOut, + ), + }); + + (shadow_rect, shadow_radius) + } + BoxShadowClipMode::Inset => { + if !shadow_rect.is_empty() { + clips.push(ClipItemKey { + kind: ClipItemKeyKind::rounded_rect( + shadow_rect, + shadow_radius, + ClipMode::ClipOut, + ), + }); + } + + (prim_info.rect, border_radius) + } + }; + + clips.push(ClipItemKey { + kind: ClipItemKeyKind::rounded_rect( + final_prim_rect, + clip_radius, + ClipMode::Clip, + ), + }); + + self.add_primitive( + spatial_node_index, + clip_chain_id, + &LayoutPrimitiveInfo::with_clip_rect(final_prim_rect, prim_info.clip_rect), + clips, + PrimitiveKeyKind::Rectangle { + color: PropertyBinding::Value(color.into()), + }, + ); + } else { + // Normal path for box-shadows with a valid blur radius. + let blur_offset = (BLUR_SAMPLE_SCALE * blur_radius).ceil(); + let mut extra_clips = vec![]; + + // Add a normal clip mask to clip out the contents + // of the surrounding primitive. + extra_clips.push(ClipItemKey { + kind: ClipItemKeyKind::rounded_rect( + prim_info.rect, + border_radius, + prim_clip_mode, + ), + }); + + // Get the local rect of where the shadow will be drawn, + // expanded to include room for the blurred region. + let dest_rect = shadow_rect.inflate(blur_offset, blur_offset); + + // Draw the box-shadow as a solid rect, using a box-shadow + // clip mask item. + let prim = PrimitiveKeyKind::Rectangle { + color: PropertyBinding::Value(color.into()), + }; + + // Create the box-shadow clip item. + let shadow_clip_source = ClipItemKey { + kind: ClipItemKeyKind::box_shadow( + shadow_rect, + shadow_radius, + dest_rect, + blur_radius, + clip_mode, + ), + }; + + let prim_info = match clip_mode { + BoxShadowClipMode::Outset => { + // Certain spread-radii make the shadow invalid. + if shadow_rect.is_empty() { + return; + } + + // Add the box-shadow clip source. + extra_clips.push(shadow_clip_source); + + // Outset shadows are expanded by the shadow + // region from the original primitive. + LayoutPrimitiveInfo::with_clip_rect(dest_rect, prim_info.clip_rect) + } + BoxShadowClipMode::Inset => { + // If the inner shadow rect contains the prim + // rect, no pixels will be shadowed. + if border_radius.is_zero() && shadow_rect + .inflate(-blur_radius, -blur_radius) + .contains_rect(&prim_info.rect) + { + return; + } + + // Inset shadows are still visible, even if the + // inset shadow rect becomes invalid (they will + // just look like a solid rectangle). + if !shadow_rect.is_empty() { + extra_clips.push(shadow_clip_source); + } + + // Inset shadows draw inside the original primitive. + prim_info.clone() + } + }; + + self.add_primitive( + spatial_node_index, + clip_chain_id, + &prim_info, + extra_clips, + prim, + ); + } + } +} + +fn adjust_border_radius_for_box_shadow(radius: BorderRadius, spread_amount: f32) -> BorderRadius { + BorderRadius { + top_left: adjust_corner_for_box_shadow(radius.top_left, spread_amount), + top_right: adjust_corner_for_box_shadow(radius.top_right, spread_amount), + bottom_right: adjust_corner_for_box_shadow(radius.bottom_right, spread_amount), + bottom_left: adjust_corner_for_box_shadow(radius.bottom_left, spread_amount), + } +} + +fn adjust_corner_for_box_shadow(corner: LayoutSize, spread_amount: f32) -> LayoutSize { + LayoutSize::new( + adjust_radius_for_box_shadow(corner.width, spread_amount), + adjust_radius_for_box_shadow(corner.height, spread_amount), + ) +} + +fn adjust_radius_for_box_shadow(border_radius: f32, spread_amount: f32) -> f32 { + if border_radius > 0.0 { + (border_radius + spread_amount).max(0.0) + } else { + 0.0 + } +} |