1use api::{units::*, ClipMode, ColorF};
6use euclid::{Scale, point2};
7
8use crate::ItemUid;
9use crate::gpu_types::ClipSpace;
10use crate::pattern::repeat::RepeatedPattern;
11use crate::render_task::{SubTask, RectangleClipSubTask, ImageClipSubTask};
12use crate::transform::TransformPalette;
13use crate::batch::{BatchKey, BatchKind, BatchTextures};
14use crate::clip::{clamped_radius, ClipChainInstance, ClipIntern, ClipItemKind, ClipNodeRange, ClipStore, ClipNodeInstance, ClipItem};
15use crate::command_buffer::{CommandBufferIndex, PrimitiveCommand, QuadFlags};
16use crate::frame_builder::{FrameBuildingContext, FrameBuildingState, PictureContext};
17use crate::gpu_types::{PrimitiveInstanceData, QuadHeader, QuadInstance, QuadPrimitive, QuadSegment, ZBufferId};
18use crate::intern::DataStore;
19use crate::internal_types::TextureSource;
20use crate::pattern::{Pattern, PatternBuilder, PatternBuilderContext, PatternBuilderState, PatternKind, PatternShaderInput};
21use crate::prim_store::{NinePatchDescriptor, PrimitiveInstanceIndex, PrimitiveScratchBuffer};
22use crate::render_task::{RenderTask, RenderTaskAddress, RenderTaskKind};
23use crate::render_task_cache::{RenderTaskCacheKey, RenderTaskCacheKeyKind, RenderTaskParent};
24use crate::render_task_graph::{RenderTaskGraph, RenderTaskGraphBuilder, RenderTaskId, SubTaskRange};
25use crate::renderer::{BlendMode, GpuBufferAddress, GpuBufferBuilder, GpuBufferBuilderF, GpuBufferDataI};
26use crate::segment::EdgeMask;
27use crate::space::SpaceMapper;
28use crate::spatial_tree::{CoordinateSpaceMapping, SpatialNodeIndex, SpatialTree};
29use crate::transform::GpuTransformId;
30use crate::util::{extract_inner_rect_k, MaxRect, ScaleOffset};
31use crate::visibility::compute_conservative_visible_rect;
32
33pub type LayoutOrDeviceRect = api::euclid::default::Box2D<f32>;
36
37const MIN_AA_SEGMENTS_SIZE: f32 = 4.0;
38const MIN_QUAD_SPLIT_SIZE: f32 = 256.0;
39const MAX_TILES_PER_QUAD_X: usize = 8;
44const MAX_TILES_PER_QUAD_Y: usize = 4;
45
46
47#[derive(Clone, Debug, Hash, PartialEq, Eq)]
48#[cfg_attr(feature = "capture", derive(Serialize))]
49#[cfg_attr(feature = "replay", derive(Deserialize))]
50pub struct QuadCacheKey {
51 pub prim: u64,
52 pub clips: [u64; 3],
53 pub transform: [u32; 4],
54}
55
56pub struct QuadTransformState {
59 map_prim_to_raster: CoordinateSpaceMapping<LayoutPixel, LayoutPixel>,
60 as_scale_offset: Option<ScaleOffset>, is_2d_axis_aligned: bool,
62 prim_spatial_node: SpatialNodeIndex,
63 raster_spatial_node: SpatialNodeIndex,
64 device_pixel_scale: DevicePixelScale,
65}
66
67impl QuadTransformState {
68 pub fn new() -> QuadTransformState {
69 QuadTransformState {
70 map_prim_to_raster: CoordinateSpaceMapping::Local,
71 as_scale_offset: Some(ScaleOffset::identity()),
72 is_2d_axis_aligned: true,
73 prim_spatial_node: SpatialNodeIndex::INVALID,
74 raster_spatial_node: SpatialNodeIndex::INVALID,
75 device_pixel_scale: DevicePixelScale::identity(),
76 }
77 }
78
79 pub fn set(
80 &mut self,
81 src_node: SpatialNodeIndex,
82 dst_node: SpatialNodeIndex,
83 spatial_tree: &SpatialTree,
84 scale: DevicePixelScale,
85 ) {
86 if self.prim_spatial_node == src_node && self.raster_spatial_node == dst_node {
87 return;
88 }
89
90 self.map_prim_to_raster = spatial_tree.get_relative_transform(src_node, dst_node);
91
92 self.as_scale_offset = self.map_prim_to_raster
93 .as_2d_scale_offset()
94 .map(|t| t.then_scale(scale.0));
95
96 self.is_2d_axis_aligned = self.as_scale_offset.is_some()
97 || self.map_prim_to_raster.is_2d_axis_aligned();
98
99 self.prim_spatial_node = src_node;
100 self.raster_spatial_node = dst_node;
101 self.device_pixel_scale = scale;
102 }
103
104 pub fn is_2d_scale_offset(&self) -> bool {
105 self.as_scale_offset.is_some()
106 }
107
108 pub fn is_2d_axis_aligned(&self) -> bool {
109 self.is_2d_axis_aligned
110 }
111
112 pub fn as_2d_scale_offset(&self) -> Option<&ScaleOffset> {
115 self.as_scale_offset.as_ref()
116 }
117
118 pub fn scale_factors(&self) -> (f32, f32) {
120 let s = self.map_prim_to_raster.scale_factors();
121
122 (s.0 * self.device_pixel_scale().0, s.1 * self.device_pixel_scale().0)
123 }
124
125 pub fn prim_spatial_node_index(&self) -> SpatialNodeIndex {
126 self.prim_spatial_node
127 }
128
129 pub fn raster_spatial_node_index(&self) -> SpatialNodeIndex {
130 self.raster_spatial_node
131 }
132
133 pub fn device_pixel_scale(&self) -> DevicePixelScale {
134 self.device_pixel_scale
135 }
136}
137
138#[derive(Debug, Copy, Clone)]
146pub enum QuadRenderStrategy {
147 Direct,
150 Indirect,
153 NinePatch {
158 radius: LayoutVector2D,
159 clip_rect: LayoutRect,
160 },
161 Tiled,
165}
166
167pub fn prepare_quad(
168 pattern_builder: &dyn PatternBuilder,
169 local_rect: &LayoutRect,
170 local_clip_rect: &LayoutRect,
171 aligned_aa_edges: EdgeMask,
172 transfomed_aa_edges: EdgeMask,
173 prim_instance_index: PrimitiveInstanceIndex,
174 cache_key: &Option<QuadCacheKey>,
175 clip_chain: &ClipChainInstance,
176 transform: &mut QuadTransformState,
177
178 frame_context: &FrameBuildingContext,
179 pic_context: &PictureContext,
180 targets: &[CommandBufferIndex],
181 interned_clips: &DataStore<ClipIntern>,
182
183 frame_state: &mut FrameBuildingState,
184 scratch: &mut PrimitiveScratchBuffer,
185) {
186 let pattern_ctx = PatternBuilderContext {
187 spatial_tree: frame_context.spatial_tree,
188 fb_config: frame_context.fb_config,
189 prim_origin: local_rect.min,
190 };
191
192 let pattern = pattern_builder.build(
193 None,
194 LayoutVector2D::zero(),
195 &pattern_ctx,
196 &mut PatternBuilderState {
197 frame_gpu_data: frame_state.frame_gpu_data,
198 transforms: frame_state.transforms,
199 },
200 );
201
202 let strategy = match cache_key {
203 Some(_) => QuadRenderStrategy::Indirect,
204 None => get_prim_render_strategy(
205 transform.prim_spatial_node_index(),
206 clip_chain,
207 frame_state.clip_store,
208 interned_clips,
209 transform.is_2d_scale_offset(),
210 pattern_ctx.spatial_tree,
211 ),
212 };
213
214 prepare_quad_impl(
215 strategy,
216 &pattern,
217 local_rect,
218 local_clip_rect,
219 aligned_aa_edges,
220 transfomed_aa_edges,
221 prim_instance_index,
222 cache_key,
223 clip_chain,
224
225 transform,
226 &pattern_ctx,
227 pic_context,
228 targets,
229 interned_clips,
230
231 frame_state,
232 scratch,
233 )
234}
235
236pub fn prepare_repeatable_quad(
237 pattern_builder: &dyn PatternBuilder,
238 local_rect: &LayoutRect,
239 local_clip_rect: &LayoutRect,
240 stretch_size: LayoutSize,
241 tile_spacing: LayoutSize,
242 aligned_aa_edges: EdgeMask,
243 transfomed_aa_edges: EdgeMask,
244 prim_instance_index: PrimitiveInstanceIndex,
245 cache_key: &Option<QuadCacheKey>,
246 clip_chain: &ClipChainInstance,
247 transform: &mut QuadTransformState,
248
249 frame_context: &FrameBuildingContext,
250 pic_context: &PictureContext,
251 targets: &[CommandBufferIndex],
252 interned_clips: &DataStore<ClipIntern>,
253
254 frame_state: &mut FrameBuildingState,
255 scratch: &mut PrimitiveScratchBuffer,
256) {
257 let pattern_ctx = PatternBuilderContext {
258 spatial_tree: frame_context.spatial_tree,
259 fb_config: frame_context.fb_config,
260 prim_origin: local_rect.min,
261 };
262
263 let pattern = pattern_builder.build(
264 None,
265 LayoutVector2D::zero(),
266 &pattern_ctx,
267 &mut PatternBuilderState {
268 frame_gpu_data: frame_state.frame_gpu_data,
269 transforms: frame_state.transforms,
270 },
271 );
272
273 let strategy = match cache_key {
278 Some(_) => QuadRenderStrategy::Indirect,
279 None => get_prim_render_strategy(
280 transform.prim_spatial_node_index(),
281 clip_chain,
282 frame_state.clip_store,
283 interned_clips,
284 transform.is_2d_scale_offset(),
285 pattern_ctx.spatial_tree,
286 ),
287 };
288
289 let needs_repetition = stretch_size.width < local_rect.width()
290 || stretch_size.height < local_rect.height();
291
292 if !needs_repetition {
293 let local_clip_rect = local_clip_rect.intersection_unchecked(&local_rect);
299 let local_rect = LayoutRect::from_origin_and_size(
300 local_rect.min,
301 stretch_size,
302 );
303
304 prepare_quad_impl(
306 strategy,
307 &pattern,
308 &local_rect,
309 &local_clip_rect,
310 aligned_aa_edges,
311 transfomed_aa_edges,
312 prim_instance_index,
313 &cache_key,
314 clip_chain,
315 transform,
316 &pattern_ctx,
317 pic_context,
318 targets,
319 interned_clips,
320 frame_state,
321 scratch,
322 );
323
324 return;
325 }
326
327 let pattern_rect = LayoutRect::from_origin_and_size(
328 local_rect.min,
329 stretch_size,
330 );
331
332 let scales = transform.scale_factors();
333 let mut indirect_transform = ScaleOffset::from_scale(scales.into());
334 let mut surface_rect: DeviceRect = indirect_transform.map_rect(&pattern_rect);
335
336 let src_task_id = pattern.as_render_task();
339
340 let num_repetitions = local_rect.area() / stretch_size.area();
343 let repeat_using_a_shader = src_task_id.is_some()
344 || (num_repetitions > 16.0 && surface_rect.width() < 1024.0 && surface_rect.height() < 1024.0)
345 || (num_repetitions > 64.0 && surface_rect.area() < 1024.0 * 1024.0);
346
347 if repeat_using_a_shader {
348 let (src_task_id, base_color) = match src_task_id {
349 Some(task) => (task, pattern.base_color),
350 None => {
351 adjust_indirect_pattern_resolution(
355 &pattern_rect,
356 2048.0,
357 &mut surface_rect,
358 &mut indirect_transform,
359 );
360
361 let Some(task_id) = prepare_indirect_pattern(
362 transform.prim_spatial_node_index(),
363 transform.raster_spatial_node_index(),
364 &pattern_rect,
365 &pattern_rect,
366 &surface_rect,
367 Some(&indirect_transform),
368 DevicePixelScale::identity(),
369 GpuTransformId::IDENTITY,
370 &pattern,
371 QuadFlags::empty(),
372 EdgeMask::empty(),
373 cache_key,
374 None,
375 &pattern_ctx,
376 interned_clips,
377 frame_state,
378 ) else {
379 return;
380 };
381
382 (task_id, ColorF::WHITE)
383 }
384 };
385
386 let repetitions = RepeatedPattern {
387 stretch_size,
388 spacing: tile_spacing,
389 src_task_id,
390 src_is_opaque: pattern.is_opaque,
391 };
392
393 let repeat_pattern = repetitions.build(
394 None,
395 LayoutVector2D::zero(),
396 &pattern_ctx,
397 &mut PatternBuilderState {
398 frame_gpu_data: frame_state.frame_gpu_data,
399 transforms: frame_state.transforms,
400 },
401 ).with_base_color(base_color);
402
403 prepare_quad_impl(
406 strategy,
407 &repeat_pattern,
408 local_rect,
409 local_clip_rect,
410 aligned_aa_edges,
411 transfomed_aa_edges,
412 prim_instance_index,
413 &None,
414 clip_chain,
415 transform,
416 &pattern_ctx,
417 pic_context,
418 targets,
419 interned_clips,
420 frame_state,
421 scratch,
422 );
423
424 return;
425 }
426
427 let visible_rect = compute_conservative_visible_rect(
430 clip_chain,
431 frame_state.current_dirty_region().combined,
432 frame_state.current_dirty_region().visibility_spatial_node,
433 transform.prim_spatial_node_index(),
434 frame_context.spatial_tree,
435 ).intersection_unchecked(local_clip_rect);
436
437 let stride = stretch_size + tile_spacing;
438 let repetitions = crate::image_tiling::repetitions(&local_rect, &visible_rect, stride);
439 for tile in repetitions {
440 let tile_rect = LayoutRect::from_origin_and_size(tile.origin, stretch_size);
441 let clip_rect = local_clip_rect.intersection_unchecked(&tile_rect);
442 let pattern_offset = tile.origin - local_rect.min;
443 let pattern = pattern_builder.build(
444 None,
445 pattern_offset,
446 &pattern_ctx,
447 &mut PatternBuilderState {
448 frame_gpu_data: frame_state.frame_gpu_data,
449 transforms: frame_state.transforms,
450 },
451 );
452
453 prepare_quad_impl(
454 strategy,
455 &pattern,
456 &tile_rect,
457 &clip_rect,
458 aligned_aa_edges & tile.edge_flags,
459 transfomed_aa_edges & tile.edge_flags,
460 prim_instance_index,
461 &None,
464 clip_chain,
465 transform,
466 &pattern_ctx,
467 pic_context,
468 targets,
469 interned_clips,
470 frame_state,
471 scratch,
472 );
473 }
474}
475
476pub fn prepare_border_image_nine_patch(
477 nine_patch: &NinePatchDescriptor,
478 pattern_builder: &dyn PatternBuilder,
479 local_rect: &LayoutRect,
480 stretch_size: LayoutSize,
481 aligned_aa_edges: EdgeMask,
482 transfomed_aa_edges: EdgeMask,
483 prim_instance_index: PrimitiveInstanceIndex,
484 clip_chain: &ClipChainInstance,
485 transform: &mut QuadTransformState,
486
487 frame_context: &FrameBuildingContext,
488 pic_context: &PictureContext,
489 targets: &[CommandBufferIndex],
490 interned_clips: &DataStore<ClipIntern>,
491
492 frame_state: &mut FrameBuildingState,
493 scratch: &mut PrimitiveScratchBuffer,
494) {
495 let pattern_ctx = PatternBuilderContext {
496 spatial_tree: frame_context.spatial_tree,
497 fb_config: frame_context.fb_config,
498 prim_origin: local_rect.min,
499 };
500
501 let pattern = pattern_builder.build(
502 None,
503 LayoutVector2D::zero(),
504 &pattern_ctx,
505 &mut PatternBuilderState {
506 frame_gpu_data: frame_state.frame_gpu_data,
507 transforms: frame_state.transforms,
508 },
509 );
510
511 let strategy = get_prim_render_strategy(
512 transform.prim_spatial_node_index(),
513 clip_chain,
514 frame_state.clip_store,
515 interned_clips,
516 transform.is_2d_scale_offset(),
517 pattern_ctx.spatial_tree,
518 );
519
520 let scales = transform.scale_factors();
523 let base_indirect_transform = ScaleOffset::from_scale(scales.into());
524
525 nine_patch.for_each_segment(local_rect, &mut|dst_rect, src_rect, side, _repeat_h, _repeat_v| {
526 let min_x = local_rect.min.x + stretch_size.width * src_rect.uv0.x;
528 let min_y = local_rect.min.y + stretch_size.height * src_rect.uv0.y;
529 let max_x = local_rect.min.x + stretch_size.width * src_rect.uv1.x;
530 let max_y = local_rect.min.y + stretch_size.height * src_rect.uv1.y;
531 let pattern_rect = LayoutRect {
532 min: point2(min_x, min_y),
533 max: point2(max_x, max_y),
534 };
535
536 let mut indirect_transform = base_indirect_transform;
547 let mut surface_rect = indirect_transform.map_rect(&pattern_rect);
548
549 adjust_indirect_pattern_resolution(
550 &pattern_rect,
551 2048.0,
552 &mut surface_rect,
553 &mut indirect_transform,
554 );
555
556 let Some(task_id) = prepare_indirect_pattern(
557 transform.prim_spatial_node_index(),
558 transform.raster_spatial_node_index(),
559 &pattern_rect,
560 &pattern_rect,
561 &surface_rect,
562 Some(&indirect_transform),
563 DevicePixelScale::identity(),
564 GpuTransformId::IDENTITY,
565 &pattern,
566 QuadFlags::empty(),
567 EdgeMask::empty(),
568 &None,
569 None,
570 &pattern_ctx,
571 interned_clips,
572 frame_state,
573 ) else {
574 return;
575 };
576
577 let img_pattern = Pattern::texture(task_id, pattern.is_opaque);
578
579 prepare_quad_impl(
580 strategy,
581 &img_pattern,
582 &dst_rect,
583 &clip_chain.local_clip_rect,
584 aligned_aa_edges & side,
585 transfomed_aa_edges & side,
586 prim_instance_index,
587 &None,
588 clip_chain,
589
590 transform,
591 &pattern_ctx,
592 pic_context,
593 targets,
594 interned_clips,
595
596 frame_state,
597 scratch,
598 )
599 });
600}
601
602fn prepare_quad_impl(
603 strategy: QuadRenderStrategy,
604 pattern: &Pattern,
605 local_rect: &LayoutRect,
606 local_clip_rect: &LayoutRect,
607 aligned_aa_edges: EdgeMask,
608 transfomed_aa_edges: EdgeMask,
609 prim_instance_index: PrimitiveInstanceIndex,
610 cache_key: &Option<QuadCacheKey>,
611 clip_chain: &ClipChainInstance,
612
613 transform: &mut QuadTransformState,
614 ctx: &PatternBuilderContext,
615 pic_context: &PictureContext,
616 targets: &[CommandBufferIndex],
617 interned_clips: &DataStore<ClipIntern>,
618
619 frame_state: &mut FrameBuildingState,
620 scratch: &mut PrimitiveScratchBuffer,
621) {
622 let transform_id = if transform.is_2d_scale_offset() {
628 GpuTransformId::IDENTITY
629 } else {
630 frame_state.transforms.gpu.get_id_with_post_scale(
631 transform.prim_spatial_node_index(),
632 transform.raster_spatial_node_index(),
633 transform.device_pixel_scale().get(),
634 ctx.spatial_tree,
635 )
636 };
637
638 let prim_is_2d_scale_translation = transform.is_2d_scale_offset();
639 let prim_is_2d_axis_aligned = transform.is_2d_axis_aligned();
640
641 let mut quad_flags = QuadFlags::empty();
642
643 let prim_size = local_rect.size();
645 if prim_size.width > MIN_AA_SEGMENTS_SIZE && prim_size.height > MIN_AA_SEGMENTS_SIZE {
646 quad_flags |= QuadFlags::USE_AA_SEGMENTS;
647 }
648
649 let needs_scissor = !prim_is_2d_scale_translation;
650 if !needs_scissor {
651 quad_flags |= QuadFlags::APPLY_RENDER_TASK_CLIP;
652 }
653
654 let aa_flags = if prim_is_2d_axis_aligned {
655 aligned_aa_edges
656 } else {
657 transfomed_aa_edges
658 };
659
660 let round_edges = !aa_flags;
665
666 if let QuadRenderStrategy::Direct = strategy {
667 if pattern.is_opaque {
668 quad_flags |= QuadFlags::IS_OPAQUE;
669 }
670
671 let quad = create_quad_primitive(
672 local_rect,
673 local_clip_rect,
674 &DeviceRect::max_rect(),
675 transform.as_2d_scale_offset(),
676 round_edges,
677 pattern,
678 );
679
680 let main_prim_address = frame_state.frame_gpu_data.f32.push(&quad);
681
682 frame_state.push_prim(
685 &PrimitiveCommand::quad(
686 pattern.kind,
687 pattern.shader_input,
688 pattern.texture_input.task_id,
689 crate::prim_store::storage::Index::from_u32(prim_instance_index.0),
690 main_prim_address,
691 transform_id,
692 quad_flags,
693 aa_flags,
694 pattern.blend_mode,
695 ),
696 transform.prim_spatial_node_index(),
697 targets,
698 );
699
700 if pattern.texture_input.task_id != RenderTaskId::INVALID {
703 frame_state
704 .surface_builder
705 .add_child_render_task(pattern.texture_input.task_id, frame_state.rg_builder);
706 }
707
708 return;
709 }
710
711 let surface = &mut frame_state.surfaces[pic_context.surface_index.0];
712 let clipped_pic_rect = clip_chain.pic_coverage_rect.intersection_unchecked(&surface.clipping_rect);
713
714 let pic_to_raster = SpaceMapper::new_with_target(
715 surface.raster_spatial_node_index,
716 surface.surface_spatial_node_index,
717 RasterRect::max_rect(),
718 ctx.spatial_tree,
719 );
720 let Some(clipped_raster_rect) = pic_to_raster.map(&clipped_pic_rect) else { return; };
721
722 let device_scale: Scale<f32, RasterPixel, DevicePixel> = Scale::new(transform.device_pixel_scale.0);
725
726 let mut clipped_surface_rect = (clipped_raster_rect * device_scale).round();
729
730 if let Some(t) = transform.as_2d_scale_offset() {
731 let clipped_local_rect = local_rect.intersection_unchecked(local_clip_rect);
732 clipped_surface_rect = clipped_surface_rect.intersection_unchecked(
733 &t.map_rect(&clipped_local_rect).round_out(),
734 );
735 }
736
737
738 if clipped_surface_rect.is_empty() {
739 return;
740 }
741
742 match strategy {
743 QuadRenderStrategy::Direct => {}
744 QuadRenderStrategy::Indirect => {
745 let Some(task_id) = prepare_indirect_pattern(
746 transform.prim_spatial_node_index(),
747 transform.raster_spatial_node_index(),
748 local_rect,
749 local_clip_rect,
750 &clipped_surface_rect,
751 transform.as_2d_scale_offset(),
752 transform.device_pixel_scale(),
753 transform_id,
754 pattern,
755 quad_flags,
756 aa_flags,
757 cache_key,
758 Some(clip_chain),
759 ctx,
760 interned_clips,
761 frame_state,
762 ) else {
763 return;
764 };
765
766 add_composite_prim(
767 pattern.blend_mode,
768 prim_instance_index,
769 &clipped_surface_rect,
770 frame_state,
771 targets,
772 &[QuadSegment { rect: clipped_surface_rect.to_untyped(), task_id }],
773 );
774 }
775 QuadRenderStrategy::Tiled => {
776 prepare_tiles(
777 prim_instance_index,
778 local_rect,
779 local_clip_rect,
780 &clipped_surface_rect,
781 pattern,
782 quad_flags,
783 aa_flags,
784 clip_chain,
785 transform_id,
786 transform,
787 pic_context,
788 ctx,
789 interned_clips,
790 frame_state,
791 scratch,
792 targets,
793 );
794 }
795 QuadRenderStrategy::NinePatch { clip_rect, radius } => {
796 prepare_nine_patch(
797 prim_instance_index,
798 local_rect,
799 local_clip_rect,
800 &clipped_surface_rect,
801 &clip_rect,
802 radius,
803 pattern,
804 quad_flags,
805 aa_flags,
806 clip_chain.clips_range,
807 transform,
808 transform_id,
809 ctx,
810 interned_clips,
811 frame_state,
812 scratch,
813 targets,
814 );
815 }
816 }
817}
818
819fn prepare_indirect_pattern(
820 prim_spatial_node_index: SpatialNodeIndex,
821 raster_spatial_node_index: SpatialNodeIndex,
822 local_rect: &LayoutRect,
823 local_clip_rect: &LayoutRect,
824 clipped_surface_rect: &DeviceRect,
825 local_to_device_scale_offset: Option<&ScaleOffset>,
826 device_pixel_scale: DevicePixelScale,
827 transform_id: GpuTransformId,
828 pattern: &Pattern,
829 mut quad_flags: QuadFlags,
830 aa_flags: EdgeMask,
831 cache_key: &Option<QuadCacheKey>,
832 clip_chain: Option<&ClipChainInstance>,
833 ctx: &PatternBuilderContext,
834 interned_clips: &DataStore<ClipIntern>,
835 frame_state: &mut FrameBuildingState,
836) -> Option<RenderTaskId> {
837 let round_edges = !aa_flags;
838 let quad = create_quad_primitive(
839 local_rect,
840 local_clip_rect,
841 clipped_surface_rect,
842 local_to_device_scale_offset,
843 round_edges,
844 pattern,
845 );
846
847 let main_prim_address = frame_state.frame_gpu_data.f32.push(&quad);
848
849 let mut clipped_surface_rect = *clipped_surface_rect;
850 if local_to_device_scale_offset.is_some() && aa_flags.is_empty() {
851 clipped_surface_rect = quad.clip.cast_unit();
858 }
859
860 let task_size = clipped_surface_rect.size().to_i32();
861 if task_size.is_empty() {
862 return None;
863 }
864
865 let cache_key = cache_key.as_ref().map(|key| {
866 RenderTaskCacheKey {
867 origin: clipped_surface_rect.min.to_i32(),
868 size: task_size,
869 kind: RenderTaskCacheKeyKind::Quad(key.clone()),
870 }
871 });
872
873 if pattern.is_opaque {
874 quad_flags |= QuadFlags::IS_OPAQUE;
875 }
876
877 let needs_scissor = local_to_device_scale_offset.is_none();
878
879 let mut local_coverage_rect = *local_rect;
880 let mut clips_range = ClipNodeRange { first: 0, count: 0 };
881 if let Some(clip_chain) = clip_chain {
882 local_coverage_rect = local_coverage_rect.intersection_unchecked(local_clip_rect);
883 clips_range = clip_chain.clips_range;
884 }
885
886 Some(add_render_task_with_mask(
887 &pattern,
888 &local_coverage_rect,
889 task_size,
890 clipped_surface_rect.min,
891 clips_range,
892 prim_spatial_node_index,
893 raster_spatial_node_index,
894 main_prim_address,
895 transform_id,
896 aa_flags,
897 quad_flags,
898 device_pixel_scale,
899 needs_scissor,
900 cache_key.as_ref(),
901 ctx.spatial_tree,
902 interned_clips,
903 frame_state,
904 ))
905}
906
907fn prepare_nine_patch(
908 prim_instance_index: PrimitiveInstanceIndex,
909 local_rect: &LayoutRect,
910 local_clip_rect: &LayoutRect,
911 clipped_surface_rect: &DeviceRect,
912 ninepatch_rect: &LayoutRect,
913 radius: LayoutVector2D,
914 pattern: &Pattern,
915 mut quad_flags: QuadFlags,
916 aa_flags: EdgeMask,
917 clips_range: ClipNodeRange,
918 transform: &mut QuadTransformState,
919 gpu_transform: GpuTransformId,
920 ctx: &PatternBuilderContext,
921 interned_clips: &DataStore<ClipIntern>,
922 frame_state: &mut FrameBuildingState,
923 scratch: &mut PrimitiveScratchBuffer,
924 targets: &[CommandBufferIndex],
925) {
926 let local_to_device = transform.as_2d_scale_offset().unwrap();
931 let mut device_prim_rect: DeviceRect = local_to_device.map_rect(&local_rect);
932 let mut device_clip_rect: DeviceRect = local_to_device
933 .map_rect(&local_clip_rect)
934 .intersection_unchecked(clipped_surface_rect);
935
936 let rounded_edges = !aa_flags;
937 device_prim_rect = rounded_edges.select(device_prim_rect.round(), device_prim_rect);
938 device_clip_rect = rounded_edges
939 .select(device_clip_rect.round(), device_clip_rect)
940 .intersection_unchecked(&device_prim_rect);
941 let clipped_surface_rect = rounded_edges
942 .select(device_clip_rect, *clipped_surface_rect);
943
944
945 let local_corner_0 = LayoutRect::new(
946 ninepatch_rect.min,
947 ninepatch_rect.min + radius,
948 );
949
950 let local_corner_1 = LayoutRect::new(
951 ninepatch_rect.max - radius,
952 ninepatch_rect.max,
953 );
954
955 let surface_rect_0: DeviceRect = local_to_device
956 .map_rect(&local_corner_0)
957 .round_out();
958 let surface_rect_1: DeviceRect = local_to_device
959 .map_rect(&local_corner_1)
960 .round_out();
961
962 let p0 = surface_rect_0.min;
963 let p1 = surface_rect_0.max;
964 let p2 = surface_rect_1.min;
965 let p3 = surface_rect_1.max;
966
967 let mut x_coords = [p0.x, p1.x, p2.x, p3.x];
968 let mut y_coords = [p0.y, p1.y, p2.y, p3.y];
969
970 x_coords.sort_by(|a, b| a.partial_cmp(b).unwrap());
971 y_coords.sort_by(|a, b| a.partial_cmp(b).unwrap());
972
973 scratch.frame.quad_direct_segments.clear();
974 scratch.frame.quad_indirect_segments.clear();
975
976 let mode = ClipMode::Clip;
978
979 if pattern.is_opaque {
980 quad_flags |= QuadFlags::IS_OPAQUE;
981 }
982
983 fn should_create_task(mode: ClipMode, x: usize, y: usize) -> bool {
984 match mode {
985 ClipMode::Clip => x != 1 && y != 1,
987 ClipMode::ClipOut => true,
990 }
991 }
992
993 let indirect_prim_address = write_device_prim_blocks(
994 &mut frame_state.frame_gpu_data.f32,
995 &device_prim_rect,
996 &device_clip_rect,
997 pattern.base_color,
998 pattern.texture_input.task_id,
999 &[],
1000 local_to_device.inverse(),
1001 );
1002
1003 for y in 0 .. y_coords.len()-1 {
1004 let y0 = y_coords[y];
1005 let y1 = y_coords[y+1];
1006
1007 if y1 <= y0 {
1008 continue;
1009 }
1010
1011 for x in 0 .. x_coords.len()-1 {
1012 if mode == ClipMode::ClipOut && x == 1 && y == 1 {
1013 continue;
1014 }
1015
1016 let x0 = x_coords[x];
1017 let x1 = x_coords[x+1];
1018
1019 if x1 <= x0 {
1020 continue;
1021 }
1022
1023 let segment = DeviceRect::new(point2(x0, y0), point2(x1, y1));
1024 let segment_device_rect = match segment.intersection(&clipped_surface_rect) {
1025 Some(rect) => rect,
1026 None => {
1027 continue;
1028 }
1029 };
1030
1031 if should_create_task(mode, x, y) {
1032 let task_size = segment_device_rect.size().to_i32();
1033 let task_id = add_render_task_with_mask(
1034 pattern,
1035 &local_rect,
1036 task_size,
1037 segment_device_rect.min,
1038 clips_range,
1039 transform.prim_spatial_node_index(),
1040 transform.raster_spatial_node_index(),
1041 indirect_prim_address,
1042 gpu_transform,
1043 aa_flags,
1044 quad_flags,
1045 transform.device_pixel_scale(),
1046 false,
1047 None,
1048 ctx.spatial_tree,
1049 interned_clips,
1050 frame_state,
1051 );
1052 scratch.frame.quad_indirect_segments.push(QuadSegment {
1053 rect: segment_device_rect.to_f32().cast_unit(),
1054 task_id,
1055 });
1056 } else {
1057 scratch.frame.quad_direct_segments.push(QuadSegment {
1058 rect: segment_device_rect.to_f32().cast_unit(),
1059 task_id: pattern.texture_input.task_id,
1060 });
1061 };
1062 }
1063 }
1064
1065 if !scratch.frame.quad_direct_segments.is_empty() {
1066 add_pattern_prim(
1067 pattern,
1068 local_to_device.inverse(),
1069 prim_instance_index,
1070 &device_prim_rect,
1071 &device_clip_rect,
1072 pattern.is_opaque,
1073 frame_state,
1074 targets,
1075 &scratch.frame.quad_direct_segments,
1076 );
1077 }
1078
1079 if !scratch.frame.quad_indirect_segments.is_empty() {
1080 add_composite_prim(
1081 pattern.blend_mode,
1082 prim_instance_index,
1083 &device_clip_rect,
1084 frame_state,
1085 targets,
1086 &scratch.frame.quad_indirect_segments,
1087 );
1088 }
1089}
1090
1091fn prepare_tiles(
1092 prim_instance_index: PrimitiveInstanceIndex,
1093 local_rect: &LayoutRect,
1094 local_clip_rect: &LayoutRect,
1095 device_clip_rect: &DeviceRect,
1096 pattern: &Pattern,
1097 mut quad_flags: QuadFlags,
1098 aa_flags: EdgeMask,
1099 clip_chain: &ClipChainInstance,
1100 gpu_transform: GpuTransformId,
1101 transform: &mut QuadTransformState,
1102 pic_context: &PictureContext,
1103 ctx: &PatternBuilderContext,
1104 interned_clips: &DataStore<ClipIntern>,
1105 frame_state: &mut FrameBuildingState,
1106 scratch: &mut PrimitiveScratchBuffer,
1107 targets: &[CommandBufferIndex],
1108) {
1109 let surface = &mut frame_state.surfaces[pic_context.surface_index.0];
1117 surface.map_local_to_picture.set_target_spatial_node(
1118 transform.prim_spatial_node_index(),
1119 ctx.spatial_tree,
1120 );
1121
1122 let unclipped_surface_rect = device_clip_rect.round_out();
1123
1124 let force_masks = !transform.is_2d_scale_offset();
1125 scratch.retained.quad_tile_classifier.reset(
1127 unclipped_surface_rect,
1128 force_masks,
1129 );
1130
1131 let mut clip_to_raster = SpaceMapper::<LayoutPixel, RasterPixel>::new(
1132 transform.raster_spatial_node_index(),
1133 RasterRect::max_rect(),
1134 );
1135
1136 for i in 0 .. clip_chain.clips_range.count {
1138 let clip_instance = frame_state.clip_store.get_instance_from_range(&clip_chain.clips_range, i);
1139 let clip_node = &interned_clips[clip_instance.handle];
1140
1141 clip_to_raster.set_target_spatial_node(clip_instance.spatial_node_index, ctx.spatial_tree);
1142
1143 let transform = match clip_to_raster.as_2d_scale_offset() {
1144 Some(t) => t.then_scale(transform.device_pixel_scale.0),
1145 None => {
1146 scratch.retained.quad_tile_classifier.add_mask_region(unclipped_surface_rect);
1151 continue;
1152 }
1153 };
1154
1155 match clip_node.item.kind {
1157 ClipItemKind::Rectangle { mode } => {
1158 let rect = transform.map_rect(&clip_instance.clip_rect);
1159 scratch.retained.quad_tile_classifier.add_clip_rect(rect, mode);
1160 }
1161 ClipItemKind::RoundedRectangle { mode: ClipMode::Clip, ref radius } => {
1162 let radius = clamped_radius(radius, clip_instance.clip_rect.size());
1167 let clip_device_rect = transform.map_rect(&clip_instance.clip_rect);
1168 let r_tl = transform.map_size(&radius.top_left).abs();
1172 let r_tr = transform.map_size(&radius.top_right).abs();
1173 let r_br = transform.map_size(&radius.bottom_right).abs();
1174 let r_bl = transform.map_size(&radius.bottom_left).abs();
1175
1176 let c_tl = DeviceRect::from_origin_and_size(
1178 clip_device_rect.min,
1179 r_tl,
1180 );
1181 let c_tr = DeviceRect::from_origin_and_size(
1182 DevicePoint::new(
1183 clip_device_rect.max.x - r_tr.width,
1184 clip_device_rect.min.y,
1185 ),
1186 r_tr,
1187 );
1188 let c_br = DeviceRect::from_origin_and_size(
1189 DevicePoint::new(
1190 clip_device_rect.max.x - r_br.width,
1191 clip_device_rect.max.y - r_br.height,
1192 ),
1193 r_br,
1194 );
1195 let c_bl = DeviceRect::from_origin_and_size(
1196 DevicePoint::new(
1197 clip_device_rect.min.x,
1198 clip_device_rect.max.y - r_bl.height,
1199 ),
1200 r_bl,
1201 );
1202
1203 scratch.retained.quad_tile_classifier.add_clip_rect(clip_device_rect, ClipMode::Clip);
1204 scratch.retained.quad_tile_classifier.add_mask_region(c_tl);
1205 scratch.retained.quad_tile_classifier.add_mask_region(c_tr);
1206 scratch.retained.quad_tile_classifier.add_mask_region(c_br);
1207 scratch.retained.quad_tile_classifier.add_mask_region(c_bl);
1208 }
1209 ClipItemKind::RoundedRectangle { mode: ClipMode::ClipOut, ref radius } => {
1210 let radius = clamped_radius(radius, clip_instance.clip_rect.size());
1211 match extract_inner_rect_k(&clip_instance.clip_rect, &radius, 0.5) {
1214 Some(ref inner_rect) => {
1215 let rect = transform.map_rect(inner_rect);
1216 scratch.retained.quad_tile_classifier.add_clip_rect(rect, ClipMode::ClipOut);
1217 }
1218 None => {
1219 let clip_device_rect = transform.map_rect(&clip_instance.clip_rect);
1220 scratch.retained.quad_tile_classifier.add_mask_region(clip_device_rect);
1221 }
1222 }
1223 }
1224 ClipItemKind::Image { .. } => {
1225 panic!("bug: image clips unexpected in this path");
1226 }
1227 }
1228 }
1229
1230 let indirect_prim_address = write_prim_blocks(
1231 &mut frame_state.frame_gpu_data.f32,
1232 local_rect,
1233 local_clip_rect,
1234 device_clip_rect,
1235 transform.as_2d_scale_offset(),
1236 !aa_flags,
1237 pattern,
1238 );
1239
1240 scratch.frame.quad_direct_segments.clear();
1242 scratch.frame.quad_indirect_segments.clear();
1243
1244 let tiles = scratch.retained.quad_tile_classifier.classify();
1245 for tile in tiles {
1246 let is_direct = match tile.kind {
1248 QuadTileKind::Clipped => {
1249 continue;
1252 }
1253 QuadTileKind::Pattern { has_mask } => !has_mask,
1254 };
1255
1256 let tile_size = tile.rect.size().to_i32();
1261 if tile_size.is_empty() {
1262 continue;
1263 }
1264
1265 if is_direct {
1266 scratch.frame.quad_direct_segments.push(QuadSegment {
1267 rect: tile.rect.cast_unit(),
1268 task_id: RenderTaskId::INVALID
1269 });
1270 } else {
1271 if pattern.is_opaque {
1272 quad_flags |= QuadFlags::IS_OPAQUE;
1273 }
1274
1275 let needs_scissor = !transform.is_2d_scale_offset();
1276 let task_id = add_render_task_with_mask(
1277 pattern,
1278 local_rect,
1279 tile_size,
1280 tile.rect.min,
1281 clip_chain.clips_range,
1282 transform.prim_spatial_node_index(),
1283 transform.raster_spatial_node_index(),
1284 indirect_prim_address,
1285 gpu_transform,
1286 aa_flags,
1287 quad_flags,
1288 transform.device_pixel_scale(),
1289 needs_scissor,
1290 None,
1291 ctx.spatial_tree,
1292 interned_clips,
1293 frame_state,
1294 );
1295
1296 scratch.frame.quad_indirect_segments.push(QuadSegment {
1297 rect: tile.rect.cast_unit(),
1298 task_id,
1299 });
1300 }
1301 }
1302
1303 if !scratch.frame.quad_direct_segments.is_empty() {
1304 let local_to_device = transform.as_2d_scale_offset().unwrap();
1306
1307 let device_prim_rect: DeviceRect = local_to_device.map_rect(&local_rect);
1308
1309 if pattern.texture_input.task_id != RenderTaskId::INVALID {
1310 for segment in &mut scratch.frame.quad_direct_segments {
1311 segment.task_id = pattern.texture_input.task_id;
1312 }
1313 }
1314
1315 add_pattern_prim(
1316 pattern,
1317 local_to_device.inverse(),
1318 prim_instance_index,
1319 &device_prim_rect,
1320 &device_clip_rect,
1321 pattern.is_opaque,
1322 frame_state,
1323 targets,
1324 &scratch.frame.quad_direct_segments,
1325 );
1326 }
1327
1328 if !scratch.frame.quad_indirect_segments.is_empty() {
1329 add_composite_prim(
1330 pattern.blend_mode,
1331 prim_instance_index,
1332 device_clip_rect,
1333 frame_state,
1334 targets,
1335 &scratch.frame.quad_indirect_segments,
1336 );
1337 }
1338}
1339
1340fn get_prim_render_strategy(
1341 prim_spatial_node_index: SpatialNodeIndex,
1342 clip_chain: &ClipChainInstance,
1343 clip_store: &ClipStore,
1344 interned_clips: &DataStore<ClipIntern>,
1345 prim_is_scale_offset: bool,
1346 spatial_tree: &SpatialTree,
1347) -> QuadRenderStrategy {
1348 if !clip_chain.needs_mask {
1349 return QuadRenderStrategy::Direct
1350 }
1351
1352 let try_split_prim = if prim_is_scale_offset {
1357 let size = clip_chain.pic_coverage_rect.size();
1360 size.width > MIN_QUAD_SPLIT_SIZE || size.height > MIN_QUAD_SPLIT_SIZE
1361 } else {
1362 false
1363 };
1364
1365 if !try_split_prim {
1366 return QuadRenderStrategy::Indirect;
1367 }
1368
1369 if prim_is_scale_offset && clip_chain.clips_range.count == 1 {
1370 let clip_instance = clip_store.get_instance_from_range(&clip_chain.clips_range, 0);
1371 let clip_node = &interned_clips[clip_instance.handle];
1372
1373 if let ClipItemKind::RoundedRectangle { ref radius, mode: ClipMode::Clip, .. } = clip_node.item.kind {
1374 let size = clip_instance.clip_rect.size();
1375 let radius = clamped_radius(radius, size);
1376 let max_corner_width = radius.top_left.width
1377 .max(radius.bottom_left.width)
1378 .max(radius.top_right.width)
1379 .max(radius.bottom_right.width);
1380 let max_corner_height = radius.top_left.height
1381 .max(radius.bottom_left.height)
1382 .max(radius.top_right.height)
1383 .max(radius.bottom_right.height);
1384
1385 if max_corner_width <= 0.5 * size.width &&
1386 max_corner_height <= 0.5 * size.height {
1387
1388 let clip_prim_coords_match = spatial_tree.is_matching_coord_system(
1389 prim_spatial_node_index,
1390 clip_instance.spatial_node_index,
1391 );
1392
1393 if clip_prim_coords_match {
1394 let map_clip_to_prim = SpaceMapper::new_with_target(
1395 prim_spatial_node_index,
1396 clip_instance.spatial_node_index,
1397 LayoutRect::max_rect(),
1398 spatial_tree,
1399 );
1400
1401 if let Some(clip_rect) = map_clip_to_prim.map(&clip_instance.clip_rect) {
1402 let radius = map_clip_to_prim.map_vector(
1403 LayoutVector2D::new(max_corner_width, max_corner_height)
1404 );
1405 return QuadRenderStrategy::NinePatch {
1406 radius,
1407 clip_rect,
1408 };
1409 }
1410 }
1411 }
1412 }
1413 }
1414
1415 QuadRenderStrategy::Tiled
1416}
1417
1418fn adjust_indirect_pattern_resolution(
1422 local_rect: &LayoutRect,
1423 max_device_size: f32,
1424 device_rect: &mut DeviceRect,
1425 indirect_transform: &mut ScaleOffset,
1426) {
1427 let valid = local_rect.width() > 0.0
1430 && local_rect.height() > 0.0
1431 && indirect_transform.scale.x != 0.0
1432 && indirect_transform.scale.y != 0.0;
1433
1434 if !valid {
1435 return;
1436 }
1437
1438 while device_rect.width() > max_device_size {
1440 indirect_transform.scale.x *= 0.5;
1441 *device_rect = indirect_transform.map_rect(local_rect);
1442 }
1443 while device_rect.height() > max_device_size {
1444 indirect_transform.scale.y *= 0.5;
1445 *device_rect = indirect_transform.map_rect(local_rect);
1446 }
1447
1448 while device_rect.width() <= 0.5 {
1450 indirect_transform.scale.x *= 2.0;
1451 *device_rect = indirect_transform.map_rect(local_rect);
1452 }
1453 while device_rect.height() <= 0.5 {
1454 indirect_transform.scale.y *= 2.0;
1455 *device_rect = indirect_transform.map_rect(local_rect);
1456 }
1457}
1458
1459pub fn cache_key(
1460 prim_uid: ItemUid,
1461 transform: &QuadTransformState,
1462 clip_chain: &ClipChainInstance,
1463 clip_store: &ClipStore,
1464) -> Option<QuadCacheKey> {
1465 const CACHE_MAX_CLIPS: usize = 3;
1466
1467 if (clip_chain.clips_range.count as usize) >= CACHE_MAX_CLIPS {
1468 return None;
1469 }
1470
1471 let prim_spatial_node_index = transform.prim_spatial_node_index();
1472 let Some(transform) = transform.as_2d_scale_offset() else {
1479 return None;
1480 };
1481
1482 let mut clip_uids = [!0; CACHE_MAX_CLIPS];
1483
1484 for i in 0 .. clip_chain.clips_range.count {
1485 let clip_instance = clip_store.get_instance_from_range(&clip_chain.clips_range, i);
1486 clip_uids[i as usize] = clip_instance.handle.uid().get_uid();
1487 if clip_instance.spatial_node_index != prim_spatial_node_index {
1488 return None;
1489 }
1490 }
1491
1492 Some(QuadCacheKey {
1493 prim: prim_uid.get_uid(),
1494 clips: clip_uids,
1495 transform: [
1496 transform.scale.x.to_bits(),
1497 transform.scale.y.to_bits(),
1498 transform.offset.x.to_bits(),
1499 transform.offset.y.to_bits(),
1500 ],
1501 })
1502}
1503
1504fn add_render_task_with_mask(
1505 pattern: &Pattern,
1506 prim_local_coverage_rect: &LayoutRect,
1507 task_size: DeviceIntSize,
1508 content_origin: DevicePoint,
1509 clips_range: ClipNodeRange,
1510 prim_spatial_node_index: SpatialNodeIndex,
1511 raster_spatial_node_index: SpatialNodeIndex,
1512 prim_address_f: GpuBufferAddress,
1513 transform_id: GpuTransformId,
1514 aa_flags: EdgeMask,
1515 quad_flags: QuadFlags,
1516 device_pixel_scale: DevicePixelScale,
1517 needs_scissor_rect: bool,
1518 cache_key: Option<&RenderTaskCacheKey>,
1519 spatial_tree: &SpatialTree,
1520 interned_clips: &DataStore<ClipIntern>,
1521 frame_state: &mut FrameBuildingState,
1522) -> RenderTaskId {
1523 let transforms = &mut frame_state.transforms;
1524 let clip_store = &frame_state.clip_store;
1525 let is_opaque = pattern.is_opaque && clips_range.count == 0;
1526 frame_state.resource_cache.request_render_task(
1527 cache_key.cloned(),
1528 is_opaque,
1529 RenderTaskParent::Surface,
1530 &mut frame_state.frame_gpu_data.f32,
1531 frame_state.rg_builder,
1532 &mut frame_state.surface_builder,
1533 &mut|rg_builder, gpu_buffer| {
1534 let task_id = rg_builder.add().init(RenderTask::new_dynamic(
1535 task_size,
1536 RenderTaskKind::new_prim(
1537 pattern.kind,
1538 pattern.shader_input,
1539 content_origin,
1540 prim_address_f,
1541 transform_id,
1542 aa_flags,
1543 quad_flags,
1544 needs_scissor_rect,
1545 pattern.texture_input.task_id,
1546 ),
1547 ));
1548
1549 if pattern.texture_input.task_id != RenderTaskId::INVALID {
1552 rg_builder.add_dependency(task_id, pattern.texture_input.task_id);
1553 }
1554
1555 if clips_range.count > 0 {
1556 let task_rect = DeviceRect::from_origin_and_size(
1557 content_origin,
1558 task_size.to_f32(),
1559 );
1560
1561 prepare_clip_range(
1562 clips_range,
1563 task_id,
1564 &task_rect,
1565 prim_local_coverage_rect,
1566 prim_spatial_node_index,
1567 raster_spatial_node_index,
1568 device_pixel_scale,
1569 interned_clips,
1570 clip_store,
1571 spatial_tree,
1572 rg_builder,
1573 gpu_buffer,
1574 transforms,
1575 );
1576 }
1577
1578 task_id
1579 }
1580 )
1581}
1582
1583fn add_pattern_prim(
1584 pattern: &Pattern,
1585 pattern_transform: ScaleOffset,
1586 prim_instance_index: PrimitiveInstanceIndex,
1587 rect: &DeviceRect,
1588 clip_rect: &DeviceRect,
1589 is_opaque: bool,
1590 frame_state: &mut FrameBuildingState,
1591 targets: &[CommandBufferIndex],
1592 segments: &[QuadSegment],
1593) {
1594 let prim_address = write_device_prim_blocks(
1595 &mut frame_state.frame_gpu_data.f32,
1596 rect,
1597 clip_rect,
1598 pattern.base_color,
1599 pattern.texture_input.task_id,
1600 segments,
1601 pattern_transform,
1602 );
1603
1604 frame_state.set_segments(segments, targets);
1605
1606 let mut quad_flags = QuadFlags::APPLY_RENDER_TASK_CLIP;
1607
1608 if is_opaque {
1609 quad_flags |= QuadFlags::IS_OPAQUE;
1610 }
1611
1612 frame_state.push_cmd(
1613 &PrimitiveCommand::quad(
1614 pattern.kind,
1615 pattern.shader_input,
1616 pattern.texture_input.task_id,
1617 crate::prim_store::storage::Index::from_u32(prim_instance_index.0),
1618 prim_address,
1619 GpuTransformId::IDENTITY,
1620 quad_flags,
1621 EdgeMask::empty(),
1623 pattern.blend_mode,
1624 ),
1625 targets,
1626 );
1627}
1628
1629fn add_composite_prim(
1630 blend_mode: BlendMode,
1631 prim_instance_index: PrimitiveInstanceIndex,
1632 rect: &DeviceRect,
1633 frame_state: &mut FrameBuildingState,
1634 targets: &[CommandBufferIndex],
1635 segments: &[QuadSegment],
1636) {
1637 assert!(!segments.is_empty());
1638
1639 let composite_prim_address = write_device_prim_blocks(
1645 &mut frame_state.frame_gpu_data.f32,
1646 rect,
1647 rect,
1648 ColorF::WHITE,
1649 RenderTaskId::INVALID,
1650 segments,
1651 ScaleOffset::identity(),
1652 );
1653
1654 frame_state.set_segments(segments, targets);
1655
1656 let quad_flags = QuadFlags::APPLY_RENDER_TASK_CLIP;
1657
1658 frame_state.push_cmd(
1659 &PrimitiveCommand::quad(
1660 PatternKind::ColorOrTexture,
1661 PatternShaderInput(
1662 crate::pattern::TEXTURED_SHADER_MODE_TEXTURE,
1663 crate::pattern::TEXTURED_SHADER_MAP_TO_SEGMENT,
1664 ),
1665 RenderTaskId::INVALID,
1666 crate::prim_store::storage::Index::from_u32(prim_instance_index.0),
1667 composite_prim_address,
1668 GpuTransformId::IDENTITY,
1669 quad_flags,
1670 EdgeMask::empty(),
1672 blend_mode,
1673 ),
1674 targets,
1675 );
1676}
1677
1678pub fn prepare_clip_range(
1679 clips_range: ClipNodeRange,
1680 masked_prim_task_id: RenderTaskId,
1681 task_rect: &DeviceRect,
1682 prim_local_coverage_rect: &LayoutRect,
1683 prim_spatial_node_index: SpatialNodeIndex,
1684 raster_spatial_node_index: SpatialNodeIndex,
1685 device_pixel_scale: DevicePixelScale,
1686 interned_clips: &DataStore<ClipIntern>,
1687 clip_store: &ClipStore,
1688 spatial_tree: &SpatialTree,
1689 rg_builder: &mut RenderTaskGraphBuilder,
1690 gpu_buffer: &mut GpuBufferBuilderF,
1691 transforms: &mut TransformPalette,
1692) {
1693 let mut sub_tasks = rg_builder.begin_sub_tasks();
1694
1695 for i in 0 .. clips_range.count {
1696 let clip_instance = clip_store.get_instance_from_range(&clips_range, i);
1697 let clip_item = &interned_clips[clip_instance.handle].item;
1698
1699 prepare_clip_task(
1700 clip_instance,
1701 clip_item,
1702 task_rect,
1703 prim_local_coverage_rect,
1704 prim_spatial_node_index,
1705 raster_spatial_node_index,
1706 device_pixel_scale,
1707 clip_store,
1708 spatial_tree,
1709 gpu_buffer,
1710 transforms,
1711 rg_builder,
1712 &mut sub_tasks,
1713 );
1714 }
1715
1716 rg_builder
1717 .get_task_mut(masked_prim_task_id)
1718 .set_sub_tasks(sub_tasks);
1719}
1720
1721pub fn prepare_clip_task(
1722 clip_instance: &ClipNodeInstance,
1723 clip_item: &ClipItem,
1724 task_rect: &DeviceRect,
1725 prim_local_coverage_rect: &LayoutRect,
1726 prim_spatial_node_index: SpatialNodeIndex,
1727 raster_spatial_node_index: SpatialNodeIndex,
1728 device_pixel_scale: DevicePixelScale,
1729 clip_store: &ClipStore,
1730 spatial_tree: &SpatialTree,
1731 gpu_buffer: &mut GpuBufferBuilderF,
1732 transforms: &mut TransformPalette,
1733 rg_builder: &mut RenderTaskGraphBuilder,
1734 sub_tasks: &mut SubTaskRange,
1735) {
1736 let (clip_address, fast_path) = match clip_item.kind {
1737 ClipItemKind::RoundedRectangle { radius, mode } => {
1738 let radius = clamped_radius(&radius, clip_instance.clip_rect.size());
1739 let (fast_path, clip_address) = if radius.can_use_fast_path_in(&clip_instance.clip_rect) {
1740 let mut writer = gpu_buffer.write_blocks(3);
1741 writer.push_one(clip_instance.clip_rect);
1742 writer.push_one([
1743 radius.bottom_right.width,
1744 radius.top_right.width,
1745 radius.bottom_left.width,
1746 radius.top_left.width,
1747 ]);
1748 writer.push_one([mode as i32 as f32, 0.0, 0.0, 0.0]);
1749 let clip_address = writer.finish();
1750
1751 (true, clip_address)
1752 } else {
1753 let mut writer = gpu_buffer.write_blocks(4);
1754 writer.push_one(clip_instance.clip_rect);
1755 writer.push_one([
1756 radius.top_left.width,
1757 radius.top_left.height,
1758 radius.top_right.width,
1759 radius.top_right.height,
1760 ]);
1761 writer.push_one([
1762 radius.bottom_left.width,
1763 radius.bottom_left.height,
1764 radius.bottom_right.width,
1765 radius.bottom_right.height,
1766 ]);
1767 writer.push_one([mode as i32 as f32, 0.0, 0.0, 0.0]);
1768 let clip_address = writer.finish();
1769
1770 (false, clip_address)
1771 };
1772
1773 (clip_address, fast_path)
1774 }
1775 ClipItemKind::Rectangle { mode, .. } => {
1776 let mut writer = gpu_buffer.write_blocks(3);
1777 writer.push_one(clip_instance.clip_rect);
1778 writer.push_one([0.0, 0.0, 0.0, 0.0]);
1779 writer.push_one([mode as i32 as f32, 0.0, 0.0, 0.0]);
1780 let clip_address = writer.finish();
1781
1782 (clip_address, true)
1783 }
1784 ClipItemKind::Image { .. } => {
1785 let transform_id = transforms.gpu.get_id_with_post_scale(
1786 clip_instance.spatial_node_index,
1787 raster_spatial_node_index,
1788 device_pixel_scale.get(),
1789 spatial_tree,
1790 );
1791
1792 let is_scale_offset = transform_id.is_2d_scale_offset();
1793 let needs_scissor_rect = !is_scale_offset;
1794
1795 let pattern = Pattern::color(ColorF::WHITE);
1796 let mut quad_flags = QuadFlags::IS_MASK;
1797
1798 if is_scale_offset {
1799 quad_flags |= QuadFlags::APPLY_RENDER_TASK_CLIP;
1800 }
1801
1802 for tile in clip_store.visible_mask_tiles(&clip_instance) {
1803 let prim_address = write_layout_prim_blocks(
1804 gpu_buffer,
1805 &tile.tile_rect,
1806 &tile.tile_rect,
1807 pattern.base_color,
1808 pattern.texture_input.task_id,
1809 &[QuadSegment {
1810 rect: tile.tile_rect.to_untyped(),
1811 task_id: tile.task_id,
1812 }],
1813 );
1814
1815 rg_builder.push_sub_task(
1816 sub_tasks,
1817 SubTask::ImageClip(ImageClipSubTask {
1818 quad_address: prim_address,
1819 quad_transform_id: transform_id,
1820 src_task: tile.task_id,
1821 quad_flags,
1822 needs_scissor_rect,
1823 }),
1824 );
1825 }
1826
1827 return;
1831 }
1832 };
1833
1834 let clip_spatial_node = spatial_tree.get_spatial_node(clip_instance.spatial_node_index);
1835 let raster_spatial_node = spatial_tree.get_spatial_node(raster_spatial_node_index);
1836 let raster_clip = raster_spatial_node.coordinate_system_id == clip_spatial_node.coordinate_system_id;
1837
1838 let (clip_space, clip_transform_id, quad_address, quad_transform_id, is_same_coord_system) = if raster_clip {
1840 let quad_transform_id = GpuTransformId::IDENTITY;
1841 let pattern = Pattern::color(ColorF::WHITE);
1842
1843 let clip_transform_id = transforms.gpu.get_id_with_pre_scale(
1848 device_pixel_scale.inverse().get(),
1849 raster_spatial_node_index,
1850 clip_instance.spatial_node_index,
1851 spatial_tree,
1852 );
1853 let pattern_transform = ScaleOffset::identity();
1854
1855 let quad_address = write_device_prim_blocks(
1856 gpu_buffer,
1857 &task_rect,
1858 &task_rect,
1859 pattern.base_color,
1860 pattern.texture_input.task_id,
1861 &[],
1862 pattern_transform,
1863 );
1864
1865 (ClipSpace::Device, clip_transform_id, quad_address, quad_transform_id, true)
1866 } else {
1867 let prim_spatial_node = spatial_tree.get_spatial_node(prim_spatial_node_index);
1868
1869 let quad_transform_id = transforms.gpu.get_id_with_post_scale(
1870 prim_spatial_node_index,
1871 raster_spatial_node_index,
1872 device_pixel_scale.get(),
1873 spatial_tree,
1874 );
1875
1876 let rect = prim_local_coverage_rect.inflate(2.0, 2.0);
1880
1881 let quad_address = write_layout_prim_blocks(
1882 gpu_buffer,
1883 &rect,
1884 &rect,
1885 ColorF::WHITE,
1886 RenderTaskId::INVALID,
1887 &[],
1888 );
1889
1890 let clip_spatial_node = spatial_tree.get_spatial_node(clip_instance.spatial_node_index);
1891 let clip_transform_id = if prim_spatial_node.coordinate_system_id < clip_spatial_node.coordinate_system_id {
1892 transforms.gpu.get_id(
1893 clip_instance.spatial_node_index,
1894 prim_spatial_node_index,
1895 spatial_tree,
1896 )
1897 } else {
1898 transforms.gpu.get_id(
1899 prim_spatial_node_index,
1900 clip_instance.spatial_node_index,
1901 spatial_tree,
1902 )
1903 };
1904
1905 let is_same_coord_system = spatial_tree.is_matching_coord_system(
1906 prim_spatial_node_index,
1907 raster_spatial_node_index,
1908 );
1909
1910 (ClipSpace::Primitive, clip_transform_id, quad_address, quad_transform_id, is_same_coord_system)
1911 };
1912
1913 let needs_scissor_rect = !is_same_coord_system;
1914
1915 let quad_flags = if is_same_coord_system {
1916 QuadFlags::APPLY_RENDER_TASK_CLIP
1917 } else {
1918 QuadFlags::empty()
1919 };
1920
1921
1922 rg_builder.push_sub_task(
1923 sub_tasks,
1924 SubTask::RectangleClip(RectangleClipSubTask {
1925 quad_address,
1926 quad_transform_id,
1927 clip_address,
1928 clip_transform_id,
1929 quad_flags,
1930 clip_space,
1931 needs_scissor_rect,
1932 rounded_rect_fast_path: fast_path,
1933 }),
1934 );
1935}
1936
1937fn create_quad_primitive(
1938 local_rect: &LayoutRect,
1939 local_clip_rect: &LayoutRect,
1940 device_clip_rect: &DeviceRect,
1941 local_to_device: Option<&ScaleOffset>,
1942 round_edges: EdgeMask,
1943 pattern: &Pattern,
1944) -> QuadPrimitive {
1945 let mut prim_rect;
1946 let mut prim_clip_rect;
1947 let pattern_transform;
1948 if let Some(local_to_device) = local_to_device {
1949 prim_rect = local_to_device.map_rect(local_rect);
1950 prim_clip_rect = local_to_device
1951 .map_rect(&local_clip_rect)
1952 .intersection_unchecked(device_clip_rect)
1953 .to_untyped();
1954 prim_rect = round_edges.select(prim_rect.round(), prim_rect);
1955 prim_clip_rect = round_edges.select(prim_clip_rect.round(), prim_clip_rect);
1956
1957 pattern_transform = local_to_device.inverse();
1958 } else {
1959 prim_rect = local_rect.to_untyped();
1960 prim_clip_rect = local_clip_rect.to_untyped();
1961 pattern_transform = ScaleOffset::identity();
1962 };
1963
1964 QuadPrimitive {
1965 bounds: prim_rect,
1966 clip: prim_clip_rect,
1967 input_task: pattern.texture_input.task_id,
1968 pattern_scale_offset: pattern_transform,
1969 color: pattern.base_color.premultiplied(),
1970 }
1971}
1972
1973fn write_prim_blocks(
1979 builder: &mut GpuBufferBuilderF,
1980 local_rect: &LayoutRect,
1981 local_clip_rect: &LayoutRect,
1982 device_clip_rect: &DeviceRect,
1983 local_to_device: Option<&ScaleOffset>,
1984 round_edges: EdgeMask,
1985 pattern: &Pattern,
1986) -> GpuBufferAddress {
1987 let mut prim_rect;
1988 let mut prim_clip_rect;
1989 let pattern_transform;
1990 if let Some(local_to_device) = local_to_device {
1991 prim_rect = local_to_device.map_rect(&local_rect);
1992 prim_clip_rect = local_to_device
1993 .map_rect(&local_clip_rect)
1994 .intersection_unchecked(&device_clip_rect)
1995 .to_untyped();
1996 prim_rect = round_edges.select(prim_rect.round(), prim_rect);
1997 prim_clip_rect = round_edges.select(prim_rect.round(), prim_clip_rect);
1998 pattern_transform = local_to_device.inverse();
1999 } else {
2000 prim_rect = local_rect.to_untyped();
2001 prim_clip_rect = local_clip_rect.to_untyped();
2002 pattern_transform = ScaleOffset::identity();
2003 };
2004
2005 write_prim_blocks_impl(
2006 builder,
2007 prim_rect,
2008 prim_clip_rect,
2009 pattern.base_color,
2010 pattern.texture_input.task_id,
2011 &[],
2012 pattern_transform,
2013 )
2014}
2015
2016pub fn write_device_prim_blocks(
2018 builder: &mut GpuBufferBuilderF,
2019 prim_rect: &DeviceRect,
2020 clip_rect: &DeviceRect,
2021 pattern_base_color: ColorF,
2022 pattern_texture_input: RenderTaskId,
2023 segments: &[QuadSegment],
2024 pattern_scale_offset: ScaleOffset,
2025) -> GpuBufferAddress {
2026 write_prim_blocks_impl(
2027 builder,
2028 prim_rect.to_untyped(),
2029 clip_rect.to_untyped(),
2030 pattern_base_color,
2031 pattern_texture_input,
2032 segments,
2033 pattern_scale_offset
2034 )
2035}
2036
2037pub fn write_layout_prim_blocks(
2039 builder: &mut GpuBufferBuilderF,
2040 prim_rect: &LayoutRect,
2041 clip_rect: &LayoutRect,
2042 pattern_base_color: ColorF,
2043 pattern_texture_input: RenderTaskId,
2044 segments: &[QuadSegment],
2045) -> GpuBufferAddress {
2046 write_prim_blocks_impl(
2047 builder,
2048 prim_rect.to_untyped(),
2049 clip_rect.to_untyped(),
2050 pattern_base_color,
2051 pattern_texture_input,
2052 segments,
2053 ScaleOffset::identity(),
2054 )
2055}
2056
2057fn write_prim_blocks_impl(
2058 builder: &mut GpuBufferBuilderF,
2059 prim_rect: LayoutOrDeviceRect,
2060 clip_rect: LayoutOrDeviceRect,
2061 pattern_base_color: ColorF,
2062 pattern_texture_input: RenderTaskId,
2063 segments: &[QuadSegment],
2064 pattern_scale_offset: ScaleOffset,
2065) -> GpuBufferAddress {
2066 let mut writer = builder.write_blocks(5 + segments.len() * 2);
2067
2068 writer.push(&QuadPrimitive {
2069 bounds: prim_rect,
2070 clip: clip_rect,
2071 input_task: pattern_texture_input,
2072 pattern_scale_offset,
2073 color: pattern_base_color.premultiplied(),
2074 });
2075
2076 for segment in segments {
2077 writer.push(segment);
2078 }
2079
2080 writer.finish()
2081}
2082
2083pub fn add_to_batch<F>(
2084 kind: PatternKind,
2085 pattern_input: PatternShaderInput,
2086 dst_task_address: RenderTaskAddress,
2087 transform_id: GpuTransformId,
2088 prim_address_f: GpuBufferAddress,
2089 quad_flags: QuadFlags,
2090 edge_flags: EdgeMask,
2091 segment_index: u8,
2092 src_task_id: RenderTaskId,
2093 z_id: ZBufferId,
2094 blend_mode: BlendMode,
2095 render_tasks: &RenderTaskGraph,
2096 gpu_buffer_builder: &mut GpuBufferBuilder,
2097 mut f: F,
2098) where F: FnMut(BatchKey, PrimitiveInstanceData) {
2099
2100 #[repr(u8)]
2102 enum PartIndex {
2103 Center = 0,
2104 Left = 1,
2105 Top = 2,
2106 Right = 3,
2107 Bottom = 4,
2108 All = 5,
2109 }
2110
2111 let texture = match src_task_id {
2112 RenderTaskId::INVALID => TextureSource::Invalid,
2113 _ => match render_tasks.resolve_texture(src_task_id) {
2114 Some(texture) => texture,
2115 None => {
2116 return;
2120 },
2121 }
2122 };
2123
2124
2125 let mut writer = gpu_buffer_builder.i32.write_blocks(QuadHeader::NUM_BLOCKS);
2127 writer.push(&QuadHeader {
2128 transform_id,
2129 z_id,
2130 pattern_input,
2131 });
2132 let prim_address_i = writer.finish();
2133
2134 let textures = BatchTextures::prim_textured(
2135 texture,
2136 TextureSource::Invalid,
2137 );
2138
2139 let prim_blend_mode = if quad_flags.contains(QuadFlags::IS_OPAQUE)
2140 && blend_mode == BlendMode::PremultipliedAlpha
2141 {
2142 BlendMode::None
2143 } else {
2144 blend_mode
2145 };
2146
2147 let edge_flags_bits = edge_flags.bits();
2148
2149 let prim_batch_key = BatchKey {
2150 blend_mode: prim_blend_mode,
2151 kind: BatchKind::Quad(kind),
2152 textures,
2153 };
2154
2155 let aa_batch_key = BatchKey {
2156 blend_mode,
2157 kind: BatchKind::Quad(kind),
2158 textures,
2159 };
2160
2161 let mut instance = QuadInstance {
2162 dst_task_address,
2163 prim_address_i: prim_address_i.as_int(),
2164 prim_address_f: prim_address_f.as_int(),
2165 edge_flags: edge_flags_bits,
2166 quad_flags: quad_flags.bits(),
2167 part_index: PartIndex::All as u8,
2168 segment_index,
2169 };
2170
2171 if edge_flags.is_empty() {
2172 f(prim_batch_key, instance.into());
2174 } else if quad_flags.contains(QuadFlags::USE_AA_SEGMENTS) {
2175 if edge_flags.contains(EdgeMask::LEFT) {
2178 let instance = QuadInstance {
2179 part_index: PartIndex::Left as u8,
2180 ..instance
2181 };
2182 f(aa_batch_key, instance.into());
2183 }
2184 if edge_flags.contains(EdgeMask::TOP) {
2185 let instance = QuadInstance {
2186 part_index: PartIndex::Top as u8,
2187 ..instance
2188 };
2189 f(aa_batch_key, instance.into());
2190 }
2191 if edge_flags.contains(EdgeMask::RIGHT) {
2192 let instance = QuadInstance {
2193 part_index: PartIndex::Right as u8,
2194 ..instance
2195 };
2196 f(aa_batch_key, instance.into());
2197 }
2198 if edge_flags.contains(EdgeMask::BOTTOM) {
2199 let instance = QuadInstance {
2200 part_index: PartIndex::Bottom as u8,
2201 ..instance
2202 };
2203 f(aa_batch_key, instance.into());
2204 }
2205
2206 instance = QuadInstance {
2207 part_index: PartIndex::Center as u8,
2208 ..instance
2209 };
2210
2211 f(prim_batch_key, instance.into());
2212 } else {
2213 f(aa_batch_key, instance.into());
2215 }
2216}
2217
2218#[allow(dead_code)]
2220#[cfg_attr(feature = "capture", derive(Serialize))]
2221#[derive(Debug, Copy, Clone, PartialEq)]
2222pub enum QuadTileKind {
2223 Clipped,
2225 Pattern {
2227 has_mask: bool,
2228 },
2229}
2230
2231#[cfg_attr(feature = "capture", derive(Serialize))]
2232#[derive(Copy, Clone, Debug)]
2233pub struct QuadTileInfo {
2234 pub rect: DeviceRect,
2235 pub kind: QuadTileKind,
2236}
2237
2238impl Default for QuadTileInfo {
2239 fn default() -> Self {
2240 QuadTileInfo {
2241 rect: DeviceRect::zero(),
2242 kind: QuadTileKind::Pattern { has_mask: false },
2243 }
2244 }
2245}
2246
2247#[cfg_attr(feature = "capture", derive(Serialize))]
2250pub struct QuadTileClassifier {
2251 buffer: [QuadTileInfo; MAX_TILES_PER_QUAD_X * MAX_TILES_PER_QUAD_Y],
2252 mask_regions: Vec<DeviceRect>,
2253 clip_in_regions: Vec<DeviceRect>,
2254 clip_out_regions: Vec<DeviceRect>,
2255 rect: DeviceRect,
2256 x_tiles: usize,
2257 y_tiles: usize,
2258 force_masks: bool,
2260}
2261
2262impl QuadTileClassifier {
2263 pub fn new() -> Self {
2264 QuadTileClassifier {
2265 buffer: [QuadTileInfo::default(); MAX_TILES_PER_QUAD_X * MAX_TILES_PER_QUAD_Y],
2266 mask_regions: Vec::new(),
2267 clip_in_regions: Vec::new(),
2268 clip_out_regions: Vec::new(),
2269 rect: DeviceRect::zero(),
2270 x_tiles: 0,
2271 y_tiles: 0,
2272 force_masks: false,
2273 }
2274 }
2275
2276 pub fn reset(
2277 &mut self,
2278 rect: DeviceRect,
2279 force_masks: bool,
2280 ) {
2281 let x_tiles = (rect.width() / MIN_QUAD_SPLIT_SIZE)
2282 .min(MAX_TILES_PER_QUAD_X as f32)
2283 .max(1.0)
2284 .ceil() as usize;
2285 let y_tiles = (rect.width() / MIN_QUAD_SPLIT_SIZE)
2286 .min(MAX_TILES_PER_QUAD_Y as f32)
2287 .max(1.0)
2288 .ceil() as usize;
2289
2290 self.x_tiles = x_tiles;
2291 self.y_tiles = y_tiles;
2292 self.rect = rect;
2293 self.force_masks = force_masks;
2294 self.mask_regions.clear();
2295 self.clip_in_regions.clear();
2296 self.clip_out_regions.clear();
2297
2298 let dx = rect.width() / x_tiles as f32;
2302 let dy = rect.height() / y_tiles as f32;
2303
2304 let mut y0 = rect.min.y;
2305
2306 for y in 0 .. y_tiles {
2307 let y1 = if y == y_tiles - 1 {
2308 rect.max.y
2309 } else {
2310 (rect.min.y + (y + 1) as f32 * dy).round()
2311 };
2312
2313 let mut x0 = rect.min.x;
2314 for x in 0 .. x_tiles {
2315 let info = &mut self.buffer[y * x_tiles + x];
2316
2317 let x1 = if x == x_tiles - 1 {
2318 rect.max.x
2319 } else {
2320 (rect.min.x + (x + 1) as f32 * dx).round()
2321 };
2322
2323 let p0 = DevicePoint::new(x0, y0);
2324 let p1 = DevicePoint::new(x1, y1);
2325 info.rect = DeviceRect::new(p0, p1);
2326 info.kind = QuadTileKind::Pattern { has_mask: force_masks };
2327
2328 x0 = x1;
2329 }
2330
2331 y0 = y1;
2332 }
2333 }
2334
2335 pub fn add_mask_region(
2337 &mut self,
2338 mask_region: DeviceRect,
2339 ) {
2340 if !mask_region.is_empty() {
2341 self.mask_regions.push(mask_region);
2342 }
2343 }
2344
2345 pub fn add_clip_rect(
2347 &mut self,
2348 clip_rect: DeviceRect,
2349 clip_mode: ClipMode,
2350 ) {
2351 match clip_mode {
2352 ClipMode::Clip => {
2353 self.clip_in_regions.push(clip_rect);
2354 }
2355 ClipMode::ClipOut => {
2356 self.clip_out_regions.push(clip_rect);
2357
2358 self.add_mask_region(self.rect);
2359 }
2360 }
2361 }
2362
2363 pub fn classify(
2365 &mut self,
2366 ) -> QuadTileIterator {
2367 assert_ne!(self.x_tiles, 0);
2368 assert_ne!(self.y_tiles, 0);
2369
2370 let tile_count = self.x_tiles * self.y_tiles;
2371 let tiles = &mut self.buffer[0 .. tile_count];
2372
2373 for info in tiles.iter_mut() {
2374 for clip_region in &self.clip_in_regions {
2376 match info.kind {
2377 QuadTileKind::Clipped => {},
2378 QuadTileKind::Pattern { .. } => {
2379 if !clip_region.intersects(&info.rect) {
2380 info.kind = QuadTileKind::Clipped;
2381 }
2382 }
2383 }
2384
2385 }
2386
2387 for clip_region in &self.clip_out_regions {
2389 match info.kind {
2390 QuadTileKind::Clipped => {},
2391 QuadTileKind::Pattern { .. } => {
2392 if clip_region.contains_box(&info.rect) {
2393 info.kind = QuadTileKind::Clipped;
2394 }
2395 }
2396 }
2397 }
2398
2399 for mask_region in &self.mask_regions {
2401 match info.kind {
2402 QuadTileKind::Clipped | QuadTileKind::Pattern { has_mask: true, .. } => {},
2403 QuadTileKind::Pattern { ref mut has_mask, .. } => {
2404 if mask_region.intersects(&info.rect) {
2405 *has_mask = true;
2406 }
2407 }
2408 }
2409 }
2410 }
2411
2412 self.x_tiles = 0;
2413 self.y_tiles = 0;
2414
2415 QuadTileIterator { tiles }
2416 }
2417}
2418
2419pub struct QuadTileIterator<'l> {
2420 tiles: &'l[QuadTileInfo],
2421}
2422
2423impl<'l> Iterator for QuadTileIterator<'l> {
2424 type Item = QuadTileInfo;
2425 fn next(&mut self) -> Option<QuadTileInfo> {
2426 if self.tiles.is_empty() {
2427 return None;
2428 }
2429
2430 let mut tile = self.tiles[0];
2431 self.tiles = &self.tiles[1..];
2432
2433 while tile.kind == QuadTileKind::Clipped {
2435 tile = *self.tiles.first()?;
2436 self.tiles = &self.tiles[1..];
2437 }
2438
2439 while let Some(info) = self.tiles.first() {
2443 if tile.rect.min.y != info.rect.min.y || tile.kind != info.kind {
2444 break;
2446 }
2447
2448 let max = match info.kind {
2449 QuadTileKind::Pattern { has_mask: true } => 1024.0,
2452 QuadTileKind::Pattern { has_mask: false } => f32::MAX,
2455 QuadTileKind::Clipped => { break; }
2456 };
2457
2458 if info.rect.max.x - tile.rect.min.x > max {
2459 break;
2460 }
2461
2462 tile.rect.max.x = info.rect.max.x;
2465 self.tiles = &self.tiles[1..];
2466 }
2467
2468 Some(tile)
2469 }
2470}
2471
2472#[cfg(test)]
2473fn qc_new(x0: f32, y0: f32, w: f32, h: f32) -> QuadTileClassifier {
2474 let mut qc = QuadTileClassifier::new();
2475
2476 qc.reset(
2477 DeviceRect::new(DevicePoint::new(x0, y0), DevicePoint::new(x0 + w, y0 + h)),
2478 false,
2479 );
2480
2481 qc
2482}
2483
2484#[cfg(test)]
2485fn qc_verify(mut qc: QuadTileClassifier, expected: &[QuadTileKind]) {
2486 let tiles = qc.classify();
2487
2488 let mut n = 0;
2489 for (tile, ex) in tiles.zip(expected.iter()) {
2490 assert_eq!(tile.kind, *ex, "Failed for tile {:?}", tile.rect.to_rect());
2491 n += 1;
2492 }
2493
2494 assert_eq!(n, expected.len())
2495}
2496
2497#[cfg(test)]
2498const P: QuadTileKind = QuadTileKind::Pattern { has_mask: false };
2499
2500#[cfg(test)]
2501const M: QuadTileKind = QuadTileKind::Pattern { has_mask: true };
2502
2503#[test]
2504fn quad_classify_1() {
2505 let qc = qc_new(0.0, 0.0, 768.0, 768.0);
2506 qc_verify(qc, &[
2507 P,
2508 P,
2509 P,
2510 ]);
2511}
2512
2513#[test]
2514fn quad_classify_2() {
2515 let mut qc = qc_new(0.0, 0.0, 768.0, 768.0);
2516
2517 let rect = DeviceRect::new(DevicePoint::new(0.0, 0.0), DevicePoint::new(768.0, 768.0));
2518 qc.add_clip_rect(rect, ClipMode::Clip);
2519
2520 qc_verify(qc, &[
2521 P,
2522 P,
2523 P,
2524 ]);
2525}
2526
2527#[test]
2528fn quad_classify_3() {
2529 let mut qc = qc_new(0.0, 0.0, 768.0, 768.0);
2530
2531 let rect = DeviceRect::new(DevicePoint::new(230.0, 230.0), DevicePoint::new(460.0, 460.0));
2532 qc.add_clip_rect(rect, ClipMode::Clip);
2533
2534 qc_verify(qc, &[P]);
2535}
2536
2537#[test]
2538fn quad_classify_4() {
2539 let mut qc = qc_new(0.0, 0.0, 768.0, 768.0);
2540
2541 let rect = DeviceRect::new(DevicePoint::new(230.0, 230.0), DevicePoint::new(537.0, 537.0));
2542 qc.add_clip_rect(rect, ClipMode::Clip);
2543
2544 qc_verify(qc, &[
2545 P,
2546 P,
2547 P,
2548 ]);
2549}
2550
2551#[test]
2552fn quad_classify_5() {
2553 let mut qc = qc_new(0.0, 0.0, 768.0, 768.0);
2554
2555 let rect = DeviceRect::new(DevicePoint::new(230.0, 230.0), DevicePoint::new(537.0, 537.0));
2556 qc.add_clip_rect(rect, ClipMode::ClipOut);
2557
2558 qc_verify(qc, &[
2559 M,
2560 M, M,
2561 M,
2562 ]);
2563}
2564
2565#[test]
2566fn quad_classify_6() {
2567 let mut qc = qc_new(0.0, 0.0, 768.0, 768.0);
2568
2569 let rect = DeviceRect::new(DevicePoint::new(40.0, 40.0), DevicePoint::new(60.0, 60.0));
2570 qc.add_clip_rect(rect, ClipMode::ClipOut);
2571
2572 qc_verify(qc, &[
2573 M,
2574 M,
2575 M,
2576 ]);
2577}
2578
2579#[test]
2580fn quad_classify_7() {
2581 let mut qc = qc_new(0.0, 0.0, 768.0, 768.0);
2582
2583 let rect = DeviceRect::new(DevicePoint::new(154.0, 77.0), DevicePoint::new(691.0, 614.0));
2584 qc.add_mask_region(rect);
2585
2586 qc_verify(qc, &[
2587 M,
2588 M,
2589 M,
2590 ]);
2591}
2592
2593#[test]
2594fn quad_classify_8() {
2595 let mut qc = qc_new(0.0, 0.0, 768.0, 768.0);
2596
2597 let rect = DeviceRect::new(DevicePoint::new(307.0, 307.0), DevicePoint::new(460.0, 460.0));
2598 qc.add_mask_region(rect);
2599
2600 qc_verify(qc, &[
2601 P,
2602 P, M, P,
2603 P,
2604 ]);
2605}
2606
2607#[test]
2608fn quad_classify_9() {
2609 let mut qc = qc_new(100.0, 200.0, 1024.0, 1024.0);
2610
2611 let rect = DeviceRect::new(DevicePoint::new(90.0, 180.0), DevicePoint::new(250.0, 650.0));
2612 qc.add_mask_region(rect);
2613
2614 qc_verify(qc, &[
2615 M, P,
2616 M, P,
2617 P,
2618 P,
2619 ]);
2620}
2621
2622#[test]
2623fn quad_classify_10() {
2624 let mut qc = qc_new(100.0, 200.0, 1024.0, 1024.0);
2625
2626 let mask_rect = DeviceRect::new(DevicePoint::new(90.0, 180.0), DevicePoint::new(510.0, 710.0));
2627 qc.add_mask_region(mask_rect);
2628
2629 let clip_rect = DeviceRect::new(DevicePoint::new(120.0, 220.0), DevicePoint::new(714.0, 1015.0));
2630 qc.add_clip_rect(clip_rect, ClipMode::Clip);
2631
2632 qc_verify(qc, &[
2633 M, P,
2634 M, P,
2635 P,
2636 P,
2637 ]);
2638}
2639
2640#[test]
2641fn quad_classify_11() {
2642 let mut qc = qc_new(100.0, 200.0, 1024.0, 1024.0);
2643
2644 let mask_rect = DeviceRect::new(DevicePoint::new(90.0, 180.0), DevicePoint::new(510.0, 710.0));
2645 qc.add_mask_region(mask_rect);
2646
2647 let clip_rect = DeviceRect::new(DevicePoint::new(120.0, 220.0), DevicePoint::new(714.0, 1015.0));
2648 qc.add_clip_rect(clip_rect, ClipMode::Clip);
2649
2650 let clip_out_rect = DeviceRect::new(DevicePoint::new(130.0, 200.0), DevicePoint::new(714.0, 609.0));
2651 qc.add_clip_rect(clip_out_rect, ClipMode::ClipOut);
2652
2653 qc_verify(qc, &[
2654 M,
2655 M,
2656 M,
2657 M,
2658 ]);
2659}
2660
2661#[test]
2662fn quad_classify_12() {
2663 let mut qc = qc_new(100.0, 200.0, 1024.0, 1024.0);
2664
2665 let clip_out_rect = DeviceRect::new(DevicePoint::new(130.0, 200.0), DevicePoint::new(714.0, 609.0));
2666 qc.add_clip_rect(clip_out_rect, ClipMode::ClipOut);
2667
2668 let clip_rect = DeviceRect::new(DevicePoint::new(120.0, 220.0), DevicePoint::new(714.0, 1015.0));
2669 qc.add_clip_rect(clip_rect, ClipMode::Clip);
2670
2671 let mask_rect = DeviceRect::new(DevicePoint::new(90.0, 180.0), DevicePoint::new(510.0, 710.0));
2672 qc.add_mask_region(mask_rect);
2673
2674 qc_verify(qc, &[
2675 M,
2676 M,
2677 M,
2678 M,
2679 ]);
2680}