Skip to main content

webrender/prim_store/
borders.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::{BorderStyle, NormalBorder, PremultipliedColorF, RasterSpace, Shadow};
6use api::units::*;
7use crate::border::{self, build_border_instances, get_max_scale_for_border};
8use crate::border::NormalBorderAu;
9use crate::gpu_types::ImageBrushPrimitiveData;
10use crate::render_backend::DataStores;
11use crate::render_task_cache::{RenderTaskCacheKey, RenderTaskCacheKeyKind, RenderTaskParent, to_cache_size};
12use crate::renderer::{GpuBufferAddress, GpuBufferWriterF};
13use crate::scene_building::{CreateShadow, IsVisible};
14use crate::frame_builder::{FrameBuildingContext, FrameBuildingState};
15use crate::intern;
16use crate::internal_types::{LayoutPrimitiveInfo, FrameId};
17use crate::prim_store::{
18    BorderSegmentInfo, BrushSegment, InternablePrimitive, NinePatchDescriptor, PrimKey, PrimTemplate, PrimTemplateCommonData, PrimitiveInstanceIndex, PrimitiveKind, PrimitiveOpacity, PrimitiveScratchBuffer, PrimitiveStore, VECS_PER_SEGMENT
19};
20use crate::resource_cache::ImageRequest;
21use crate::render_task::{RenderTask, RenderTaskKind};
22use crate::render_task_graph::RenderTaskId;
23use crate::spatial_tree::SpatialNodeIndex;
24use crate::util::clamp_to_scale_factor;
25use crate::visibility::KindScratchHandle;
26
27use crate::prim_store::storage;
28
29/// Per-frame scratch data for a NormalBorder primitive.
30#[derive(Copy, Clone, Debug)]
31#[cfg_attr(feature = "capture", derive(Serialize))]
32pub struct NormalBorderScratch {
33    /// Range into `PrimitiveScratchBuffer::border_task_ids` holding the
34    /// cached render-task ids for this border's segments.
35    pub task_ids: storage::Range<RenderTaskId>,
36    /// Range into `PrimitiveFrameScratch::segments` holding the per-
37    /// frame brush segments for this border. Built fresh each frame
38    /// against the prim's current size in `prepare_prim_for_render`,
39    /// so the segmentation matches the rendered rect.
40    pub brush_segments_range: storage::Range<BrushSegment>,
41    /// Range into `PrimitiveFrameScratch::border_segments` holding the
42    /// per-frame edge/corner cache-key + task-size records for this
43    /// border. Parallel to `brush_segments_range` and built alongside.
44    pub border_segments_range: storage::Range<BorderSegmentInfo>,
45    /// Per-instance GPU buffer address for the brush + segment blocks
46    /// written by `NormalBorderData::write_brush_gpu_blocks`. Per-
47    /// instance because the block contents (stretch_size and segments)
48    /// depend on the prim's per-instance size.
49    pub gpu_address: GpuBufferAddress,
50    /// True if any side uses a Dotted or Dashed style. Read by batch
51    /// to set `BatchFeatures::REPETITION` so the cached dot/dash tile
52    /// repeats across the rendered segment via brush_image.
53    pub may_need_repetition: bool,
54}
55
56impl NormalBorderScratch {
57    /// Build the per-frame brush + border segments and the parallel
58    /// task-id slot for a NormalBorder prim, push the resulting
59    /// `NormalBorderScratch` entry, and wire it up to the prim's
60    /// `PrimitiveDrawHeader.kind_scratch`.
61    ///
62    /// Called from the prep-pass before `update_clip_task` runs, since
63    /// `update_clip_task_for_brush` reads the brush segments via the
64    /// `NormalBorderScratch` allocated here. The segment list is built
65    /// against the prim's per-instance size, with the two arenas
66    /// (`scratch.frame.segments` and `scratch.frame.border_segments`)
67    /// receiving direct pushes through `data_mut` to avoid intermediate
68    /// `Vec` allocations.
69    pub fn build_for_prim(
70        data_handle: NormalBorderDataHandle,
71        prim_instance_index: PrimitiveInstanceIndex,
72        prim_size: LayoutSize,
73        data_stores: &DataStores,
74        scratch: &mut PrimitiveScratchBuffer,
75    ) {
76        let prim_data = &data_stores.normal_border[data_handle];
77        let border = &prim_data.kind.border;
78        let widths = &prim_data.kind.widths;
79
80        let brush_open = scratch.frame.segments.open_range();
81        let border_open = scratch.frame.border_segments.open_range();
82        border::create_border_segments(
83            prim_size,
84            border,
85            widths,
86            scratch.frame.border_segments.data_mut(),
87            scratch.frame.segments.data_mut(),
88        );
89        let brush_segments_range = scratch.frame.segments.close_range(brush_open);
90        let border_segments_range = scratch.frame.border_segments.close_range(border_open);
91
92        let may_need_repetition =
93            matches!(border.top.style, BorderStyle::Dotted | BorderStyle::Dashed)
94                || matches!(border.right.style, BorderStyle::Dotted | BorderStyle::Dashed)
95                || matches!(border.bottom.style, BorderStyle::Dotted | BorderStyle::Dashed)
96                || matches!(border.left.style, BorderStyle::Dotted | BorderStyle::Dashed);
97
98        let segment_count = border_segments_range.end.0
99            .saturating_sub(border_segments_range.start.0) as usize;
100        let task_ids = scratch.frame.border_task_ids.extend(
101            std::iter::repeat(RenderTaskId::INVALID).take(segment_count),
102        );
103        let handle = scratch.frame.normal_border.push(NormalBorderScratch {
104            task_ids,
105            brush_segments_range,
106            border_segments_range,
107            gpu_address: GpuBufferAddress::INVALID,
108            may_need_repetition,
109        });
110        scratch.frame.draws[prim_instance_index.0 as usize].kind_scratch =
111            KindScratchHandle::NormalBorder(handle);
112    }
113}
114
115#[cfg_attr(feature = "capture", derive(Serialize))]
116#[cfg_attr(feature = "replay", derive(Deserialize))]
117#[derive(Debug, Clone, Eq, MallocSizeOf, PartialEq, Hash)]
118pub struct NormalBorderPrim {
119    pub border: NormalBorderAu,
120    pub widths: LayoutSideOffsetsAu,
121}
122
123pub type NormalBorderKey = PrimKey<NormalBorderPrim>;
124
125impl NormalBorderKey {
126    pub fn new(
127        info: &LayoutPrimitiveInfo,
128        normal_border: NormalBorderPrim,
129    ) -> Self {
130        NormalBorderKey {
131            common: info.into(),
132            kind: normal_border,
133        }
134    }
135}
136
137impl intern::InternDebug for NormalBorderKey {}
138
139#[cfg_attr(feature = "capture", derive(Serialize))]
140#[cfg_attr(feature = "replay", derive(Deserialize))]
141#[derive(MallocSizeOf)]
142pub struct NormalBorderData {
143    pub border: NormalBorder,
144    pub widths: LayoutSideOffsets,
145}
146
147impl NormalBorderData {
148    /// Update the GPU cache for a given primitive template. This may be called multiple
149    /// times per frame, by each primitive reference that refers to this interned
150    /// template. The initial request call to the GPU cache ensures that work is only
151    /// done if the cache entry is invalid (due to first use or eviction).
152    pub fn write_brush_gpu_blocks(
153        &mut self,
154        common: &mut PrimTemplateCommonData,
155        prim_size: LayoutSize,
156        brush_segments: &[BrushSegment],
157        frame_state: &mut FrameBuildingState,
158    ) -> GpuBufferAddress {
159        let mut writer = frame_state.frame_gpu_data.f32.write_blocks(3 + brush_segments.len() * VECS_PER_SEGMENT);
160
161        // Border primitives currently used for
162        // image borders, and run through the
163        // normal brush_image shader.
164        writer.push(&ImageBrushPrimitiveData {
165            color: PremultipliedColorF::WHITE,
166            background_color: PremultipliedColorF::WHITE,
167            stretch_size: prim_size,
168        });
169
170        for segment in brush_segments {
171            segment.write_gpu_blocks(&mut writer);
172        }
173
174        let gpu_address = writer.finish();
175        common.opacity = PrimitiveOpacity::translucent();
176        gpu_address
177    }
178
179    pub fn update(
180        &mut self,
181        border_segments: &[BorderSegmentInfo],
182        prim_spatial_node_index: SpatialNodeIndex,
183        device_pixel_scale: DevicePixelScale,
184        frame_context: &FrameBuildingContext,
185        frame_state: &mut FrameBuildingState,
186        task_ids: &mut [RenderTaskId],
187    ) {
188        // TODO(gw): For now, the scale factors to rasterize borders at are
189        //           based on the true world transform of the primitive. When
190        //           raster roots with local scale are supported in future,
191        //           that will need to be accounted for here.
192        let scale = frame_context
193            .spatial_tree
194            .get_world_transform(prim_spatial_node_index)
195            .scale_factors();
196
197        // Scale factors are normalized to a power of 2 to reduce the number of
198        // resolution changes.
199        // For frames with a changing scale transform round scale factors up to
200        // nearest power-of-2 boundary so that we don't keep having to redraw
201        // the content as it scales up and down. Rounding up to nearest
202        // power-of-2 boundary ensures we never scale up, only down --- avoiding
203        // jaggies. It also ensures we never scale down by more than a factor of
204        // 2, avoiding bad downscaling quality.
205        let scale_width = clamp_to_scale_factor(scale.0, false);
206        let scale_height = clamp_to_scale_factor(scale.1, false);
207        // Pick the maximum dimension as scale
208        let world_scale = LayoutToWorldScale::new(scale_width.max(scale_height));
209        let mut scale = world_scale * device_pixel_scale;
210        let max_scale = get_max_scale_for_border(border_segments);
211        scale.0 = scale.0.min(max_scale.0);
212
213        // For each edge and corner, request the render task by content key
214        // from the render task cache. This ensures that the render task for
215        // this segment will be available for batching later in the frame.
216        // TODO: this does not ensure that segments will be in the same cache
217        // texture, though? The brush code path relies on that.
218
219        for (i, segment) in border_segments.iter().enumerate() {
220            // Update the cache key device size based on requested scale.
221            let cache_size = to_cache_size(segment.local_task_size, &mut scale);
222            let cache_key = RenderTaskCacheKey {
223                kind: RenderTaskCacheKeyKind::BorderSegment(segment.cache_key.clone()),
224                origin: DeviceIntPoint::zero(),
225                size: cache_size,
226            };
227
228            let task_id = frame_state.resource_cache.request_render_task(
229                Some(cache_key),
230                false,          // TODO(gw): We don't calculate opacity for borders yet!
231                RenderTaskParent::Surface,
232                &mut frame_state.frame_gpu_data.f32,
233                frame_state.rg_builder,
234                &mut frame_state.surface_builder,
235                &mut |rg_builder, _| {
236                    rg_builder.add().init(RenderTask::new_dynamic(
237                        cache_size,
238                        RenderTaskKind::new_border_segment(
239                            build_border_instances(
240                                &segment.cache_key,
241                                cache_size,
242                                &self.border,
243                                scale,
244                            )
245                        ),
246                    ))
247                }
248            );
249
250            task_ids[i] = task_id;
251        }
252    }
253}
254
255pub type NormalBorderTemplate = PrimTemplate<NormalBorderData>;
256
257impl From<NormalBorderKey> for NormalBorderTemplate {
258    fn from(key: NormalBorderKey) -> Self {
259        let common = PrimTemplateCommonData::with_key_common(key.common);
260
261        let mut border: NormalBorder = key.kind.border.into();
262        let widths = LayoutSideOffsets::from_au(key.kind.widths);
263
264        // FIXME(emilio): Is this the best place to do this?
265        border.normalize(&widths);
266
267        NormalBorderTemplate {
268            common,
269            kind: NormalBorderData {
270                border,
271                widths,
272            }
273        }
274    }
275}
276
277pub type NormalBorderDataHandle = intern::Handle<NormalBorderPrim>;
278
279impl intern::Internable for NormalBorderPrim {
280    type Key = NormalBorderKey;
281    type StoreData = NormalBorderTemplate;
282    type InternData = ();
283    const PROFILE_COUNTER: usize = crate::profiler::INTERNED_NORMAL_BORDERS;
284}
285
286impl InternablePrimitive for NormalBorderPrim {
287    fn into_key(
288        self,
289        info: &LayoutPrimitiveInfo,
290    ) -> NormalBorderKey {
291        NormalBorderKey::new(
292            info,
293            self,
294        )
295    }
296
297    fn make_instance_kind(
298        _key: NormalBorderKey,
299        data_handle: NormalBorderDataHandle,
300        _: &mut PrimitiveStore,
301    ) -> PrimitiveKind {
302        PrimitiveKind::NormalBorder {
303            data_handle,
304        }
305    }
306}
307
308impl CreateShadow for NormalBorderPrim {
309    fn create_shadow(
310        &self,
311        shadow: &Shadow,
312        _: bool,
313        _: RasterSpace,
314    ) -> Self {
315        let border = self.border.with_color(shadow.color.into());
316        NormalBorderPrim {
317            border,
318            widths: self.widths,
319        }
320    }
321}
322
323impl IsVisible for NormalBorderPrim {
324    fn is_visible(&self) -> bool {
325        true
326    }
327}
328
329////////////////////////////////////////////////////////////////////////////////
330
331#[cfg_attr(feature = "capture", derive(Serialize))]
332#[cfg_attr(feature = "replay", derive(Deserialize))]
333#[derive(Debug, Clone, Eq, MallocSizeOf, PartialEq, Hash)]
334pub struct ImageBorder {
335    #[ignore_malloc_size_of = "Arc"]
336    pub request: ImageRequest,
337    pub nine_patch: NinePatchDescriptor,
338}
339
340pub type ImageBorderKey = PrimKey<ImageBorder>;
341
342impl ImageBorderKey {
343    pub fn new(
344        info: &LayoutPrimitiveInfo,
345        image_border: ImageBorder,
346    ) -> Self {
347        ImageBorderKey {
348            common: info.into(),
349            kind: image_border,
350        }
351    }
352}
353
354impl intern::InternDebug for ImageBorderKey {}
355
356
357/// Per-frame scratch data for an ImageBorder primitive.
358#[derive(Copy, Clone, Debug)]
359#[cfg_attr(feature = "capture", derive(Serialize))]
360pub struct ImageBorderScratch {
361    /// Range into `PrimitiveFrameScratch::segments` holding the per-
362    /// frame nine-patch brush segments for this border. Built fresh
363    /// each frame against the prim's current size in
364    /// `prepare_prim_for_render`.
365    pub brush_segments_range: storage::Range<BrushSegment>,
366    /// Per-instance GPU buffer address for the brush + segment blocks
367    /// written by `ImageBorderData::update`. Per-instance because the
368    /// block contents (stretch_size and segments) depend on the prim's
369    /// per-instance size.
370    pub gpu_address: GpuBufferAddress,
371}
372
373impl ImageBorderScratch {
374    /// Build the per-frame nine-patch brush segments for an ImageBorder
375    /// prim, push the resulting `ImageBorderScratch` entry, and wire it
376    /// up to the prim's `PrimitiveDrawHeader.kind_scratch`.
377    ///
378    /// Called from the prep early pass before `update_clip_task` runs,
379    /// since `update_clip_task_for_brush` reads the brush segments via
380    /// the scratch entry allocated here.
381    pub fn build_for_prim(
382        data_handle: ImageBorderDataHandle,
383        prim_instance_index: PrimitiveInstanceIndex,
384        prim_size: LayoutSize,
385        data_stores: &DataStores,
386        scratch: &mut PrimitiveScratchBuffer,
387    ) {
388        let prim_data = &data_stores.image_border[data_handle];
389        let nine_patch = &prim_data.kind.nine_patch;
390
391        let brush_open = scratch.frame.segments.open_range();
392        scratch.frame.segments.data_mut().extend(
393            nine_patch.create_brush_segments(prim_size),
394        );
395        let brush_segments_range = scratch.frame.segments.close_range(brush_open);
396
397        let handle = scratch.frame.image_border.push(ImageBorderScratch {
398            brush_segments_range,
399            gpu_address: GpuBufferAddress::INVALID,
400        });
401        scratch.frame.draws[prim_instance_index.0 as usize].kind_scratch =
402            KindScratchHandle::ImageBorder(handle);
403    }
404}
405
406#[cfg_attr(feature = "capture", derive(Serialize))]
407#[cfg_attr(feature = "replay", derive(Deserialize))]
408#[derive(MallocSizeOf)]
409pub struct ImageBorderData {
410    #[ignore_malloc_size_of = "Arc"]
411    pub request: ImageRequest,
412    pub nine_patch: NinePatchDescriptor,
413    pub src_color: Option<RenderTaskId>,
414    pub frame_id: FrameId,
415    pub is_opaque: bool,
416}
417
418impl ImageBorderData {
419    /// Update the GPU cache for a given primitive template. This may be called multiple
420    /// times per frame, by each primitive reference that refers to this interned
421    /// template. The initial request call to the GPU cache ensures that work is only
422    /// done if the cache entry is invalid (due to first use or eviction).
423    pub fn update(
424        &mut self,
425        common: &mut PrimTemplateCommonData,
426        prim_size: LayoutSize,
427        brush_segments: &[BrushSegment],
428        frame_state: &mut FrameBuildingState,
429    ) -> GpuBufferAddress {
430        let mut writer = frame_state.frame_gpu_data.f32.write_blocks(3 + brush_segments.len() * VECS_PER_SEGMENT);
431        self.write_prim_gpu_blocks(&mut writer, &prim_size);
432        Self::write_segment_gpu_blocks(&mut writer, brush_segments);
433        let gpu_address = writer.finish();
434
435        let frame_id = frame_state.rg_builder.frame_id();
436        if self.frame_id != frame_id {
437            self.frame_id = frame_id;
438
439            let size = frame_state.resource_cache.request_image(
440                self.request,
441                &mut frame_state.frame_gpu_data.f32,
442            );
443
444            let task_id = frame_state.rg_builder.add().init(
445                RenderTask::new_image(size, self.request, false)
446            );
447
448            self.src_color = Some(task_id);
449
450            let image_properties = frame_state
451                .resource_cache
452                .get_image_properties(self.request.key);
453
454            self.is_opaque = image_properties
455                .map(|properties| properties.descriptor.is_opaque())
456                .unwrap_or(true);
457        }
458
459        common.opacity = PrimitiveOpacity { is_opaque: self.is_opaque };
460        gpu_address
461    }
462
463    fn write_prim_gpu_blocks(
464        &self,
465        writer: &mut GpuBufferWriterF,
466        prim_size: &LayoutSize,
467    ) {
468        // Border primitives currently used for
469        // image borders, and run through the
470        // normal brush_image shader.
471        writer.push(&ImageBrushPrimitiveData {
472            color: PremultipliedColorF::WHITE,
473            background_color: PremultipliedColorF::WHITE,
474            stretch_size: *prim_size,
475        });
476    }
477
478    fn write_segment_gpu_blocks(
479        writer: &mut GpuBufferWriterF,
480        brush_segments: &[BrushSegment],
481    ) {
482        for segment in brush_segments {
483            segment.write_gpu_blocks(writer);
484        }
485    }
486}
487
488pub type ImageBorderTemplate = PrimTemplate<ImageBorderData>;
489
490impl From<ImageBorderKey> for ImageBorderTemplate {
491    fn from(key: ImageBorderKey) -> Self {
492        let common = PrimTemplateCommonData::with_key_common(key.common);
493
494        ImageBorderTemplate {
495            common,
496            kind: ImageBorderData {
497                request: key.kind.request,
498                nine_patch: key.kind.nine_patch,
499                src_color: None,
500                frame_id: FrameId::INVALID,
501                is_opaque: false,
502            }
503        }
504    }
505}
506
507pub type ImageBorderDataHandle = intern::Handle<ImageBorder>;
508
509impl intern::Internable for ImageBorder {
510    type Key = ImageBorderKey;
511    type StoreData = ImageBorderTemplate;
512    type InternData = ();
513    const PROFILE_COUNTER: usize = crate::profiler::INTERNED_IMAGE_BORDERS;
514}
515
516impl InternablePrimitive for ImageBorder {
517    fn into_key(
518        self,
519        info: &LayoutPrimitiveInfo,
520    ) -> ImageBorderKey {
521        ImageBorderKey::new(
522            info,
523            self,
524        )
525    }
526
527    fn make_instance_kind(
528        _key: ImageBorderKey,
529        data_handle: ImageBorderDataHandle,
530        _: &mut PrimitiveStore,
531    ) -> PrimitiveKind {
532        PrimitiveKind::ImageBorder {
533            data_handle
534        }
535    }
536}
537
538impl IsVisible for ImageBorder {
539    fn is_visible(&self) -> bool {
540        true
541    }
542}
543
544#[test]
545#[cfg(target_pointer_width = "64")]
546fn test_struct_sizes() {
547    use std::mem;
548    // The sizes of these structures are critical for performance on a number of
549    // talos stress tests. If you get a failure here on CI, there's two possibilities:
550    // (a) You made a structure smaller than it currently is. Great work! Update the
551    //     test expectations and move on.
552    // (b) You made a structure larger. This is not necessarily a problem, but should only
553    //     be done with care, and after checking if talos performance regresses badly.
554    assert_eq!(mem::size_of::<NormalBorderPrim>(), 84, "NormalBorderPrim size changed");
555    assert_eq!(mem::size_of::<NormalBorderTemplate>(), 140, "NormalBorderTemplate size changed");
556    assert_eq!(mem::size_of::<NormalBorderKey>(), 88, "NormalBorderKey size changed");
557    assert_eq!(mem::size_of::<ImageBorder>(), 68, "ImageBorder size changed");
558    assert_eq!(mem::size_of::<ImageBorderTemplate>(), 104, "ImageBorderTemplate size changed");
559    assert_eq!(mem::size_of::<ImageBorderKey>(), 72, "ImageBorderKey size changed");
560}