tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 }