zng_view/
display_list.rs

1use std::{cell::Cell, mem};
2
3use rustc_hash::FxHashMap;
4use webrender::api as wr;
5use zng_unit::{Factor, Px, PxCornerRadius, PxRect, PxSideOffsets, PxSize, PxTransform, Rgba};
6use zng_view_api::{
7    GradientStop, ReferenceFrameId, RepeatMode,
8    api_extension::{ApiExtensionId, ApiExtensionPayload},
9    display_list::{DisplayItem, DisplayList, FilterOp, FrameValue, FrameValueId, FrameValueUpdate, NinePatchSource, SegmentId},
10    font::{GlyphIndex, GlyphInstance},
11    window::FrameId,
12};
13
14use crate::px_wr::PxToWr;
15
16pub fn display_list_to_webrender(
17    list: DisplayList,
18    ext: &mut dyn DisplayListExtension,
19    cache: &mut DisplayListCache,
20) -> wr::BuiltDisplayList {
21    let r = display_list_build(&list, cache, ext, false);
22    cache.insert(list);
23
24    r
25}
26
27/// Handler for display list extension items.
28///
29/// Note that this is for extensions that still use Webrender, to generate normal
30/// Webrender items or *blobs* that are Webrender's own extension mechanism.
31/// Custom renderers can just inspect the display list directly.
32///
33/// This trait is implemented for `()` for view implementations that don't provide any extension.
34pub trait DisplayListExtension {
35    /// Handle new display list starting.
36    ///
37    /// This is called for every list, even if the list does not have any item for the extension.
38    fn display_list_start(&mut self, args: &mut DisplayExtensionArgs) {
39        let _ = args;
40    }
41
42    /// Handle extension push.
43    ///
44    /// This is only called for items addressing the extension.
45    fn push_display_item(&mut self, args: &mut DisplayExtensionItemArgs);
46    /// Handle extension pop.
47    ///
48    /// This is only called for items addressing the extension.
49    fn pop_display_item(&mut self, args: &mut DisplayExtensionItemArgs) {
50        let _ = args;
51    }
52
53    /// Handle display list finishing.
54    fn display_list_end(&mut self, args: &mut DisplayExtensionArgs) {
55        let _ = args;
56    }
57
58    /// Handle extension update.
59    fn update(&mut self, args: &mut DisplayExtensionUpdateArgs) {
60        let _ = args;
61    }
62}
63impl DisplayListExtension for () {
64    fn push_display_item(&mut self, args: &mut DisplayExtensionItemArgs) {
65        let _ = args;
66    }
67}
68
69/// Arguments for [`DisplayListExtension`] begin/end list.
70pub struct DisplayExtensionArgs<'a> {
71    /// The webrender display list.
72    pub list: &'a mut wr::DisplayListBuilder,
73    /// Space and clip tracker.
74    pub sc: &'a mut SpaceAndClip,
75}
76
77/// Arguments for [`DisplayListExtension`] push and pop.
78pub struct DisplayExtensionItemArgs<'a> {
79    /// Extension index.
80    pub extension_id: ApiExtensionId,
81    /// Push payload, is empty for pop.
82    pub payload: &'a ApiExtensionPayload,
83    /// If the display item is reused.
84    ///
85    /// If `true` the payload is the same as received before any updates, the updated
86    /// values must be applied to value deserialized from the payload.
87    pub is_reuse: bool,
88    /// The webrender display list.
89    pub list: &'a mut wr::DisplayListBuilder,
90    /// Space and clip tracker.
91    pub sc: &'a mut SpaceAndClip,
92}
93
94/// Arguments for [`DisplayListExtension`] update.
95pub struct DisplayExtensionUpdateArgs<'a> {
96    /// Extension index.
97    pub extension_id: ApiExtensionId,
98    /// Update payload.
99    pub payload: &'a ApiExtensionPayload,
100
101    /// Set to `true` to rebuild the display list.
102    ///
103    /// The list will be rebuild using the last full payload received, the extension
104    /// must patch in any subsequent updates onto this value.
105    pub new_frame: bool,
106
107    /// Webrender binding updates.
108    ///
109    /// If no other extension and update handlers request a new frame these properties
110    /// will be send to Webrender to update the current frame.
111    pub properties: &'a mut wr::DynamicProperties,
112}
113
114/// Tracks the current space & clip chain, and the backface visibility primitive flag.
115pub struct SpaceAndClip {
116    spatial_stack: Vec<wr::SpatialId>,
117    clip_stack: Vec<wr::ClipId>,
118    clip_chain_stack: Vec<(wr::ClipChainId, usize)>,
119    prim_flags: wr::PrimitiveFlags,
120    view_process_frame_id: u64,
121}
122impl SpaceAndClip {
123    pub(crate) fn new(pipeline_id: wr::PipelineId) -> Self {
124        let sid = wr::SpatialId::root_reference_frame(pipeline_id);
125        SpaceAndClip {
126            spatial_stack: vec![sid],
127            clip_stack: vec![],
128            clip_chain_stack: vec![],
129            prim_flags: wr::PrimitiveFlags::IS_BACKFACE_VISIBLE,
130            view_process_frame_id: 0,
131        }
132    }
133
134    /// Current space.
135    pub fn spatial_id(&self) -> wr::SpatialId {
136        self.spatial_stack[self.spatial_stack.len() - 1]
137    }
138
139    /// Current clip chain.
140    pub fn clip_chain_id(&mut self, list: &mut wr::DisplayListBuilder) -> wr::ClipChainId {
141        let mut start = 0;
142        let mut parent = None;
143
144        if let Some((id, i)) = self.clip_chain_stack.last().copied() {
145            if i == self.clip_stack.len() {
146                return id;
147            } else {
148                start = i;
149                parent = Some(id);
150            }
151        }
152
153        let clips = self.clip_stack[start..].iter().copied();
154        let id = list.define_clip_chain(parent, clips);
155        self.clip_chain_stack.push((id, self.clip_stack.len()));
156
157        id
158    }
159
160    /// Push space.
161    pub fn push_spatial(&mut self, spatial_id: wr::SpatialId) {
162        self.spatial_stack.push(spatial_id);
163    }
164
165    /// Pop space.
166    pub fn pop_spatial(&mut self) {
167        self.spatial_stack.truncate(self.spatial_stack.len() - 1);
168    }
169
170    /// Push clip.
171    pub fn push_clip(&mut self, clip: wr::ClipId) {
172        self.clip_stack.push(clip);
173    }
174
175    /// Pop clip.
176    pub fn pop_clip(&mut self) {
177        self.clip_stack.truncate(self.clip_stack.len() - 1);
178
179        if let Some((_, i)) = self.clip_chain_stack.last() {
180            if *i > self.clip_stack.len() {
181                self.clip_chain_stack.truncate(self.clip_chain_stack.len() - 1);
182            }
183        }
184    }
185
186    /// Gets the primitive flags for the item.
187    pub fn primitive_flags(&self) -> wr::PrimitiveFlags {
188        self.prim_flags
189    }
190
191    /// Set the `IS_BACKFACE_VISIBLE` flag to the next items.
192    pub fn set_backface_visibility(&mut self, visible: bool) {
193        self.prim_flags.set(wr::PrimitiveFlags::IS_BACKFACE_VISIBLE, visible);
194    }
195
196    /// Generate a reference frame ID, unique on this list.
197    pub fn next_view_process_frame_id(&mut self) -> ReferenceFrameId {
198        self.view_process_frame_id = self.view_process_frame_id.wrapping_add(1);
199        ReferenceFrameId(self.view_process_frame_id, 1 << 63)
200    }
201
202    pub(crate) fn clear(&mut self, pipeline_id: wr::PipelineId) {
203        #[cfg(debug_assertions)]
204        {
205            if self.clip_chain_stack.len() >= 2 {
206                tracing::error!("found {} clip chains, expected 0 or 1", self.clip_chain_stack.len());
207            }
208            if !self.clip_stack.is_empty() {
209                tracing::error!("found {} clips, expected 0", self.clip_stack.len());
210            }
211            if self.spatial_stack.len() != 1 {
212                tracing::error!("found {} spatial, expected 1 root_reference_frame", self.spatial_stack.len());
213            } else if self.spatial_stack[0].0 != 0 {
214                tracing::error!("found other spatial id, expected root_reference_frame");
215            }
216        }
217
218        self.clip_stack.clear();
219
220        self.spatial_stack.clear();
221        self.spatial_stack.push(wr::SpatialId::root_reference_frame(pipeline_id));
222
223        self.clip_chain_stack.clear();
224
225        self.prim_flags = wr::PrimitiveFlags::IS_BACKFACE_VISIBLE;
226    }
227}
228struct CachedDisplayList {
229    list: Vec<DisplayItem>,
230    segments: Vec<(SegmentId, usize)>,
231    used: Cell<bool>,
232}
233
234/// View process side cache of [`DisplayList`] frames for a pipeline.
235pub struct DisplayListCache {
236    pipeline_id: wr::PipelineId,
237    id_namespace: wr::IdNamespace,
238    lists: FxHashMap<FrameId, CachedDisplayList>,
239    space_and_clip: Option<SpaceAndClip>,
240
241    latest_frame: FrameId,
242    bindings: FxHashMap<FrameValueId, (FrameId, usize)>,
243
244    wr_list: Option<wr::DisplayListBuilder>,
245}
246impl DisplayListCache {
247    /// New empty.
248    pub fn new(pipeline_id: wr::PipelineId, id_namespace: wr::IdNamespace) -> Self {
249        DisplayListCache {
250            pipeline_id,
251            id_namespace,
252            lists: FxHashMap::default(),
253            latest_frame: FrameId::INVALID,
254            space_and_clip: Some(SpaceAndClip::new(pipeline_id)),
255            bindings: FxHashMap::default(),
256            wr_list: Some(wr::DisplayListBuilder::new(pipeline_id)),
257        }
258    }
259
260    /// Keys namespace.
261    pub fn id_namespace(&self) -> wr::IdNamespace {
262        self.id_namespace
263    }
264
265    fn begin_wr(&mut self) -> (wr::DisplayListBuilder, SpaceAndClip) {
266        let mut list = self.wr_list.take().unwrap();
267        let sc = self.space_and_clip.take().unwrap();
268        list.begin();
269        (list, sc)
270    }
271
272    fn end_wr(&mut self, mut list: wr::DisplayListBuilder, mut sc: SpaceAndClip) -> wr::BuiltDisplayList {
273        let r = list.end().1;
274        self.wr_list = Some(list);
275        sc.clear(self.pipeline_id);
276        self.space_and_clip = Some(sc);
277        r
278    }
279
280    #[expect(clippy::too_many_arguments)]
281    fn reuse(
282        &self,
283        frame_id: FrameId,
284        seg_id: SegmentId,
285        mut start: usize,
286        mut end: usize,
287        wr_list: &mut wr::DisplayListBuilder,
288        ext: &mut dyn DisplayListExtension,
289        sc: &mut SpaceAndClip,
290    ) {
291        if let Some(l) = self.lists.get(&frame_id) {
292            l.used.set(true);
293
294            let offset = l
295                .segments
296                .iter()
297                .find_map(|&(id, o)| if id == seg_id { Some(o) } else { None })
298                .unwrap_or_else(|| {
299                    tracing::error!("unknown segment id {seg_id}");
300                    l.list.len()
301                });
302            start += offset;
303            end += offset;
304
305            let range = l.list.get(start..end).unwrap_or_else(|| {
306                tracing::error!("invalid reuse range ({start}..{end}) ignored, offset: {offset}");
307                &[]
308            });
309            for item in range {
310                display_item_to_webrender(item, wr_list, ext, sc, self, true);
311            }
312        } else {
313            tracing::error!("did not find reuse frame {frame_id:?}");
314        }
315    }
316
317    fn insert(&mut self, list: DisplayList) {
318        self.lists.retain(|_, l| l.used.take());
319
320        let (frame_id, list, segments) = list.into_parts();
321
322        for (i, item) in list.iter().enumerate() {
323            display_item_register_bindings(item, &mut self.bindings, (frame_id, i));
324        }
325
326        self.latest_frame = frame_id;
327        self.lists.insert(
328            frame_id,
329            CachedDisplayList {
330                list,
331                segments,
332                used: Cell::new(false),
333            },
334        );
335    }
336
337    fn get_update_target(&mut self, id: FrameValueId) -> Option<&mut DisplayItem> {
338        if let Some((frame_id, i)) = self.bindings.get(&id) {
339            if let Some(list) = self.lists.get_mut(frame_id) {
340                if let Some(item) = list.list.get_mut(*i) {
341                    return Some(item);
342                }
343            }
344        }
345        None
346    }
347
348    /// Apply updates, returns the webrender update if the renderer can also be updated and there are any updates,
349    /// or returns a new frame if a new frame must be rendered.
350    #[expect(clippy::result_large_err)] // both are large
351    pub fn update(
352        &mut self,
353        ext: &mut dyn DisplayListExtension,
354        transforms: Vec<FrameValueUpdate<PxTransform>>,
355        floats: Vec<FrameValueUpdate<f32>>,
356        colors: Vec<FrameValueUpdate<Rgba>>,
357        extensions: Vec<(ApiExtensionId, ApiExtensionPayload)>,
358        resized: bool,
359    ) -> Result<Option<wr::DynamicProperties>, wr::BuiltDisplayList> {
360        let mut new_frame = resized;
361
362        for t in &transforms {
363            if let Some(item) = self.get_update_target(t.id) {
364                new_frame |= item.update_transform(t);
365            }
366        }
367        for t in &floats {
368            if let Some(item) = self.get_update_target(t.id) {
369                new_frame |= item.update_float(t);
370            }
371        }
372        for t in &colors {
373            if let Some(item) = self.get_update_target(t.id) {
374                new_frame |= item.update_color(t);
375            }
376        }
377
378        let mut properties = wr::DynamicProperties::default();
379
380        for (k, e) in &extensions {
381            let mut args = DisplayExtensionUpdateArgs {
382                extension_id: *k,
383                payload: e,
384                new_frame: false,
385                properties: &mut properties,
386            };
387            ext.update(&mut args);
388            new_frame |= args.new_frame;
389        }
390
391        if new_frame {
392            let list = self.lists.get_mut(&self.latest_frame).expect("no frame to update");
393            let list = mem::take(&mut list.list);
394            let r = display_list_build(&list, self, ext, true);
395            self.lists.get_mut(&self.latest_frame).unwrap().list = list;
396
397            Err(r)
398        } else {
399            properties.transforms.extend(transforms.into_iter().filter_map(PxToWr::to_wr));
400            properties.floats.extend(floats.into_iter().filter_map(PxToWr::to_wr));
401            properties.colors.extend(colors.into_iter().filter_map(PxToWr::to_wr));
402
403            if properties.transforms.is_empty() && properties.floats.is_empty() && properties.colors.is_empty() {
404                Ok(None)
405            } else {
406                Ok(Some(properties))
407            }
408        }
409    }
410}
411
412fn display_list_build(
413    list: &[DisplayItem],
414    cache: &mut DisplayListCache,
415    ext: &mut dyn DisplayListExtension,
416    is_reuse: bool,
417) -> wr::BuiltDisplayList {
418    let _s = tracing::trace_span!("DisplayList::build").entered();
419
420    let (mut wr_list, mut sc) = cache.begin_wr();
421
422    ext.display_list_start(&mut DisplayExtensionArgs {
423        list: &mut wr_list,
424        sc: &mut sc,
425    });
426
427    for item in list {
428        display_item_to_webrender(item, &mut wr_list, ext, &mut sc, cache, is_reuse);
429    }
430
431    ext.display_list_end(&mut DisplayExtensionArgs {
432        list: &mut wr_list,
433        sc: &mut sc,
434    });
435
436    cache.end_wr(wr_list, sc)
437}
438
439fn display_item_to_webrender(
440    item: &DisplayItem,
441    wr_list: &mut wr::DisplayListBuilder,
442    ext: &mut dyn DisplayListExtension,
443    sc: &mut SpaceAndClip,
444    cache: &DisplayListCache,
445    is_reuse: bool,
446) {
447    match item {
448        DisplayItem::Reuse {
449            frame_id,
450            seg_id,
451            start,
452            end,
453        } => cache.reuse(*frame_id, *seg_id, *start, *end, wr_list, ext, sc),
454
455        DisplayItem::PushReferenceFrame {
456            id,
457            transform,
458            transform_style,
459            is_2d_scale_translation,
460        } => {
461            let spatial_id = wr_list.push_reference_frame(
462                wr::units::LayoutPoint::zero(),
463                sc.spatial_id(),
464                transform_style.to_wr(),
465                transform.to_wr(),
466                wr::ReferenceFrameKind::Transform {
467                    is_2d_scale_translation: *is_2d_scale_translation,
468                    should_snap: false,
469                    paired_with_perspective: false,
470                },
471                id.to_wr(),
472            );
473            sc.push_spatial(spatial_id);
474        }
475        DisplayItem::PopReferenceFrame => {
476            wr_list.pop_reference_frame();
477            sc.pop_spatial();
478        }
479
480        DisplayItem::PushStackingContext {
481            blend_mode,
482            transform_style,
483            filters,
484        } => {
485            let clip = sc.clip_chain_id(wr_list);
486            wr_list.push_stacking_context(
487                wr::units::LayoutPoint::zero(),
488                sc.spatial_id(),
489                sc.primitive_flags(),
490                Some(clip),
491                transform_style.to_wr(),
492                blend_mode.to_wr(),
493                &filters.iter().map(|f| f.to_wr()).collect::<Vec<_>>(),
494                &[],
495                &[],
496                wr::RasterSpace::Screen, // Local disables sub-pixel AA for performance (future perf.)
497                wr::StackingContextFlags::empty(),
498            )
499        }
500        DisplayItem::PopStackingContext => wr_list.pop_stacking_context(),
501
502        DisplayItem::PushClipRect { clip_rect, clip_out } => {
503            let clip_id = if *clip_out {
504                wr_list.define_clip_rounded_rect(
505                    sc.spatial_id(),
506                    wr::ComplexClipRegion::new(clip_rect.to_wr(), PxCornerRadius::zero().to_wr(), wr::ClipMode::ClipOut),
507                )
508            } else {
509                wr_list.define_clip_rect(sc.spatial_id(), clip_rect.to_wr())
510            };
511
512            sc.push_clip(clip_id);
513        }
514        DisplayItem::PushClipRoundedRect {
515            clip_rect,
516            corners,
517            clip_out,
518        } => {
519            let clip_id = wr_list.define_clip_rounded_rect(
520                sc.spatial_id(),
521                wr::ComplexClipRegion::new(
522                    clip_rect.to_wr(),
523                    corners.to_wr(),
524                    if *clip_out { wr::ClipMode::ClipOut } else { wr::ClipMode::Clip },
525                ),
526            );
527            sc.push_clip(clip_id);
528        }
529        DisplayItem::PopClip => sc.pop_clip(),
530
531        DisplayItem::PushMask { image_id, rect } => {
532            let clip_id = wr_list.define_clip_image_mask(
533                sc.spatial_id(),
534                wr::ImageMask {
535                    image: wr::ImageKey(cache.id_namespace(), image_id.get()),
536                    rect: rect.to_wr(),
537                },
538                &[],
539                wr::FillRule::Nonzero,
540            );
541            sc.push_clip(clip_id);
542            let clip = sc.clip_chain_id(wr_list);
543            wr_list.push_stacking_context(
544                wr::units::LayoutPoint::zero(),
545                sc.spatial_id(),
546                sc.primitive_flags(),
547                Some(clip),
548                wr::TransformStyle::Flat,
549                wr::MixBlendMode::Normal,
550                &[],
551                &[],
552                &[],
553                wr::RasterSpace::Screen,
554                wr::StackingContextFlags::empty(),
555            );
556        }
557        DisplayItem::PopMask => {
558            wr_list.pop_stacking_context();
559            sc.pop_clip();
560        }
561
562        DisplayItem::SetBackfaceVisibility { visible } => {
563            sc.set_backface_visibility(*visible);
564        }
565
566        DisplayItem::Text {
567            clip_rect,
568            font_id,
569            glyphs,
570            color,
571            options,
572        } => {
573            let bounds = clip_rect.to_wr();
574            let clip = sc.clip_chain_id(wr_list);
575            wr_list.push_text(
576                &wr::CommonItemProperties {
577                    clip_rect: bounds,
578                    clip_chain_id: clip,
579                    spatial_id: sc.spatial_id(),
580                    flags: sc.primitive_flags(),
581                },
582                bounds,
583                cast_glyphs_to_wr(glyphs),
584                wr::FontInstanceKey(cache.id_namespace(), font_id.get()),
585                color.into_value().to_wr(),
586                options.clone().to_wr_world(),
587            );
588        }
589
590        DisplayItem::Color { clip_rect, color } => {
591            let bounds = clip_rect.to_wr();
592            let clip = sc.clip_chain_id(wr_list);
593            wr_list.push_rect_with_animation(
594                &wr::CommonItemProperties {
595                    clip_rect: bounds,
596                    clip_chain_id: clip,
597                    spatial_id: sc.spatial_id(),
598                    flags: sc.primitive_flags(),
599                },
600                bounds,
601                color.to_wr(),
602            )
603        }
604        DisplayItem::BackdropFilter { clip_rect, filters } => {
605            let bounds = clip_rect.to_wr();
606            let clip = sc.clip_chain_id(wr_list);
607            wr_list.push_backdrop_filter(
608                &wr::CommonItemProperties {
609                    clip_rect: bounds,
610                    clip_chain_id: clip,
611                    spatial_id: sc.spatial_id(),
612                    flags: sc.primitive_flags(),
613                },
614                &filters.iter().map(|f| f.to_wr()).collect::<Vec<_>>(),
615                &[],
616                &[],
617            )
618        }
619
620        DisplayItem::Border {
621            bounds,
622            widths,
623            sides: [top, right, bottom, left],
624            radius,
625        } => {
626            let bounds = bounds.to_wr();
627            let clip = sc.clip_chain_id(wr_list);
628            wr_list.push_border(
629                &wr::CommonItemProperties {
630                    clip_rect: bounds,
631                    clip_chain_id: clip,
632                    spatial_id: sc.spatial_id(),
633                    flags: sc.primitive_flags(),
634                },
635                bounds,
636                widths.to_wr(),
637                wr::BorderDetails::Normal(wr::NormalBorder {
638                    left: left.to_wr(),
639                    right: right.to_wr(),
640                    top: top.to_wr(),
641                    bottom: bottom.to_wr(),
642                    radius: radius.to_wr(),
643                    do_aa: true,
644                }),
645            );
646        }
647        DisplayItem::NinePatchBorder {
648            source,
649            bounds,
650            widths,
651            img_size,
652            fill,
653            slice,
654            repeat_horizontal,
655            repeat_vertical,
656        } => {
657            nine_patch_border_to_webrender(
658                sc,
659                wr_list,
660                source,
661                cache,
662                *bounds,
663                *widths,
664                *repeat_horizontal,
665                *slice,
666                *img_size,
667                *repeat_vertical,
668                *fill,
669            );
670        }
671
672        DisplayItem::Image {
673            clip_rect,
674            image_id,
675            image_size,
676            rendering,
677            alpha_type,
678            tile_size,
679            tile_spacing,
680        } => {
681            let bounds = clip_rect.to_wr();
682            let clip = sc.clip_chain_id(wr_list);
683            let props = wr::CommonItemProperties {
684                clip_rect: bounds,
685                clip_chain_id: clip,
686                spatial_id: sc.spatial_id(),
687                flags: sc.primitive_flags(),
688            };
689
690            if tile_spacing.is_empty() && tile_size == image_size {
691                wr_list.push_image(
692                    &props,
693                    PxRect::from_size(*image_size).to_wr(),
694                    rendering.to_wr(),
695                    alpha_type.to_wr(),
696                    wr::ImageKey(cache.id_namespace(), image_id.get()),
697                    wr::ColorF::WHITE,
698                );
699            } else {
700                wr_list.push_repeating_image(
701                    &props,
702                    PxRect::from_size(*image_size).to_wr(),
703                    tile_size.to_wr(),
704                    tile_spacing.to_wr(),
705                    rendering.to_wr(),
706                    alpha_type.to_wr(),
707                    wr::ImageKey(cache.id_namespace(), image_id.get()),
708                    wr::ColorF::WHITE,
709                );
710            }
711        }
712
713        DisplayItem::LinearGradient {
714            clip_rect,
715            start_point,
716            end_point,
717            extend_mode,
718            stops,
719            tile_origin,
720            tile_size,
721            tile_spacing,
722        } => {
723            let mut tile_origin = *tile_origin;
724            tile_origin.x.0 = tile_origin.x.0.rem_euclid(tile_size.width.0);
725            tile_origin.y.0 = tile_origin.y.0.rem_euclid(tile_size.height.0);
726            let bounds = PxRect::new(
727                -tile_origin + clip_rect.origin.to_vector(),
728                clip_rect.size + tile_origin.to_vector().to_size(),
729            )
730            .to_wr();
731
732            let clip = sc.clip_chain_id(wr_list);
733            // stops needs to be immediately followed by the gradient, if the clip-chain item
734            // is inserted in the between the stops are lost.
735            wr_list.push_stops(cast_gradient_stops_to_wr(stops));
736            wr_list.push_gradient(
737                &wr::CommonItemProperties {
738                    clip_rect: clip_rect.to_wr(),
739                    clip_chain_id: clip,
740                    spatial_id: sc.spatial_id(),
741                    flags: sc.primitive_flags(),
742                },
743                bounds,
744                wr::Gradient {
745                    start_point: start_point.cast_unit(),
746                    end_point: end_point.cast_unit(),
747                    extend_mode: extend_mode.to_wr(),
748                },
749                tile_size.to_wr(),
750                tile_spacing.to_wr(),
751            )
752        }
753        DisplayItem::RadialGradient {
754            clip_rect,
755            center,
756            radius,
757            start_offset,
758            end_offset,
759            extend_mode,
760            stops,
761            tile_origin,
762            tile_size,
763            tile_spacing,
764        } => {
765            let mut tile_origin = *tile_origin;
766            tile_origin.x.0 = tile_origin.x.0.rem_euclid(tile_size.width.0);
767            tile_origin.y.0 = tile_origin.y.0.rem_euclid(tile_size.height.0);
768            let bounds = PxRect::new(
769                -tile_origin + clip_rect.origin.to_vector(),
770                clip_rect.size + tile_origin.to_vector().to_size(),
771            )
772            .to_wr();
773
774            let clip = sc.clip_chain_id(wr_list);
775            wr_list.push_stops(cast_gradient_stops_to_wr(stops));
776            wr_list.push_radial_gradient(
777                &wr::CommonItemProperties {
778                    clip_rect: clip_rect.to_wr(),
779                    clip_chain_id: clip,
780                    spatial_id: sc.spatial_id(),
781                    flags: sc.primitive_flags(),
782                },
783                bounds,
784                wr::RadialGradient {
785                    center: center.cast_unit(),
786                    radius: radius.cast_unit(),
787                    start_offset: *start_offset,
788                    end_offset: *end_offset,
789                    extend_mode: extend_mode.to_wr(),
790                },
791                tile_size.to_wr(),
792                tile_spacing.to_wr(),
793            )
794        }
795        DisplayItem::ConicGradient {
796            clip_rect,
797            center,
798            angle,
799            start_offset,
800            end_offset,
801            extend_mode,
802            stops,
803            tile_origin,
804            tile_size,
805            tile_spacing,
806        } => {
807            let mut tile_origin = *tile_origin;
808            tile_origin.x.0 = tile_origin.x.0.rem_euclid(tile_size.width.0);
809            tile_origin.y.0 = tile_origin.y.0.rem_euclid(tile_size.height.0);
810            let bounds = PxRect::new(
811                -tile_origin + clip_rect.origin.to_vector(),
812                clip_rect.size + tile_origin.to_vector().to_size(),
813            )
814            .to_wr();
815
816            let clip = sc.clip_chain_id(wr_list);
817            wr_list.push_stops(cast_gradient_stops_to_wr(stops));
818            wr_list.push_conic_gradient(
819                &wr::CommonItemProperties {
820                    clip_rect: clip_rect.to_wr(),
821                    clip_chain_id: clip,
822                    spatial_id: sc.spatial_id(),
823                    flags: sc.primitive_flags(),
824                },
825                bounds,
826                wr::ConicGradient {
827                    center: center.cast_unit(),
828                    angle: angle.0,
829                    start_offset: *start_offset,
830                    end_offset: *end_offset,
831                    extend_mode: extend_mode.to_wr(),
832                },
833                tile_size.to_wr(),
834                tile_spacing.to_wr(),
835            )
836        }
837        DisplayItem::Line {
838            clip_rect,
839            color,
840            style,
841            orientation,
842        } => {
843            let bounds = clip_rect.to_wr();
844            let clip = sc.clip_chain_id(wr_list);
845            let (line_style, wavy_line_thickness) = style.to_wr();
846            wr_list.push_line(
847                &wr::CommonItemProperties {
848                    clip_rect: bounds,
849                    clip_chain_id: clip,
850                    spatial_id: sc.spatial_id(),
851                    flags: sc.primitive_flags(),
852                },
853                &bounds,
854                wavy_line_thickness,
855                orientation.to_wr(),
856                &color.to_wr(),
857                line_style,
858            );
859        }
860        DisplayItem::PushExtension { extension_id, payload } => ext.push_display_item(&mut DisplayExtensionItemArgs {
861            extension_id: *extension_id,
862            payload,
863            is_reuse,
864            list: wr_list,
865            sc,
866        }),
867        DisplayItem::PopExtension { extension_id } => ext.pop_display_item(&mut DisplayExtensionItemArgs {
868            extension_id: *extension_id,
869            payload: &ApiExtensionPayload::empty(),
870            is_reuse,
871            list: wr_list,
872            sc,
873        }),
874    }
875}
876
877#[allow(clippy::too_many_arguments)]
878fn nine_patch_border_to_webrender(
879    sc: &mut SpaceAndClip,
880    wr_list: &mut wr::DisplayListBuilder,
881    source: &NinePatchSource,
882    cache: &DisplayListCache,
883    mut bounds: PxRect,
884    mut widths: PxSideOffsets,
885    repeat_horizontal: RepeatMode,
886    slice: PxSideOffsets,
887    img_size: PxSize,
888    repeat_vertical: RepeatMode,
889    fill: bool,
890) {
891    let clip = sc.clip_chain_id(wr_list);
892
893    let source = match source {
894        NinePatchSource::Image { image_id, rendering } => {
895            wr::NinePatchBorderSource::Image(wr::ImageKey(cache.id_namespace(), image_id.get()), rendering.to_wr())
896        }
897        NinePatchSource::LinearGradient {
898            start_point,
899            end_point,
900            extend_mode,
901            stops,
902        } => {
903            wr_list.push_stops(cast_gradient_stops_to_wr(stops));
904            wr::NinePatchBorderSource::Gradient(wr::Gradient {
905                start_point: start_point.cast_unit(),
906                end_point: end_point.cast_unit(),
907                extend_mode: extend_mode.to_wr(),
908            })
909        }
910        NinePatchSource::RadialGradient {
911            center,
912            radius,
913            start_offset,
914            end_offset,
915            extend_mode,
916            stops,
917        } => {
918            wr_list.push_stops(cast_gradient_stops_to_wr(stops));
919            wr::NinePatchBorderSource::RadialGradient(wr::RadialGradient {
920                center: center.cast_unit(),
921                radius: radius.cast_unit(),
922                start_offset: *start_offset,
923                end_offset: *end_offset,
924                extend_mode: extend_mode.to_wr(),
925            })
926        }
927        NinePatchSource::ConicGradient {
928            center,
929            angle,
930            start_offset,
931            end_offset,
932            extend_mode,
933            stops,
934        } => {
935            wr_list.push_stops(cast_gradient_stops_to_wr(stops));
936            wr::NinePatchBorderSource::ConicGradient(wr::ConicGradient {
937                center: center.cast_unit(),
938                angle: angle.0,
939                start_offset: *start_offset,
940                end_offset: *end_offset,
941                extend_mode: extend_mode.to_wr(),
942            })
943        }
944    };
945
946    // webrender does not implement RepeatMode::Space, so we hide the space lines (and corners) and do a manual repeat
947    let actual_bounds = bounds;
948    let actual_widths = widths;
949    let mut render_corners = false;
950    if let wr::NinePatchBorderSource::Image(image_key, rendering) = source {
951        use wr::euclid::rect as r;
952
953        if matches!(repeat_horizontal, zng_view_api::RepeatMode::Space) {
954            bounds.origin.y += widths.top;
955            bounds.size.height -= widths.vertical();
956            widths.top = Px(0);
957            widths.bottom = Px(0);
958            render_corners = true;
959
960            for (bounds, slice) in [
961                // top
962                (
963                    r::<_, Px>(
964                        actual_widths.left,
965                        Px(0),
966                        actual_bounds.width() - actual_widths.horizontal(),
967                        actual_widths.top,
968                    ),
969                    r::<_, Px>(slice.left, Px(0), img_size.width - slice.horizontal(), slice.top),
970                ),
971                // bottom
972                (
973                    r(
974                        actual_widths.left,
975                        actual_bounds.height() - actual_widths.bottom,
976                        actual_bounds.width() - actual_widths.horizontal(),
977                        actual_widths.bottom,
978                    ),
979                    r(
980                        slice.left,
981                        img_size.height - slice.bottom,
982                        img_size.width - slice.horizontal(),
983                        slice.bottom,
984                    ),
985                ),
986            ] {
987                let scale = Factor(bounds.height().0 as f32 / slice.height().0 as f32);
988
989                let size = PxRect::from_size(img_size * scale).to_wr();
990                let clip = slice * scale;
991
992                let mut offset_x = (bounds.origin.x - clip.origin.x).0 as f32;
993                let offset_y = (bounds.origin.y - clip.origin.y).0 as f32;
994
995                let bounds_width = bounds.width().0 as f32;
996                let clip = clip.to_wr();
997                let n = (bounds_width / clip.width()).floor();
998                let space = bounds_width - clip.width() * n;
999                let space = space / (n + 1.0);
1000
1001                offset_x += space;
1002                let advance = clip.width() + space;
1003                for _ in 0..n as u32 {
1004                    let spatial_id = wr_list.push_reference_frame(
1005                        wr::units::LayoutPoint::zero(),
1006                        sc.spatial_id(),
1007                        wr::TransformStyle::Flat,
1008                        wr::PropertyBinding::Value(wr::units::LayoutTransform::translation(offset_x, offset_y, 0.0)),
1009                        wr::ReferenceFrameKind::Transform {
1010                            is_2d_scale_translation: true,
1011                            should_snap: false,
1012                            paired_with_perspective: false,
1013                        },
1014                        sc.next_view_process_frame_id().to_wr(),
1015                    );
1016                    sc.push_spatial(spatial_id);
1017
1018                    let clip_id = sc.clip_chain_id(wr_list);
1019                    wr_list.push_image(
1020                        &wr::CommonItemProperties {
1021                            clip_rect: clip,
1022                            clip_chain_id: clip_id,
1023                            spatial_id: sc.spatial_id(),
1024                            flags: sc.primitive_flags(),
1025                        },
1026                        size,
1027                        rendering,
1028                        wr::AlphaType::Alpha,
1029                        image_key,
1030                        wr::ColorF::WHITE,
1031                    );
1032
1033                    wr_list.pop_reference_frame();
1034                    sc.pop_spatial();
1035
1036                    offset_x += advance;
1037                }
1038            }
1039        }
1040        if matches!(repeat_vertical, zng_view_api::RepeatMode::Space) {
1041            bounds.origin.x += widths.left;
1042            bounds.size.width -= widths.horizontal();
1043            widths.left = Px(0);
1044            widths.right = Px(0);
1045            render_corners = true;
1046
1047            for (bounds, slice) in [
1048                // left
1049                (
1050                    r::<_, Px>(
1051                        Px(0),
1052                        actual_widths.top,
1053                        actual_widths.left,
1054                        actual_bounds.height() - actual_widths.vertical(),
1055                    ),
1056                    r::<_, Px>(Px(0), slice.top, slice.left, img_size.height - slice.vertical()),
1057                ),
1058                // right
1059                (
1060                    r(
1061                        actual_bounds.width() - actual_widths.right,
1062                        actual_widths.top,
1063                        actual_widths.right,
1064                        actual_bounds.height() - actual_widths.vertical(),
1065                    ),
1066                    r(
1067                        img_size.width - slice.right,
1068                        slice.left,
1069                        slice.right,
1070                        img_size.height - slice.vertical(),
1071                    ),
1072                ),
1073            ] {
1074                let scale = Factor(bounds.width().0 as f32 / slice.width().0 as f32);
1075
1076                let size = PxRect::from_size(img_size * scale).to_wr();
1077                let clip = slice * scale;
1078
1079                let offset_x = (bounds.origin.x - clip.origin.x).0 as f32;
1080                let mut offset_y = (bounds.origin.y - clip.origin.y).0 as f32;
1081
1082                let bounds_height = bounds.height().0 as f32;
1083                let clip = clip.to_wr();
1084                let n = (bounds_height / clip.height()).floor();
1085                let space = bounds_height - clip.height() * n;
1086                let space = space / (n + 1.0);
1087
1088                offset_y += space;
1089                let advance = clip.height() + space;
1090                for _ in 0..n as u32 {
1091                    let spatial_id = wr_list.push_reference_frame(
1092                        wr::units::LayoutPoint::zero(),
1093                        sc.spatial_id(),
1094                        wr::TransformStyle::Flat,
1095                        wr::PropertyBinding::Value(wr::units::LayoutTransform::translation(offset_x, offset_y, 0.0)),
1096                        wr::ReferenceFrameKind::Transform {
1097                            is_2d_scale_translation: true,
1098                            should_snap: false,
1099                            paired_with_perspective: false,
1100                        },
1101                        sc.next_view_process_frame_id().to_wr(),
1102                    );
1103                    sc.push_spatial(spatial_id);
1104
1105                    let clip_id = sc.clip_chain_id(wr_list);
1106                    wr_list.push_image(
1107                        &wr::CommonItemProperties {
1108                            clip_rect: clip,
1109                            clip_chain_id: clip_id,
1110                            spatial_id: sc.spatial_id(),
1111                            flags: sc.primitive_flags(),
1112                        },
1113                        size,
1114                        rendering,
1115                        wr::AlphaType::Alpha,
1116                        image_key,
1117                        wr::ColorF::WHITE,
1118                    );
1119
1120                    wr_list.pop_reference_frame();
1121                    sc.pop_spatial();
1122
1123                    offset_y += advance;
1124                }
1125            }
1126        }
1127    }
1128
1129    let wr_bounds = bounds.to_wr();
1130
1131    wr_list.push_border(
1132        &wr::CommonItemProperties {
1133            clip_rect: wr_bounds,
1134            clip_chain_id: clip,
1135            spatial_id: sc.spatial_id(),
1136            flags: sc.primitive_flags(),
1137        },
1138        wr_bounds,
1139        widths.to_wr(),
1140        wr::BorderDetails::NinePatch(wr::NinePatchBorder {
1141            source,
1142            width: img_size.width.0,
1143            height: img_size.height.0,
1144            slice: slice.to_wr_device(),
1145            fill,
1146            repeat_horizontal: repeat_horizontal.to_wr(),
1147            repeat_vertical: repeat_vertical.to_wr(),
1148        }),
1149    );
1150
1151    // if we rendered RepeatMode::Space
1152    if render_corners {
1153        let wr::NinePatchBorderSource::Image(image_key, rendering) = source else {
1154            unreachable!()
1155        };
1156
1157        use wr::euclid::rect as r;
1158
1159        for (bounds, slice) in [
1160            // top-left
1161            (
1162                r::<_, Px>(Px(0), Px(0), actual_widths.left, actual_widths.top),
1163                r::<_, Px>(Px(0), Px(0), slice.left, slice.top),
1164            ),
1165            // top-right
1166            (
1167                r(
1168                    actual_bounds.width() - actual_widths.right,
1169                    Px(0),
1170                    actual_widths.right,
1171                    actual_widths.top,
1172                ),
1173                r(img_size.width - slice.right, Px(0), slice.right, slice.top),
1174            ),
1175            // bottom-right
1176            (
1177                r(
1178                    actual_bounds.width() - actual_widths.right,
1179                    actual_bounds.height() - actual_widths.bottom,
1180                    actual_widths.right,
1181                    actual_widths.bottom,
1182                ),
1183                r(
1184                    img_size.width - slice.right,
1185                    img_size.height - slice.bottom,
1186                    slice.right,
1187                    slice.bottom,
1188                ),
1189            ),
1190            // bottom-left
1191            (
1192                r(
1193                    Px(0),
1194                    actual_bounds.height() - actual_widths.bottom,
1195                    actual_widths.left,
1196                    actual_widths.bottom,
1197                ),
1198                r(Px(0), img_size.height - slice.bottom, slice.left, slice.bottom),
1199            ),
1200        ] {
1201            let scale_x = bounds.size.width.0 as f32 / slice.size.width.0 as f32;
1202            let scale_y = bounds.size.height.0 as f32 / slice.size.height.0 as f32;
1203
1204            let mut size = img_size;
1205            size.width *= scale_x;
1206            size.height *= scale_y;
1207
1208            let mut clip = slice;
1209            clip.origin.x *= scale_x;
1210            clip.origin.y *= scale_y;
1211            clip.size.width *= scale_x;
1212            clip.size.height *= scale_y;
1213
1214            let offset_x = bounds.origin.x - clip.origin.x;
1215            let offset_y = bounds.origin.y - clip.origin.y;
1216
1217            let spatial_id = wr_list.push_reference_frame(
1218                wr::units::LayoutPoint::zero(),
1219                sc.spatial_id(),
1220                wr::TransformStyle::Flat,
1221                wr::PropertyBinding::Value(wr::units::LayoutTransform::translation(offset_x.0 as _, offset_y.0 as _, 0.0)),
1222                wr::ReferenceFrameKind::Transform {
1223                    is_2d_scale_translation: true,
1224                    should_snap: false,
1225                    paired_with_perspective: false,
1226                },
1227                sc.next_view_process_frame_id().to_wr(),
1228            );
1229            sc.push_spatial(spatial_id);
1230
1231            let clip_id = sc.clip_chain_id(wr_list);
1232            wr_list.push_image(
1233                &wr::CommonItemProperties {
1234                    clip_rect: clip.to_wr(),
1235                    clip_chain_id: clip_id,
1236                    spatial_id: sc.spatial_id(),
1237                    flags: sc.primitive_flags(),
1238                },
1239                PxRect::from_size(size).to_wr(),
1240                rendering,
1241                wr::AlphaType::Alpha,
1242                image_key,
1243                wr::ColorF::WHITE,
1244            );
1245
1246            wr_list.pop_reference_frame();
1247            sc.pop_spatial();
1248        }
1249    }
1250}
1251
1252fn display_item_register_bindings(item: &DisplayItem, bindings: &mut FxHashMap<FrameValueId, (FrameId, usize)>, value: (FrameId, usize)) {
1253    match item {
1254        DisplayItem::PushReferenceFrame {
1255            transform: FrameValue::Bind { id, .. },
1256            ..
1257        } => {
1258            bindings.insert(*id, value);
1259        }
1260        DisplayItem::PushStackingContext { filters, .. } => {
1261            for filter in filters.iter() {
1262                if let FilterOp::Opacity(FrameValue::Bind { id, .. }) = filter {
1263                    bindings.insert(*id, value);
1264                }
1265            }
1266        }
1267        DisplayItem::Color {
1268            color: FrameValue::Bind { id, .. },
1269            ..
1270        } => {
1271            bindings.insert(*id, value);
1272        }
1273        DisplayItem::Text {
1274            color: FrameValue::Bind { id, .. },
1275            ..
1276        } => {
1277            bindings.insert(*id, value);
1278        }
1279        _ => {}
1280    }
1281}
1282
1283pub(crate) fn cast_glyphs_to_wr(glyphs: &[GlyphInstance]) -> &[wr::GlyphInstance] {
1284    debug_assert_eq!(std::mem::size_of::<GlyphInstance>(), std::mem::size_of::<wr::GlyphInstance>());
1285    debug_assert_eq!(std::mem::size_of::<GlyphIndex>(), std::mem::size_of::<wr::GlyphIndex>());
1286    debug_assert_eq!(
1287        std::mem::size_of::<wr::euclid::Point2D<f32, zng_unit::Px>>(),
1288        std::mem::size_of::<wr::units::LayoutPoint>()
1289    );
1290
1291    // SAFETY: GlyphInstance is a copy of the webrender_api
1292    unsafe { std::mem::transmute(glyphs) }
1293}
1294
1295fn cast_gradient_stops_to_wr(stops: &[GradientStop]) -> &[wr::GradientStop] {
1296    debug_assert_eq!(std::mem::size_of::<GradientStop>(), std::mem::size_of::<wr::GradientStop>());
1297    debug_assert_eq!(std::mem::size_of::<Rgba>(), std::mem::size_of::<wr::ColorF>());
1298
1299    // SAFETY: GradientStop has the same layout as webrender_api (f32, [f32; 4])
1300    unsafe { std::mem::transmute(stops) }
1301}