webrender/prim_store/
line_dec.rs

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
5use api::{
6    ColorF, ColorU, RasterSpace,
7    LineOrientation, LineStyle, PremultipliedColorF, Shadow,
8};
9use api::units::*;
10use crate::scene_building::{CreateShadow, IsVisible};
11use crate::frame_builder::FrameBuildingState;
12use crate::gpu_cache::GpuDataRequest;
13use crate::intern;
14use crate::internal_types::LayoutPrimitiveInfo;
15use crate::prim_store::{
16    PrimKey, PrimTemplate, PrimTemplateCommonData,
17    InternablePrimitive, PrimitiveStore,
18};
19use crate::prim_store::PrimitiveInstanceKind;
20
21/// Maximum resolution in device pixels at which line decorations are rasterized.
22pub const MAX_LINE_DECORATION_RESOLUTION: u32 = 4096;
23
24#[derive(Clone, Debug, Hash, MallocSizeOf, PartialEq, Eq)]
25#[cfg_attr(feature = "capture", derive(Serialize))]
26#[cfg_attr(feature = "replay", derive(Deserialize))]
27pub struct LineDecorationCacheKey {
28    pub style: LineStyle,
29    pub orientation: LineOrientation,
30    pub wavy_line_thickness: Au,
31    pub size: LayoutSizeAu,
32}
33
34/// Identifying key for a line decoration.
35#[derive(Clone, Debug, Hash, MallocSizeOf, PartialEq, Eq)]
36#[cfg_attr(feature = "capture", derive(Serialize))]
37#[cfg_attr(feature = "replay", derive(Deserialize))]
38pub struct LineDecoration {
39    // If the cache_key is Some(..) it is a line decoration
40    // that relies on a render task (e.g. wavy). If the
41    // cache key is None, it uses a fast path to draw the
42    // line decoration as a solid rect.
43    pub cache_key: Option<LineDecorationCacheKey>,
44    pub color: ColorU,
45}
46
47pub type LineDecorationKey = PrimKey<LineDecoration>;
48
49impl LineDecorationKey {
50    pub fn new(
51        info: &LayoutPrimitiveInfo,
52        line_dec: LineDecoration,
53    ) -> Self {
54        LineDecorationKey {
55            common: info.into(),
56            kind: line_dec,
57        }
58    }
59}
60
61impl intern::InternDebug for LineDecorationKey {}
62
63#[cfg_attr(feature = "capture", derive(Serialize))]
64#[cfg_attr(feature = "replay", derive(Deserialize))]
65#[derive(MallocSizeOf)]
66pub struct LineDecorationData {
67    pub cache_key: Option<LineDecorationCacheKey>,
68    pub color: ColorF,
69}
70
71impl LineDecorationData {
72    /// Update the GPU cache for a given primitive template. This may be called multiple
73    /// times per frame, by each primitive reference that refers to this interned
74    /// template. The initial request call to the GPU cache ensures that work is only
75    /// done if the cache entry is invalid (due to first use or eviction).
76    pub fn update(
77        &mut self,
78        common: &mut PrimTemplateCommonData,
79        frame_state: &mut FrameBuildingState,
80    ) {
81        if let Some(ref mut request) = frame_state.gpu_cache.request(&mut common.gpu_cache_handle) {
82            self.write_prim_gpu_blocks(request);
83        }
84    }
85
86    fn write_prim_gpu_blocks(
87        &self,
88        request: &mut GpuDataRequest
89    ) {
90        match self.cache_key.as_ref() {
91            Some(cache_key) => {
92                request.push(self.color.premultiplied());
93                request.push(PremultipliedColorF::WHITE);
94                request.push([
95                    cache_key.size.width.to_f32_px(),
96                    cache_key.size.height.to_f32_px(),
97                    0.0,
98                    0.0,
99                ]);
100            }
101            None => {
102                request.push(self.color.premultiplied());
103            }
104        }
105    }
106}
107
108pub type LineDecorationTemplate = PrimTemplate<LineDecorationData>;
109
110impl From<LineDecorationKey> for LineDecorationTemplate {
111    fn from(line_dec: LineDecorationKey) -> Self {
112        let common = PrimTemplateCommonData::with_key_common(line_dec.common);
113        LineDecorationTemplate {
114            common,
115            kind: LineDecorationData {
116                cache_key: line_dec.kind.cache_key,
117                color: line_dec.kind.color.into(),
118            }
119        }
120    }
121}
122
123pub type LineDecorationDataHandle = intern::Handle<LineDecoration>;
124
125impl intern::Internable for LineDecoration {
126    type Key = LineDecorationKey;
127    type StoreData = LineDecorationTemplate;
128    type InternData = ();
129    const PROFILE_COUNTER: usize = crate::profiler::INTERNED_LINE_DECORATIONS;
130}
131
132impl InternablePrimitive for LineDecoration {
133    fn into_key(
134        self,
135        info: &LayoutPrimitiveInfo,
136    ) -> LineDecorationKey {
137        LineDecorationKey::new(
138            info,
139            self,
140        )
141    }
142
143    fn make_instance_kind(
144        _key: LineDecorationKey,
145        data_handle: LineDecorationDataHandle,
146        _: &mut PrimitiveStore,
147    ) -> PrimitiveInstanceKind {
148        PrimitiveInstanceKind::LineDecoration {
149            data_handle,
150            render_task: None,
151        }
152    }
153}
154
155impl CreateShadow for LineDecoration {
156    fn create_shadow(
157        &self,
158        shadow: &Shadow,
159        _: bool,
160        _: RasterSpace,
161    ) -> Self {
162        LineDecoration {
163            color: shadow.color.into(),
164            cache_key: self.cache_key.clone(),
165        }
166    }
167}
168
169impl IsVisible for LineDecoration {
170    fn is_visible(&self) -> bool {
171        self.color.a > 0
172    }
173}
174
175/// Choose the decoration mask tile size for a given line.
176///
177/// Given a line with overall size `rect_size` and the given `orientation`,
178/// return the dimensions of a single mask tile for the decoration pattern
179/// described by `style` and `wavy_line_thickness`.
180///
181/// If `style` is `Solid`, no mask tile is necessary; return `None`. The other
182/// styles each have their own characteristic periods of repetition, so for each
183/// one, this function returns a `LayoutSize` with the right aspect ratio and
184/// whose specific size is convenient for the `cs_line_decoration.glsl` fragment
185/// shader to work with. The shader uses a local coordinate space in which the
186/// tile fills a rectangle with one corner at the origin, and with the size this
187/// function returns.
188///
189/// The returned size is not necessarily in pixels; device scaling and other
190/// concerns can still affect the actual task size.
191///
192/// Regardless of whether `orientation` is `Vertical` or `Horizontal`, the
193/// `width` and `height` of the returned size are always horizontal and
194/// vertical, respectively.
195pub fn get_line_decoration_size(
196    rect_size: &LayoutSize,
197    orientation: LineOrientation,
198    style: LineStyle,
199    wavy_line_thickness: f32,
200) -> Option<LayoutSize> {
201    let h = match orientation {
202        LineOrientation::Horizontal => rect_size.height,
203        LineOrientation::Vertical => rect_size.width,
204    };
205
206    // TODO(gw): The formulae below are based on the existing gecko and line
207    //           shader code. They give reasonable results for most inputs,
208    //           but could definitely do with a detailed pass to get better
209    //           quality on a wider range of inputs!
210    //           See nsCSSRendering::PaintDecorationLine in Gecko.
211
212    let (parallel, perpendicular) = match style {
213        LineStyle::Solid => {
214            return None;
215        }
216        LineStyle::Dashed => {
217            let dash_length = (3.0 * h).min(64.0).max(1.0);
218
219            (2.0 * dash_length, 4.0)
220        }
221        LineStyle::Dotted => {
222            let diameter = h.min(64.0).max(1.0);
223            let period = 2.0 * diameter;
224
225            (period, diameter)
226        }
227        LineStyle::Wavy => {
228            let line_thickness = wavy_line_thickness.max(1.0);
229            let slope_length = h - line_thickness;
230            let flat_length = ((line_thickness - 1.0) * 2.0).max(1.0);
231            let approx_period = 2.0 * (slope_length + flat_length);
232
233            (approx_period, h)
234        }
235    };
236
237    Some(match orientation {
238        LineOrientation::Horizontal => LayoutSize::new(parallel, perpendicular),
239        LineOrientation::Vertical => LayoutSize::new(perpendicular, parallel),
240    })
241}
242
243#[test]
244#[cfg(target_pointer_width = "64")]
245fn test_struct_sizes() {
246    use std::mem;
247    // The sizes of these structures are critical for performance on a number of
248    // talos stress tests. If you get a failure here on CI, there's two possibilities:
249    // (a) You made a structure smaller than it currently is. Great work! Update the
250    //     test expectations and move on.
251    // (b) You made a structure larger. This is not necessarily a problem, but should only
252    //     be done with care, and after checking if talos performance regresses badly.
253    assert_eq!(mem::size_of::<LineDecoration>(), 20, "LineDecoration size changed");
254    assert_eq!(mem::size_of::<LineDecorationTemplate>(), 60, "LineDecorationTemplate size changed");
255    assert_eq!(mem::size_of::<LineDecorationKey>(), 40, "LineDecorationKey size changed");
256}