tor-browser

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

conic.rs (17691B)


      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 
      5 //! Conic gradients
      6 //!
      7 //! Specification: https://drafts.csswg.org/css-images-4/#conic-gradients
      8 //!
      9 //! Conic gradients are rendered via cached render tasks and composited with the image brush.
     10 
     11 use euclid::vec2;
     12 use api::{ColorF, ExtendMode, GradientStop, PremultipliedColorF};
     13 use api::units::*;
     14 use crate::gpu_types::ImageBrushPrimitiveData;
     15 use crate::pattern::{Pattern, PatternBuilder, PatternBuilderContext, PatternBuilderState, PatternKind, PatternShaderInput, PatternTextureInput};
     16 use crate::prim_store::gradient::{gpu_gradient_stops_blocks, write_gpu_gradient_stops_tree, GradientKind};
     17 use crate::scene_building::IsVisible;
     18 use crate::frame_builder::FrameBuildingState;
     19 use crate::intern::{Internable, InternDebug, Handle as InternHandle};
     20 use crate::internal_types::LayoutPrimitiveInfo;
     21 use crate::prim_store::{BrushSegment, GradientTileRange, VECS_PER_SEGMENT};
     22 use crate::prim_store::{PrimitiveInstanceKind, PrimitiveOpacity, FloatKey};
     23 use crate::prim_store::{PrimKeyCommonData, PrimTemplateCommonData, PrimitiveStore};
     24 use crate::prim_store::{NinePatchDescriptor, PointKey, SizeKey, InternablePrimitive};
     25 use crate::render_task::{RenderTask, RenderTaskKind};
     26 use crate::render_task_graph::RenderTaskId;
     27 use crate::render_task_cache::{RenderTaskCacheKeyKind, RenderTaskCacheKey, RenderTaskParent};
     28 use crate::renderer::{GpuBufferAddress, GpuBufferBuilder};
     29 
     30 use std::{hash, ops::{Deref, DerefMut}};
     31 use super::{stops_and_min_alpha, GradientStopKey, GradientGpuBlockBuilder};
     32 
     33 /// Hashable conic gradient parameters, for use during prim interning.
     34 #[cfg_attr(feature = "capture", derive(Serialize))]
     35 #[cfg_attr(feature = "replay", derive(Deserialize))]
     36 #[derive(Debug, Clone, MallocSizeOf, PartialEq)]
     37 pub struct ConicGradientParams {
     38    pub angle: f32, // in radians
     39    pub start_offset: f32,
     40    pub end_offset: f32,
     41 }
     42 
     43 impl Eq for ConicGradientParams {}
     44 
     45 impl hash::Hash for ConicGradientParams {
     46    fn hash<H: hash::Hasher>(&self, state: &mut H) {
     47        self.angle.to_bits().hash(state);
     48        self.start_offset.to_bits().hash(state);
     49        self.end_offset.to_bits().hash(state);
     50    }
     51 }
     52 
     53 /// Identifying key for a line decoration.
     54 #[cfg_attr(feature = "capture", derive(Serialize))]
     55 #[cfg_attr(feature = "replay", derive(Deserialize))]
     56 #[derive(Debug, Clone, Eq, PartialEq, Hash, MallocSizeOf)]
     57 pub struct ConicGradientKey {
     58    pub common: PrimKeyCommonData,
     59    pub extend_mode: ExtendMode,
     60    pub center: PointKey,
     61    pub params: ConicGradientParams,
     62    pub stretch_size: SizeKey,
     63    pub stops: Vec<GradientStopKey>,
     64    pub tile_spacing: SizeKey,
     65    pub nine_patch: Option<Box<NinePatchDescriptor>>,
     66 }
     67 
     68 impl ConicGradientKey {
     69    pub fn new(
     70        info: &LayoutPrimitiveInfo,
     71        conic_grad: ConicGradient,
     72    ) -> Self {
     73        ConicGradientKey {
     74            common: info.into(),
     75            extend_mode: conic_grad.extend_mode,
     76            center: conic_grad.center,
     77            params: conic_grad.params,
     78            stretch_size: conic_grad.stretch_size,
     79            stops: conic_grad.stops,
     80            tile_spacing: conic_grad.tile_spacing,
     81            nine_patch: conic_grad.nine_patch,
     82        }
     83    }
     84 }
     85 
     86 impl InternDebug for ConicGradientKey {}
     87 
     88 #[cfg_attr(feature = "capture", derive(Serialize))]
     89 #[cfg_attr(feature = "replay", derive(Deserialize))]
     90 #[derive(MallocSizeOf)]
     91 pub struct ConicGradientTemplate {
     92    pub common: PrimTemplateCommonData,
     93    pub extend_mode: ExtendMode,
     94    pub center: DevicePoint,
     95    pub params: ConicGradientParams,
     96    pub task_size: DeviceIntSize,
     97    pub scale: DeviceVector2D,
     98    pub stretch_size: LayoutSize,
     99    pub tile_spacing: LayoutSize,
    100    pub brush_segments: Vec<BrushSegment>,
    101    pub stops_opacity: PrimitiveOpacity,
    102    pub stops: Vec<GradientStop>,
    103    pub src_color: Option<RenderTaskId>,
    104 }
    105 
    106 impl PatternBuilder for ConicGradientTemplate {
    107    fn build(
    108        &self,
    109        _sub_rect: Option<DeviceRect>,
    110        ctx: &PatternBuilderContext,
    111        state: &mut PatternBuilderState,
    112    ) -> Pattern {
    113        // The scaling parameter is used to compensate for when we reduce the size
    114        // of the render task for cached gradients. Here we aren't applying any.
    115        let no_scale = DeviceVector2D::one();
    116 
    117        if ctx.fb_config.precise_conic_gradients {
    118            conic_gradient_pattern(
    119                self.center,
    120                no_scale,
    121                &self.params,
    122                self.extend_mode,
    123                &self.stops,
    124                state.frame_gpu_data,
    125            )
    126        } else {
    127            conic_gradient_pattern_with_table(
    128                self.center,
    129                no_scale,
    130                &self.params,
    131                self.extend_mode,
    132                &self.stops,
    133                state.frame_gpu_data,
    134            )
    135        }
    136    }
    137 
    138    fn get_base_color(
    139        &self,
    140        _ctx: &PatternBuilderContext,
    141    ) -> ColorF {
    142        ColorF::WHITE
    143    }
    144 
    145    fn use_shared_pattern(
    146        &self,
    147    ) -> bool {
    148        true
    149    }
    150 }
    151 
    152 impl Deref for ConicGradientTemplate {
    153    type Target = PrimTemplateCommonData;
    154    fn deref(&self) -> &Self::Target {
    155        &self.common
    156    }
    157 }
    158 
    159 impl DerefMut for ConicGradientTemplate {
    160    fn deref_mut(&mut self) -> &mut Self::Target {
    161        &mut self.common
    162    }
    163 }
    164 
    165 impl From<ConicGradientKey> for ConicGradientTemplate {
    166    fn from(item: ConicGradientKey) -> Self {
    167        let common = PrimTemplateCommonData::with_key_common(item.common);
    168        let mut brush_segments = Vec::new();
    169 
    170        if let Some(ref nine_patch) = item.nine_patch {
    171            brush_segments = nine_patch.create_segments(common.prim_rect.size());
    172        }
    173 
    174        let (stops, min_alpha) = stops_and_min_alpha(&item.stops);
    175 
    176        // Save opacity of the stops for use in
    177        // selecting which pass this gradient
    178        // should be drawn in.
    179        let stops_opacity = PrimitiveOpacity::from_alpha(min_alpha);
    180 
    181        let mut stretch_size: LayoutSize = item.stretch_size.into();
    182        stretch_size.width = stretch_size.width.min(common.prim_rect.width());
    183        stretch_size.height = stretch_size.height.min(common.prim_rect.height());
    184 
    185        fn approx_eq(a: f32, b: f32) -> bool { (a - b).abs() < 0.01 }
    186 
    187        // Attempt to detect some of the common configurations with hard gradient stops. Allow
    188        // those a higher maximum resolution to avoid the worst cases of aliasing artifacts with
    189        // large conic gradients. A better solution would be to go back to rendering very large
    190        // conic gradients via a brush shader instead of caching all of them (unclear whether
    191        // it is important enough to warrant the better solution).
    192        let mut has_hard_stops = false;
    193        let mut prev_stop = None;
    194        let offset_range = item.params.end_offset - item.params.start_offset;
    195        for stop in &stops {
    196            if offset_range <= 0.0 {
    197                break;
    198            }
    199            if let Some(prev_offset) = prev_stop {
    200                // Check whether two consecutive stops are very close (hard stops).
    201                if stop.offset < prev_offset + 0.005 / offset_range {
    202                    // a is the angle of the stop normalized into 0-1 space and repeating in the 0-0.25 range.
    203                    // If close to 0.0 or 0.25 it means the stop is vertical or horizontal. For those, the lower
    204                    // resolution isn't a big issue.
    205                    let a = item.params.angle / (2.0 * std::f32::consts::PI)
    206                        + item.params.start_offset
    207                        + stop.offset / offset_range;
    208                    let a = a.rem_euclid(0.25);
    209 
    210                    if !approx_eq(a, 0.0) && !approx_eq(a, 0.25) {
    211                        has_hard_stops = true;
    212                        break;
    213                    }
    214                }
    215            }
    216            prev_stop = Some(stop.offset);
    217        }
    218 
    219        let max_size = if has_hard_stops {
    220            2048.0
    221        } else {
    222            1024.0
    223        };
    224 
    225        // Avoid rendering enormous gradients. Radial gradients are mostly made of soft transitions,
    226        // so it is unlikely that rendering at a higher resolution that 1024 would produce noticeable
    227        // differences, especially with 8 bits per channel.
    228        let mut task_size: DeviceSize = stretch_size.cast_unit();
    229        let mut scale = vec2(1.0, 1.0);
    230        if task_size.width > max_size {
    231            scale.x = task_size.width / max_size;
    232            task_size.width = max_size;
    233        }
    234        if task_size.height > max_size {
    235            scale.y = task_size.height / max_size;
    236            task_size.height = max_size;
    237        }
    238 
    239        ConicGradientTemplate {
    240            common,
    241            center: DevicePoint::new(item.center.x, item.center.y),
    242            extend_mode: item.extend_mode,
    243            params: item.params,
    244            stretch_size,
    245            task_size: task_size.ceil().to_i32(),
    246            scale,
    247            tile_spacing: item.tile_spacing.into(),
    248            brush_segments,
    249            stops_opacity,
    250            stops,
    251            src_color: None,
    252        }
    253    }
    254 }
    255 
    256 impl ConicGradientTemplate {
    257    /// Update the GPU cache for a given primitive template. This may be called multiple
    258    /// times per frame, by each primitive reference that refers to this interned
    259    /// template. The initial request call to the GPU cache ensures that work is only
    260    /// done if the cache entry is invalid (due to first use or eviction).
    261    pub fn update(
    262        &mut self,
    263        frame_state: &mut FrameBuildingState,
    264    ) {
    265        let mut writer = frame_state.frame_gpu_data.f32.write_blocks(3 + self.brush_segments.len() * VECS_PER_SEGMENT);
    266        // write_prim_gpu_blocks
    267        writer.push(&ImageBrushPrimitiveData {
    268            color: PremultipliedColorF::WHITE,
    269            background_color: PremultipliedColorF::WHITE,
    270            stretch_size: self.stretch_size,
    271        });
    272        // write_segment_gpu_blocks
    273        for segment in &self.brush_segments {
    274            segment.write_gpu_blocks(&mut writer);
    275        }
    276        self.common.gpu_buffer_address = writer.finish();
    277 
    278        let cache_key = ConicGradientCacheKey {
    279            size: self.task_size,
    280            center: PointKey { x: self.center.x, y: self.center.y },
    281            scale: PointKey { x: self.scale.x, y: self.scale.y },
    282            start_offset: FloatKey(self.params.start_offset),
    283            end_offset: FloatKey(self.params.end_offset),
    284            angle: FloatKey(self.params.angle),
    285            extend_mode: self.extend_mode,
    286            stops: self.stops.iter().map(|stop| (*stop).into()).collect(),
    287        };
    288 
    289        let task_id = frame_state.resource_cache.request_render_task(
    290            Some(RenderTaskCacheKey {
    291                size: self.task_size,
    292                kind: RenderTaskCacheKeyKind::ConicGradient(cache_key),
    293            }),
    294            false,
    295            RenderTaskParent::Surface,
    296            &mut frame_state.frame_gpu_data.f32,
    297            frame_state.rg_builder,
    298            &mut frame_state.surface_builder,
    299            &mut |rg_builder, gpu_buffer_builder| {
    300                let stops = GradientGpuBlockBuilder::build(
    301                    false,
    302                    gpu_buffer_builder,
    303                    &self.stops,
    304                );
    305 
    306                rg_builder.add().init(RenderTask::new_dynamic(
    307                    self.task_size,
    308                    RenderTaskKind::ConicGradient(ConicGradientTask {
    309                        extend_mode: self.extend_mode,
    310                        scale: self.scale,
    311                        center: self.center,
    312                        params: self.params.clone(),
    313                        stops,
    314                    }),
    315                ))
    316            }
    317        );
    318 
    319        self.src_color = Some(task_id);
    320 
    321        // Tile spacing is always handled by decomposing into separate draw calls so the
    322        // primitive opacity is equivalent to stops opacity. This might change to being
    323        // set to non-opaque in the presence of tile spacing if/when tile spacing is handled
    324        // in the same way as with the image primitive.
    325        self.opacity = self.stops_opacity;
    326    }
    327 }
    328 
    329 pub type ConicGradientDataHandle = InternHandle<ConicGradient>;
    330 
    331 #[derive(Debug, MallocSizeOf)]
    332 #[cfg_attr(feature = "capture", derive(Serialize))]
    333 #[cfg_attr(feature = "replay", derive(Deserialize))]
    334 pub struct ConicGradient {
    335    pub extend_mode: ExtendMode,
    336    pub center: PointKey,
    337    pub params: ConicGradientParams,
    338    pub stretch_size: SizeKey,
    339    pub stops: Vec<GradientStopKey>,
    340    pub tile_spacing: SizeKey,
    341    pub nine_patch: Option<Box<NinePatchDescriptor>>,
    342 }
    343 
    344 impl Internable for ConicGradient {
    345    type Key = ConicGradientKey;
    346    type StoreData = ConicGradientTemplate;
    347    type InternData = ();
    348    const PROFILE_COUNTER: usize = crate::profiler::INTERNED_CONIC_GRADIENTS;
    349 }
    350 
    351 impl InternablePrimitive for ConicGradient {
    352    fn into_key(
    353        self,
    354        info: &LayoutPrimitiveInfo,
    355    ) -> ConicGradientKey {
    356        ConicGradientKey::new(info, self)
    357    }
    358 
    359    fn make_instance_kind(
    360        _key: ConicGradientKey,
    361        data_handle: ConicGradientDataHandle,
    362        _prim_store: &mut PrimitiveStore,
    363    ) -> PrimitiveInstanceKind {
    364        PrimitiveInstanceKind::ConicGradient {
    365            data_handle,
    366            visible_tiles_range: GradientTileRange::empty(),
    367            use_legacy_path: true,
    368        }
    369    }
    370 }
    371 
    372 impl IsVisible for ConicGradient {
    373    fn is_visible(&self) -> bool {
    374        true
    375    }
    376 }
    377 
    378 #[derive(Debug)]
    379 #[cfg_attr(feature = "capture", derive(Serialize))]
    380 #[cfg_attr(feature = "replay", derive(Deserialize))]
    381 pub struct ConicGradientTask {
    382    pub extend_mode: ExtendMode,
    383    pub center: DevicePoint,
    384    pub scale: DeviceVector2D,
    385    pub params: ConicGradientParams,
    386    pub stops: GpuBufferAddress,
    387 }
    388 
    389 impl ConicGradientTask {
    390    pub fn to_instance(&self, target_rect: &DeviceIntRect) -> ConicGradientInstance {
    391        ConicGradientInstance {
    392            task_rect: target_rect.to_f32(),
    393            center: self.center,
    394            scale: self.scale,
    395            start_offset: self.params.start_offset,
    396            end_offset: self.params.end_offset,
    397            angle: self.params.angle,
    398            extend_mode: self.extend_mode as i32,
    399            gradient_stops_address: self.stops.as_int(),
    400        }
    401    }
    402 }
    403 
    404 /// The per-instance shader input of a radial gradient render task.
    405 ///
    406 /// Must match the RADIAL_GRADIENT instance description in renderer/vertex.rs.
    407 #[cfg_attr(feature = "capture", derive(Serialize))]
    408 #[cfg_attr(feature = "replay", derive(Deserialize))]
    409 #[repr(C)]
    410 #[derive(Clone, Debug)]
    411 pub struct ConicGradientInstance {
    412    pub task_rect: DeviceRect,
    413    pub center: DevicePoint,
    414    pub scale: DeviceVector2D,
    415    pub start_offset: f32,
    416    pub end_offset: f32,
    417    pub angle: f32,
    418    pub extend_mode: i32,
    419    pub gradient_stops_address: i32,
    420 }
    421 
    422 #[derive(Clone, Debug, Hash, PartialEq, Eq)]
    423 #[cfg_attr(feature = "capture", derive(Serialize))]
    424 #[cfg_attr(feature = "replay", derive(Deserialize))]
    425 pub struct ConicGradientCacheKey {
    426    pub size: DeviceIntSize,
    427    pub center: PointKey,
    428    pub scale: PointKey,
    429    pub start_offset: FloatKey,
    430    pub end_offset: FloatKey,
    431    pub angle: FloatKey,
    432    pub extend_mode: ExtendMode,
    433    pub stops: Vec<GradientStopKey>,
    434 }
    435 
    436 pub fn conic_gradient_pattern_with_table(
    437    center: DevicePoint,
    438    scale: DeviceVector2D,
    439    params: &ConicGradientParams,
    440    extend_mode: ExtendMode,
    441    stops: &[GradientStop],
    442    gpu_buffer_builder: &mut GpuBufferBuilder
    443 ) -> Pattern {
    444    let mut writer = gpu_buffer_builder.f32.write_blocks(2);
    445    writer.push_one([
    446        center.x,
    447        center.y,
    448        scale.x,
    449        scale.y,
    450    ]);
    451    writer.push_one([
    452        params.start_offset,
    453        params.end_offset,
    454        params.angle,
    455        if extend_mode == ExtendMode::Repeat { 1.0 } else { 0.0 }
    456    ]);
    457    let gradient_address = writer.finish();
    458 
    459    let stops_address = GradientGpuBlockBuilder::build(
    460        false,
    461        &mut gpu_buffer_builder.f32,
    462        &stops,
    463    );
    464 
    465    let is_opaque = stops.iter().all(|stop| stop.color.a >= 1.0);
    466 
    467    Pattern {
    468        kind: PatternKind::ConicGradient,
    469        shader_input: PatternShaderInput(
    470            gradient_address.as_int(),
    471            stops_address.as_int(),
    472        ),
    473        texture_input: PatternTextureInput::default(),
    474        base_color: ColorF::WHITE,
    475        is_opaque,
    476    }
    477 }
    478 
    479 pub fn conic_gradient_pattern(
    480    center: DevicePoint,
    481    scale: DeviceVector2D,
    482    params: &ConicGradientParams,
    483    extend_mode: ExtendMode,
    484    stops: &[GradientStop],
    485    gpu_buffer_builder: &mut GpuBufferBuilder
    486 ) -> Pattern {
    487    let num_blocks = 2 + gpu_gradient_stops_blocks(stops.len());
    488    let mut writer = gpu_buffer_builder.f32.write_blocks(num_blocks);
    489    writer.push_one([
    490        center.x,
    491        center.y,
    492        scale.x,
    493        scale.y,
    494    ]);
    495    writer.push_one([
    496        params.start_offset,
    497        params.end_offset,
    498        params.angle,
    499        0.0,
    500    ]);
    501    let is_opaque = write_gpu_gradient_stops_tree(stops, GradientKind::Conic, extend_mode, &mut writer);
    502    let gradient_address = writer.finish();
    503 
    504    Pattern {
    505        kind: PatternKind::Gradient,
    506        shader_input: PatternShaderInput(
    507            gradient_address.as_int(),
    508            0,
    509        ),
    510        texture_input: PatternTextureInput::default(),
    511        base_color: ColorF::WHITE,
    512        is_opaque,
    513    }
    514 }