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
27pub trait DisplayListExtension {
35 fn display_list_start(&mut self, args: &mut DisplayExtensionArgs) {
39 let _ = args;
40 }
41
42 fn push_display_item(&mut self, args: &mut DisplayExtensionItemArgs);
46 fn pop_display_item(&mut self, args: &mut DisplayExtensionItemArgs) {
50 let _ = args;
51 }
52
53 fn display_list_end(&mut self, args: &mut DisplayExtensionArgs) {
55 let _ = args;
56 }
57
58 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
69pub struct DisplayExtensionArgs<'a> {
71 pub list: &'a mut wr::DisplayListBuilder,
73 pub sc: &'a mut SpaceAndClip,
75}
76
77pub struct DisplayExtensionItemArgs<'a> {
79 pub extension_id: ApiExtensionId,
81 pub payload: &'a ApiExtensionPayload,
83 pub is_reuse: bool,
88 pub list: &'a mut wr::DisplayListBuilder,
90 pub sc: &'a mut SpaceAndClip,
92}
93
94pub struct DisplayExtensionUpdateArgs<'a> {
96 pub extension_id: ApiExtensionId,
98 pub payload: &'a ApiExtensionPayload,
100
101 pub new_frame: bool,
106
107 pub properties: &'a mut wr::DynamicProperties,
112}
113
114pub 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 pub fn spatial_id(&self) -> wr::SpatialId {
136 self.spatial_stack[self.spatial_stack.len() - 1]
137 }
138
139 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 pub fn push_spatial(&mut self, spatial_id: wr::SpatialId) {
162 self.spatial_stack.push(spatial_id);
163 }
164
165 pub fn pop_spatial(&mut self) {
167 self.spatial_stack.truncate(self.spatial_stack.len() - 1);
168 }
169
170 pub fn push_clip(&mut self, clip: wr::ClipId) {
172 self.clip_stack.push(clip);
173 }
174
175 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 pub fn primitive_flags(&self) -> wr::PrimitiveFlags {
188 self.prim_flags
189 }
190
191 pub fn set_backface_visibility(&mut self, visible: bool) {
193 self.prim_flags.set(wr::PrimitiveFlags::IS_BACKFACE_VISIBLE, visible);
194 }
195
196 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
234pub 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 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 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 #[expect(clippy::result_large_err)] 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, 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 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 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 (
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 (
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 (
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 (
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 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 (
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 (
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 (
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 (
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 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 unsafe { std::mem::transmute(stops) }
1301}