Skip to main content

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 euclid::Scale;
11use crate::gpu_types::ImageBrushPrimitiveData;
12use crate::render_task::{RenderTask, RenderTaskKind};
13use crate::render_task_cache::{RenderTaskCacheKey, RenderTaskCacheKeyKind, RenderTaskParent};
14use crate::render_task_graph::RenderTaskId;
15use crate::renderer::GpuBufferAddress;
16use crate::scene_building::{CreateShadow, IsVisible};
17use crate::frame_builder::{FrameBuildingContext, FrameBuildingState};
18use crate::intern;
19use crate::internal_types::LayoutPrimitiveInfo;
20use crate::prim_store::{
21    PrimKey, PrimTemplate, PrimTemplateCommonData,
22    InternablePrimitive, PrimitiveStore,
23};
24use crate::prim_store::PrimitiveKind;
25use crate::spatial_tree::SpatialNodeIndex;
26use crate::util::clamp_to_scale_factor;
27
28/// Maximum resolution in device pixels at which line decorations are rasterized.
29pub const MAX_LINE_DECORATION_RESOLUTION: u32 = 4096;
30
31/// Per-frame scratch data for a LineDecoration primitive.
32#[derive(Copy, Clone, Debug)]
33#[cfg_attr(feature = "capture", derive(Serialize))]
34pub struct LineDecorationScratch {
35    pub task_id: RenderTaskId,
36    pub gpu_address: GpuBufferAddress,
37}
38
39#[derive(Clone, Debug, Hash, MallocSizeOf, PartialEq, Eq)]
40#[cfg_attr(feature = "capture", derive(Serialize))]
41#[cfg_attr(feature = "replay", derive(Deserialize))]
42pub struct LineDecorationCacheKey {
43    pub style: LineStyle,
44    pub orientation: LineOrientation,
45    pub wavy_line_thickness: Au,
46    pub size: LayoutSizeAu,
47}
48
49/// Identifying key for a line decoration. The mask tile size
50/// (`LineDecorationCacheKey`) is intentionally not part of the intern
51/// key — it is a deterministic function of `style`, `orientation`,
52/// `wavy_line_thickness`, and the prim's per-frame local rect, and is
53/// rebuilt during frame building.
54#[derive(Clone, Debug, Hash, MallocSizeOf, PartialEq, Eq)]
55#[cfg_attr(feature = "capture", derive(Serialize))]
56#[cfg_attr(feature = "replay", derive(Deserialize))]
57pub struct LineDecoration {
58    pub style: LineStyle,
59    pub orientation: LineOrientation,
60    pub wavy_line_thickness: Au,
61    pub color: ColorU,
62}
63
64pub type LineDecorationKey = PrimKey<LineDecoration>;
65
66impl LineDecorationKey {
67    pub fn new(
68        info: &LayoutPrimitiveInfo,
69        line_dec: LineDecoration,
70    ) -> Self {
71        LineDecorationKey {
72            common: info.into(),
73            kind: line_dec,
74        }
75    }
76}
77
78impl intern::InternDebug for LineDecorationKey {}
79
80#[cfg_attr(feature = "capture", derive(Serialize))]
81#[cfg_attr(feature = "replay", derive(Deserialize))]
82#[derive(MallocSizeOf)]
83pub struct LineDecorationData {
84    pub style: LineStyle,
85    pub orientation: LineOrientation,
86    pub wavy_line_thickness: Au,
87    pub color: ColorF,
88}
89
90impl LineDecorationData {
91    /// Per-frame preparation: derive the mask tile size from the prim's
92    /// current local size, write the GPU block, and (for non-solid styles)
93    /// allocate a cached render task. Returns the task id and the per-
94    /// instance GPU buffer address consumed by `batch.rs`.
95    pub fn prepare(
96        &self,
97        prim_size: LayoutSize,
98        prim_spatial_node_index: SpatialNodeIndex,
99        frame_context: &FrameBuildingContext,
100        frame_state: &mut FrameBuildingState,
101    ) -> (RenderTaskId, GpuBufferAddress) {
102        let cache_key = get_line_decoration_size(
103            &prim_size,
104            self.orientation,
105            self.style,
106            self.wavy_line_thickness.to_f32_px(),
107        ).map(|size| LineDecorationCacheKey {
108            style: self.style,
109            orientation: self.orientation,
110            wavy_line_thickness: self.wavy_line_thickness,
111            size: size.to_au(),
112        });
113
114        let mut writer = frame_state.frame_gpu_data.f32.write_blocks(3);
115        match cache_key.as_ref() {
116            Some(cache_key) => {
117                writer.push(&ImageBrushPrimitiveData {
118                    color: self.color.premultiplied(),
119                    background_color: PremultipliedColorF::WHITE,
120                    stretch_size: LayoutSize::new(
121                        cache_key.size.width.to_f32_px(),
122                        cache_key.size.height.to_f32_px(),
123                    ),
124                });
125            }
126            None => {
127                writer.push_one(self.color.premultiplied());
128            }
129        }
130        let gpu_address = writer.finish();
131
132        let task_id = match cache_key {
133            Some(cache_key) => self.allocate_render_task(
134                cache_key,
135                prim_spatial_node_index,
136                frame_context,
137                frame_state,
138            ),
139            None => RenderTaskId::INVALID,
140        };
141
142        (task_id, gpu_address)
143    }
144
145    fn allocate_render_task(
146        &self,
147        cache_key: LineDecorationCacheKey,
148        prim_spatial_node_index: SpatialNodeIndex,
149        frame_context: &FrameBuildingContext,
150        frame_state: &mut FrameBuildingState,
151    ) -> RenderTaskId {
152        // TODO(gw): These scale factors don't do a great job if the world transform
153        //           contains perspective
154        let scale = frame_context
155            .spatial_tree
156            .get_world_transform(prim_spatial_node_index)
157            .scale_factors();
158
159        // Scale factors are normalized to a power of 2 to reduce the number of
160        // resolution changes.
161        // For frames with a changing scale transform round scale factors up to
162        // nearest power-of-2 boundary so that we don't keep having to redraw
163        // the content as it scales up and down. Rounding up to nearest
164        // power-of-2 boundary ensures we never scale up, only down --- avoiding
165        // jaggies. It also ensures we never scale down by more than a factor of
166        // 2, avoiding bad downscaling quality.
167        let scale_width = clamp_to_scale_factor(scale.0, false);
168        let scale_height = clamp_to_scale_factor(scale.1, false);
169        // Pick the maximum dimension as scale
170        let scale_factor = LayoutToDeviceScale::new(scale_width.max(scale_height));
171
172        let task_size_f = (LayoutSize::from_au(cache_key.size) * scale_factor).ceil();
173        let mut task_size = if task_size_f.width > MAX_LINE_DECORATION_RESOLUTION as f32 ||
174            task_size_f.height > MAX_LINE_DECORATION_RESOLUTION as f32 {
175                let max_extent = task_size_f.width.max(task_size_f.height);
176                let task_scale_factor = Scale::new(MAX_LINE_DECORATION_RESOLUTION as f32 / max_extent);
177                let task_size = (LayoutSize::from_au(cache_key.size) * scale_factor * task_scale_factor)
178                            .ceil().to_i32();
179            task_size
180        } else {
181            task_size_f.to_i32()
182        };
183
184        // It's plausible, due to float accuracy issues that the line decoration may be considered
185        // visible even if the scale factors are ~0. However, the render task allocation below requires
186        // that the size of the task is > 0. To work around this, ensure that the task size is at least
187        // 1x1 pixels
188        task_size.width = task_size.width.max(1);
189        task_size.height = task_size.height.max(1);
190
191        // Request a pre-rendered image task.
192        frame_state.resource_cache.request_render_task(
193            Some(RenderTaskCacheKey {
194                origin: DeviceIntPoint::zero(),
195                size: task_size,
196                kind: RenderTaskCacheKeyKind::LineDecoration(cache_key.clone()),
197            }),
198            false,
199            RenderTaskParent::Surface,
200            &mut frame_state.frame_gpu_data.f32,
201            frame_state.rg_builder,
202            &mut frame_state.surface_builder,
203            &mut |rg_builder, _| {
204                rg_builder.add().init(RenderTask::new_dynamic(
205                    task_size,
206                    RenderTaskKind::new_line_decoration(
207                        cache_key.style,
208                        cache_key.orientation,
209                        cache_key.wavy_line_thickness.to_f32_px(),
210                        LayoutSize::from_au(cache_key.size),
211                    ),
212                ))
213            }
214        )
215    }
216}
217
218pub type LineDecorationTemplate = PrimTemplate<LineDecorationData>;
219
220impl From<LineDecorationKey> for LineDecorationTemplate {
221    fn from(line_dec: LineDecorationKey) -> Self {
222        let common = PrimTemplateCommonData::with_key_common(line_dec.common);
223        LineDecorationTemplate {
224            common,
225            kind: LineDecorationData {
226                style: line_dec.kind.style,
227                orientation: line_dec.kind.orientation,
228                wavy_line_thickness: line_dec.kind.wavy_line_thickness,
229                color: line_dec.kind.color.into(),
230            }
231        }
232    }
233}
234
235pub type LineDecorationDataHandle = intern::Handle<LineDecoration>;
236
237impl intern::Internable for LineDecoration {
238    type Key = LineDecorationKey;
239    type StoreData = LineDecorationTemplate;
240    type InternData = ();
241    const PROFILE_COUNTER: usize = crate::profiler::INTERNED_LINE_DECORATIONS;
242}
243
244impl InternablePrimitive for LineDecoration {
245    fn into_key(
246        self,
247        info: &LayoutPrimitiveInfo,
248    ) -> LineDecorationKey {
249        LineDecorationKey::new(
250            info,
251            self,
252        )
253    }
254
255    fn make_instance_kind(
256        _key: LineDecorationKey,
257        data_handle: LineDecorationDataHandle,
258        _: &mut PrimitiveStore,
259    ) -> PrimitiveKind {
260        PrimitiveKind::LineDecoration {
261            data_handle,
262        }
263    }
264}
265
266impl CreateShadow for LineDecoration {
267    fn create_shadow(
268        &self,
269        shadow: &Shadow,
270        _: bool,
271        _: RasterSpace,
272    ) -> Self {
273        LineDecoration {
274            style: self.style,
275            orientation: self.orientation,
276            wavy_line_thickness: self.wavy_line_thickness,
277            color: shadow.color.into(),
278        }
279    }
280}
281
282impl IsVisible for LineDecoration {
283    fn is_visible(&self) -> bool {
284        self.color.a > 0
285    }
286}
287
288/// Choose the decoration mask tile size for a given line.
289///
290/// Given a line with overall size `rect_size` and the given `orientation`,
291/// return the dimensions of a single mask tile for the decoration pattern
292/// described by `style` and `wavy_line_thickness`.
293///
294/// If `style` is `Solid`, no mask tile is necessary; return `None`. The other
295/// styles each have their own characteristic periods of repetition, so for each
296/// one, this function returns a `LayoutSize` with the right aspect ratio and
297/// whose specific size is convenient for the `cs_line_decoration.glsl` fragment
298/// shader to work with. The shader uses a local coordinate space in which the
299/// tile fills a rectangle with one corner at the origin, and with the size this
300/// function returns.
301///
302/// The returned size is not necessarily in pixels; device scaling and other
303/// concerns can still affect the actual task size.
304///
305/// Regardless of whether `orientation` is `Vertical` or `Horizontal`, the
306/// `width` and `height` of the returned size are always horizontal and
307/// vertical, respectively.
308pub fn get_line_decoration_size(
309    rect_size: &LayoutSize,
310    orientation: LineOrientation,
311    style: LineStyle,
312    wavy_line_thickness: f32,
313) -> Option<LayoutSize> {
314    let h = match orientation {
315        LineOrientation::Horizontal => rect_size.height,
316        LineOrientation::Vertical => rect_size.width,
317    };
318
319    // TODO(gw): The formulae below are based on the existing gecko and line
320    //           shader code. They give reasonable results for most inputs,
321    //           but could definitely do with a detailed pass to get better
322    //           quality on a wider range of inputs!
323    //           See nsCSSRendering::PaintDecorationLine in Gecko.
324
325    let (parallel, perpendicular) = match style {
326        LineStyle::Solid => {
327            return None;
328        }
329        LineStyle::Dashed => {
330            let dash_length = (3.0 * h).min(64.0).max(1.0);
331
332            (2.0 * dash_length, 4.0)
333        }
334        LineStyle::Dotted => {
335            let diameter = h.min(64.0).max(1.0);
336            let period = 2.0 * diameter;
337
338            (period, diameter)
339        }
340        LineStyle::Wavy => {
341            let line_thickness = wavy_line_thickness.max(1.0);
342            let slope_length = h - line_thickness;
343            let flat_length = ((line_thickness - 1.0) * 2.0).max(1.0);
344            let approx_period = 2.0 * (slope_length + flat_length);
345
346            (approx_period, h)
347        }
348    };
349
350    Some(match orientation {
351        LineOrientation::Horizontal => LayoutSize::new(parallel, perpendicular),
352        LineOrientation::Vertical => LayoutSize::new(perpendicular, parallel),
353    })
354}
355
356#[test]
357#[cfg(target_pointer_width = "64")]
358fn test_struct_sizes() {
359    use std::mem;
360    // The sizes of these structures are critical for performance on a number of
361    // talos stress tests. If you get a failure here on CI, there's two possibilities:
362    // (a) You made a structure smaller than it currently is. Great work! Update the
363    //     test expectations and move on.
364    // (b) You made a structure larger. This is not necessarily a problem, but should only
365    //     be done with care, and after checking if talos performance regresses badly.
366    assert_eq!(mem::size_of::<LineDecoration>(), 12, "LineDecoration size changed");
367    assert_eq!(mem::size_of::<LineDecorationTemplate>(), 32, "LineDecorationTemplate size changed");
368    assert_eq!(mem::size_of::<LineDecorationKey>(), 16, "LineDecorationKey size changed");
369}