1use api::{units::*, ClipMode, ColorF};
6use euclid::point2;
7
8use crate::batch::{BatchKey, BatchKind, BatchTextures};
9use crate::clip::{ClipChainInstance, ClipIntern, ClipItemKind, ClipNodeRange, ClipSpaceConversion, ClipStore};
10use crate::command_buffer::{CommandBufferIndex, PrimitiveCommand, QuadFlags};
11use crate::frame_builder::{FrameBuildingContext, FrameBuildingState, PictureContext, PictureState};
12use crate::gpu_types::{PrimitiveInstanceData, QuadInstance, QuadSegment, TransformPaletteId, ZBufferId};
13use crate::intern::DataStore;
14use crate::internal_types::TextureSource;
15use crate::pattern::{Pattern, PatternBuilder, PatternBuilderContext, PatternBuilderState, PatternKind, PatternShaderInput};
16use crate::prim_store::{PrimitiveInstanceIndex, PrimitiveScratchBuffer};
17use crate::render_task::{MaskSubPass, RenderTask, RenderTaskAddress, RenderTaskKind, SubPass};
18use crate::render_task_graph::{RenderTaskGraph, RenderTaskGraphBuilder, RenderTaskId};
19use crate::renderer::{BlendMode, GpuBufferAddress, GpuBufferBuilder, GpuBufferBuilderF};
20use crate::segment::EdgeAaSegmentMask;
21use crate::space::SpaceMapper;
22use crate::spatial_tree::{SpatialNodeIndex, SpatialTree};
23use crate::surface::SurfaceBuilder;
24use crate::util::{extract_inner_rect_k, MaxRect, ScaleOffset};
25
26const MIN_AA_SEGMENTS_SIZE: f32 = 4.0;
27const MIN_QUAD_SPLIT_SIZE: f32 = 256.0;
28const MAX_TILES_PER_QUAD: usize = 4;
29
30#[derive(Debug, Copy, Clone)]
38pub enum QuadRenderStrategy {
39 Direct,
42 Indirect,
45 NinePatch {
50 radius: LayoutVector2D,
51 clip_rect: LayoutRect,
52 },
53 Tiled {
57 x_tiles: u16,
58 y_tiles: u16,
59 }
60}
61
62pub fn prepare_quad(
63 pattern_builder: &dyn PatternBuilder,
64 local_rect: &LayoutRect,
65 prim_instance_index: PrimitiveInstanceIndex,
66 prim_spatial_node_index: SpatialNodeIndex,
67 clip_chain: &ClipChainInstance,
68 device_pixel_scale: DevicePixelScale,
69
70 frame_context: &FrameBuildingContext,
71 pic_context: &PictureContext,
72 targets: &[CommandBufferIndex],
73 interned_clips: &DataStore<ClipIntern>,
74
75 frame_state: &mut FrameBuildingState,
76 pic_state: &mut PictureState,
77 scratch: &mut PrimitiveScratchBuffer,
78) {
79 let map_prim_to_raster = frame_context.spatial_tree.get_relative_transform(
80 prim_spatial_node_index,
81 pic_context.raster_spatial_node_index,
82 );
83
84 let ctx = PatternBuilderContext {
85 scene_properties: frame_context.scene_properties,
86 spatial_tree: frame_context.spatial_tree,
87 };
88
89 let mut state = PatternBuilderState {
90 frame_gpu_data: frame_state.frame_gpu_data,
91 rg_builder: frame_state.rg_builder,
92 clip_store: frame_state.clip_store,
93 };
94
95 let shared_pattern = if pattern_builder.use_shared_pattern() {
96 Some(pattern_builder.build(
97 None,
98 &ctx,
99 &mut state,
100 ))
101 } else {
102 None
103 };
104
105 let prim_is_2d_scale_translation = map_prim_to_raster.is_2d_scale_translation();
106 let prim_is_2d_axis_aligned = map_prim_to_raster.is_2d_axis_aligned();
107
108 let can_use_nine_patch = prim_is_2d_scale_translation && pattern_builder.can_use_nine_patch();
112
113 let strategy = get_prim_render_strategy(
114 prim_spatial_node_index,
115 clip_chain,
116 state.clip_store,
117 interned_clips,
118 can_use_nine_patch,
119 frame_context.spatial_tree,
120 );
121
122 let mut quad_flags = QuadFlags::empty();
123
124 let prim_size = local_rect.size();
126 if prim_size.width > MIN_AA_SEGMENTS_SIZE && prim_size.height > MIN_AA_SEGMENTS_SIZE {
127 quad_flags |= QuadFlags::USE_AA_SEGMENTS;
128 }
129
130 let needs_scissor = !prim_is_2d_scale_translation;
131 if !needs_scissor {
132 quad_flags |= QuadFlags::APPLY_RENDER_TASK_CLIP;
133 }
134
135 let aa_flags = if prim_is_2d_axis_aligned {
140 EdgeAaSegmentMask::empty()
141 } else {
142 EdgeAaSegmentMask::all()
143 };
144
145 let transform_id = frame_state.transforms.get_id(
146 prim_spatial_node_index,
147 pic_context.raster_spatial_node_index,
148 frame_context.spatial_tree,
149 );
150
151 if let QuadRenderStrategy::Direct = strategy {
152 let pattern = shared_pattern.unwrap_or_else(|| {
153 pattern_builder.build(
154 None,
155 &ctx,
156 &mut state,
157 )
158 });
159
160 if pattern.is_opaque {
161 quad_flags |= QuadFlags::IS_OPAQUE;
162 }
163
164 let main_prim_address = write_prim_blocks(
165 &mut frame_state.frame_gpu_data.f32,
166 *local_rect,
167 clip_chain.local_clip_rect,
168 pattern.base_color,
169 pattern.texture_input.task_id,
170 &[],
171 ScaleOffset::identity(),
172 );
173
174 frame_state.push_prim(
177 &PrimitiveCommand::quad(
178 pattern.kind,
179 pattern.shader_input,
180 pattern.texture_input.task_id,
181 prim_instance_index,
182 main_prim_address,
183 transform_id,
184 quad_flags,
185 aa_flags,
186 ),
187 prim_spatial_node_index,
188 targets,
189 );
190
191 if pattern.texture_input.task_id != RenderTaskId::INVALID {
194 frame_state
195 .surface_builder
196 .add_child_render_task(pattern.texture_input.task_id, frame_state.rg_builder);
197 }
198
199 return;
200 }
201
202 let surface = &mut frame_state.surfaces[pic_context.surface_index.0];
203 let Some(clipped_surface_rect) = surface.get_surface_rect(
204 &clip_chain.pic_coverage_rect, frame_context.spatial_tree
205 ) else {
206 return;
207 };
208
209 match strategy {
210 QuadRenderStrategy::Direct => {}
211 QuadRenderStrategy::Indirect => {
212 let pattern = shared_pattern.unwrap_or_else(|| {
213 pattern_builder.build(
214 None,
215 &ctx,
216 &mut state,
217 )
218 });
219
220 if pattern.is_opaque {
221 quad_flags |= QuadFlags::IS_OPAQUE;
222 }
223
224 let main_prim_address = write_prim_blocks(
225 &mut frame_state.frame_gpu_data.f32,
226 *local_rect,
227 clip_chain.local_clip_rect,
228 pattern.base_color,
229 pattern.texture_input.task_id,
230 &[],
231 ScaleOffset::identity(),
232 );
233
234 let task_id = add_render_task_with_mask(
240 &pattern,
241 clipped_surface_rect.size(),
242 clipped_surface_rect.min.to_f32(),
243 clip_chain.clips_range,
244 prim_spatial_node_index,
245 pic_context.raster_spatial_node_index,
246 main_prim_address,
247 transform_id,
248 aa_flags,
249 quad_flags,
250 device_pixel_scale,
251 needs_scissor,
252 frame_state.rg_builder,
253 &mut frame_state.surface_builder,
254 );
255
256 let rect = clipped_surface_rect.to_f32().cast_unit();
257 add_composite_prim(
258 pattern_builder.get_base_color(&ctx),
259 prim_instance_index,
260 rect,
261 frame_state,
262 targets,
263 &[QuadSegment { rect, task_id }],
264 );
265 }
266 QuadRenderStrategy::Tiled { x_tiles, y_tiles } => {
267 let clip_coverage_rect = surface
274 .map_to_device_rect(&clip_chain.pic_coverage_rect, frame_context.spatial_tree);
275 let clipped_surface_rect = clipped_surface_rect.to_f32();
276
277 surface.map_local_to_picture.set_target_spatial_node(
278 prim_spatial_node_index,
279 frame_context.spatial_tree,
280 );
281
282 let Some(pic_rect) = surface.map_local_to_picture.map(local_rect) else { return };
283
284 let unclipped_surface_rect = surface.map_to_device_rect(
285 &pic_rect, frame_context.spatial_tree
286 ).round_out();
287
288 scratch.quad_tile_classifier.reset(
290 x_tiles as usize,
291 y_tiles as usize,
292 *local_rect,
293 );
294
295 for i in 0 .. clip_chain.clips_range.count {
297 let clip_instance = state.clip_store.get_instance_from_range(&clip_chain.clips_range, i);
298 let clip_node = &interned_clips[clip_instance.handle];
299
300 let conversion = ClipSpaceConversion::new(
302 prim_spatial_node_index,
303 clip_node.item.spatial_node_index,
304 pic_context.visibility_spatial_node_index,
305 frame_context.spatial_tree,
306 );
307
308 let transform = match conversion {
310 ClipSpaceConversion::Local => ScaleOffset::identity(),
311 ClipSpaceConversion::ScaleOffset(scale_offset) => scale_offset,
312 ClipSpaceConversion::Transform(..) => {
313 scratch.quad_tile_classifier.add_mask_region(*local_rect);
317 continue;
318 }
319 };
320
321 match clip_node.item.kind {
323 ClipItemKind::Rectangle { mode, ref rect } => {
324 let rect = transform.map_rect(rect);
325 scratch.quad_tile_classifier.add_clip_rect(rect, mode);
326 }
327 ClipItemKind::RoundedRectangle { mode: ClipMode::Clip, ref rect, ref radius } => {
328 let rect = transform.map_rect(rect);
333 let r_tl = transform.map_size(&radius.top_left);
334 let r_tr = transform.map_size(&radius.top_right);
335 let r_br = transform.map_size(&radius.bottom_right);
336 let r_bl = transform.map_size(&radius.bottom_left);
337
338 let c_tl = LayoutRect::from_origin_and_size(
340 LayoutPoint::new(rect.min.x, rect.min.y),
341 r_tl,
342 );
343 let c_tr = LayoutRect::from_origin_and_size(
344 LayoutPoint::new(
345 rect.max.x - r_tr.width,
346 rect.min.y,
347 ),
348 r_tr,
349 );
350 let c_br = LayoutRect::from_origin_and_size(
351 LayoutPoint::new(
352 rect.max.x - r_br.width,
353 rect.max.y - r_br.height,
354 ),
355 r_br,
356 );
357 let c_bl = LayoutRect::from_origin_and_size(
358 LayoutPoint::new(
359 rect.min.x,
360 rect.max.y - r_bl.height,
361 ),
362 r_bl,
363 );
364
365 scratch.quad_tile_classifier.add_clip_rect(rect, ClipMode::Clip);
366 scratch.quad_tile_classifier.add_mask_region(c_tl);
367 scratch.quad_tile_classifier.add_mask_region(c_tr);
368 scratch.quad_tile_classifier.add_mask_region(c_br);
369 scratch.quad_tile_classifier.add_mask_region(c_bl);
370 }
371 ClipItemKind::RoundedRectangle { mode: ClipMode::ClipOut, ref rect, ref radius } => {
372 match extract_inner_rect_k(rect, radius, 0.5) {
375 Some(ref rect) => {
376 let rect = transform.map_rect(rect);
377 scratch.quad_tile_classifier.add_clip_rect(rect, ClipMode::ClipOut);
378 }
379 None => {
380 scratch.quad_tile_classifier.add_mask_region(*local_rect);
381 }
382 }
383 }
384 ClipItemKind::BoxShadow { .. } => {
385 panic!("bug: old box-shadow clips unexpected in this path");
386 }
387 ClipItemKind::Image { .. } => {
388 panic!("bug: image clips unexpected in this path");
389 }
390 }
391 }
392
393 let tile_info = scratch.quad_tile_classifier.classify();
395 scratch.quad_direct_segments.clear();
396 scratch.quad_indirect_segments.clear();
397
398 let mut x_coords = vec![unclipped_surface_rect.min.x];
399 let mut y_coords = vec![unclipped_surface_rect.min.y];
400
401 let dx = (unclipped_surface_rect.max.x - unclipped_surface_rect.min.x) as f32 / x_tiles as f32;
402 let dy = (unclipped_surface_rect.max.y - unclipped_surface_rect.min.y) as f32 / y_tiles as f32;
403
404 for x in 1 .. (x_tiles as i32) {
405 x_coords.push((unclipped_surface_rect.min.x as f32 + x as f32 * dx).round());
406 }
407 for y in 1 .. (y_tiles as i32) {
408 y_coords.push((unclipped_surface_rect.min.y as f32 + y as f32 * dy).round());
409 }
410
411 x_coords.push(unclipped_surface_rect.max.x);
412 y_coords.push(unclipped_surface_rect.max.y);
413
414 for y in 0 .. y_coords.len()-1 {
415 let y0 = y_coords[y];
416 let y1 = y_coords[y+1];
417
418 if y1 <= y0 {
419 continue;
420 }
421
422 for x in 0 .. x_coords.len()-1 {
423 let x0 = x_coords[x];
424 let x1 = x_coords[x+1];
425
426 if x1 <= x0 {
427 continue;
428 }
429
430 let tile_info = &tile_info[y * x_tiles as usize + x];
432 let is_direct = match tile_info.kind {
433 QuadTileKind::Clipped => {
434 continue;
436 }
437 QuadTileKind::Pattern { has_mask } => {
438 prim_is_2d_scale_translation && !has_mask && shared_pattern.is_some()
439 }
440 };
441
442 let int_rect = DeviceRect {
443 min: point2(x0, y0),
444 max: point2(x1, y1),
445 };
446
447 let int_rect = match clipped_surface_rect.intersection(&int_rect) {
448 Some(rect) => rect,
449 None => continue,
450 };
451
452 let rect = int_rect.to_f32();
453
454 let int_rect_size = int_rect.round().to_i32().size();
459 if int_rect_size.is_empty() {
460 continue;
461 }
462
463 if is_direct {
464 scratch.quad_direct_segments.push(QuadSegment { rect: rect.cast_unit(), task_id: RenderTaskId::INVALID });
465 } else {
466 let pattern = match shared_pattern {
467 Some(ref shared_pattern) => shared_pattern.clone(),
468 None => {
469 pattern_builder.build(
470 Some(rect),
471 &ctx,
472 &mut state,
473 )
474 }
475 };
476
477 if pattern.is_opaque {
478 quad_flags |= QuadFlags::IS_OPAQUE;
479 }
480
481 let main_prim_address = write_prim_blocks(
482 &mut state.frame_gpu_data.f32,
483 *local_rect,
484 clip_chain.local_clip_rect,
485 pattern.base_color,
486 pattern.texture_input.task_id,
487 &[],
488 ScaleOffset::identity(),
489 );
490
491 let task_id = add_render_task_with_mask(
492 &pattern,
493 int_rect_size,
494 rect.min,
495 clip_chain.clips_range,
496 prim_spatial_node_index,
497 pic_context.raster_spatial_node_index,
498 main_prim_address,
499 transform_id,
500 aa_flags,
501 quad_flags,
502 device_pixel_scale,
503 needs_scissor,
504 state.rg_builder,
505 &mut frame_state.surface_builder,
506 );
507
508 scratch.quad_indirect_segments.push(QuadSegment { rect: rect.cast_unit(), task_id });
509 }
510 }
511 }
512
513 if !scratch.quad_direct_segments.is_empty() {
514 let local_to_device = map_prim_to_raster.as_2d_scale_offset()
515 .expect("bug: nine-patch segments should be axis-aligned only")
516 .then_scale(device_pixel_scale.0);
517
518 let device_prim_rect: DeviceRect = local_to_device.map_rect(&local_rect);
519
520 let pattern = match shared_pattern {
521 Some(ref shared_pattern) => shared_pattern.clone(),
522 None => {
523 pattern_builder.build(
524 Some(device_prim_rect),
525 &ctx,
526 &mut state,
527 )
528 }
529 };
530
531 add_pattern_prim(
532 &pattern,
533 local_to_device.inverse(),
534 prim_instance_index,
535 device_prim_rect.cast_unit(),
536 clip_coverage_rect.cast_unit(),
537 pattern.is_opaque,
538 frame_state,
539 targets,
540 &scratch.quad_direct_segments,
541 );
542 }
543
544 if !scratch.quad_indirect_segments.is_empty() {
545 add_composite_prim(
546 pattern_builder.get_base_color(&ctx),
547 prim_instance_index,
548 clip_coverage_rect.cast_unit(),
549 frame_state,
550 targets,
551 &scratch.quad_indirect_segments,
552 );
553 }
554 }
555 QuadRenderStrategy::NinePatch { clip_rect, radius } => {
556 let clip_coverage_rect = surface
563 .map_to_device_rect(&clip_chain.pic_coverage_rect, frame_context.spatial_tree);
564
565 let local_to_device = map_prim_to_raster.as_2d_scale_offset()
566 .expect("bug: nine-patch segments should be axis-aligned only")
567 .then_scale(device_pixel_scale.0);
568
569 let device_prim_rect: DeviceRect = local_to_device.map_rect(&local_rect);
570
571 let local_corner_0 = LayoutRect::new(
572 clip_rect.min,
573 clip_rect.min + radius,
574 );
575
576 let local_corner_1 = LayoutRect::new(
577 clip_rect.max - radius,
578 clip_rect.max,
579 );
580
581 let pic_corner_0 = pic_state.map_local_to_pic.map(&local_corner_0).unwrap();
582 let pic_corner_1 = pic_state.map_local_to_pic.map(&local_corner_1).unwrap();
583
584 let surface_rect_0 = surface.map_to_device_rect(
585 &pic_corner_0,
586 frame_context.spatial_tree,
587 ).round_out().to_i32();
588
589 let surface_rect_1 = surface.map_to_device_rect(
590 &pic_corner_1,
591 frame_context.spatial_tree,
592 ).round_out().to_i32();
593
594 let p0 = surface_rect_0.min;
595 let p1 = surface_rect_0.max;
596 let p2 = surface_rect_1.min;
597 let p3 = surface_rect_1.max;
598
599 let mut x_coords = [p0.x, p1.x, p2.x, p3.x];
600 let mut y_coords = [p0.y, p1.y, p2.y, p3.y];
601
602 x_coords.sort_by(|a, b| a.partial_cmp(b).unwrap());
603 y_coords.sort_by(|a, b| a.partial_cmp(b).unwrap());
604
605 scratch.quad_direct_segments.clear();
606 scratch.quad_indirect_segments.clear();
607
608 let mode = ClipMode::Clip;
610
611 fn should_create_task(mode: ClipMode, x: usize, y: usize) -> bool {
612 match mode {
613 ClipMode::Clip => x != 1 && y != 1,
615 ClipMode::ClipOut => true,
618 }
619 }
620
621 for y in 0 .. y_coords.len()-1 {
622 let y0 = y_coords[y];
623 let y1 = y_coords[y+1];
624
625 if y1 <= y0 {
626 continue;
627 }
628
629 for x in 0 .. x_coords.len()-1 {
630 if mode == ClipMode::ClipOut && x == 1 && y == 1 {
631 continue;
632 }
633
634 let x0 = x_coords[x];
635 let x1 = x_coords[x+1];
636
637 if x1 <= x0 {
638 continue;
639 }
640
641 let rect = DeviceIntRect::new(point2(x0, y0), point2(x1, y1));
642
643 let device_rect = match rect.intersection(&clipped_surface_rect) {
644 Some(rect) => rect,
645 None => {
646 continue;
647 }
648 };
649
650 if should_create_task(mode, x, y) {
651 let pattern = shared_pattern
652 .as_ref()
653 .expect("bug: nine-patch expects shared pattern, for now");
654
655 if pattern.is_opaque {
656 quad_flags |= QuadFlags::IS_OPAQUE;
657 }
658
659 let main_prim_address = write_prim_blocks(
660 &mut state.frame_gpu_data.f32,
661 *local_rect,
662 clip_chain.local_clip_rect,
663 pattern.base_color,
664 pattern.texture_input.task_id,
665 &[],
666 ScaleOffset::identity(),
667 );
668
669 let task_id = add_render_task_with_mask(
670 &pattern,
671 device_rect.size(),
672 device_rect.min.to_f32(),
673 clip_chain.clips_range,
674 prim_spatial_node_index,
675 pic_context.raster_spatial_node_index,
676 main_prim_address,
677 transform_id,
678 aa_flags,
679 quad_flags,
680 device_pixel_scale,
681 false,
682 state.rg_builder,
683 &mut frame_state.surface_builder,
684 );
685 scratch.quad_indirect_segments.push(QuadSegment {
686 rect: device_rect.to_f32().cast_unit(),
687 task_id,
688 });
689 } else {
690 scratch.quad_direct_segments.push(QuadSegment {
691 rect: device_rect.to_f32().cast_unit(),
692 task_id: RenderTaskId::INVALID,
693 });
694 };
695 }
696 }
697
698 if !scratch.quad_direct_segments.is_empty() {
699 let pattern = pattern_builder.build(
700 None,
701 &ctx,
702 &mut state,
703 );
704
705 add_pattern_prim(
706 &pattern,
707 local_to_device.inverse(),
708 prim_instance_index,
709 device_prim_rect.cast_unit(),
710 clip_coverage_rect.cast_unit(),
711 pattern.is_opaque,
712 frame_state,
713 targets,
714 &scratch.quad_direct_segments,
715 );
716 }
717
718 if !scratch.quad_indirect_segments.is_empty() {
719 add_composite_prim(
720 pattern_builder.get_base_color(&ctx),
721 prim_instance_index,
722 clip_coverage_rect.cast_unit(),
723 frame_state,
724 targets,
725 &scratch.quad_indirect_segments,
726 );
727 }
728 }
729 }
730}
731
732fn get_prim_render_strategy(
733 prim_spatial_node_index: SpatialNodeIndex,
734 clip_chain: &ClipChainInstance,
735 clip_store: &ClipStore,
736 interned_clips: &DataStore<ClipIntern>,
737 can_use_nine_patch: bool,
738 spatial_tree: &SpatialTree,
739) -> QuadRenderStrategy {
740 if !clip_chain.needs_mask {
741 return QuadRenderStrategy::Direct
742 }
743
744 fn tile_count_for_size(size: f32) -> u16 {
745 (size / MIN_QUAD_SPLIT_SIZE).min(MAX_TILES_PER_QUAD as f32).max(1.0).ceil() as u16
746 }
747
748 let prim_coverage_size = clip_chain.pic_coverage_rect.size();
749 let x_tiles = tile_count_for_size(prim_coverage_size.width);
750 let y_tiles = tile_count_for_size(prim_coverage_size.height);
751 let try_split_prim = x_tiles > 1 || y_tiles > 1;
752
753 if !try_split_prim {
754 return QuadRenderStrategy::Indirect;
755 }
756
757 if can_use_nine_patch && clip_chain.clips_range.count == 1 {
758 let clip_instance = clip_store.get_instance_from_range(&clip_chain.clips_range, 0);
759 let clip_node = &interned_clips[clip_instance.handle];
760
761 if let ClipItemKind::RoundedRectangle { ref radius, mode: ClipMode::Clip, rect, .. } = clip_node.item.kind {
762 let max_corner_width = radius.top_left.width
763 .max(radius.bottom_left.width)
764 .max(radius.top_right.width)
765 .max(radius.bottom_right.width);
766 let max_corner_height = radius.top_left.height
767 .max(radius.bottom_left.height)
768 .max(radius.top_right.height)
769 .max(radius.bottom_right.height);
770
771 if max_corner_width <= 0.5 * rect.size().width &&
772 max_corner_height <= 0.5 * rect.size().height {
773
774 let clip_prim_coords_match = spatial_tree.is_matching_coord_system(
775 prim_spatial_node_index,
776 clip_node.item.spatial_node_index,
777 );
778
779 if clip_prim_coords_match {
780 let map_clip_to_prim = SpaceMapper::new_with_target(
781 prim_spatial_node_index,
782 clip_node.item.spatial_node_index,
783 LayoutRect::max_rect(),
784 spatial_tree,
785 );
786
787 if let Some(rect) = map_clip_to_prim.map(&rect) {
788 return QuadRenderStrategy::NinePatch {
789 radius: LayoutVector2D::new(max_corner_width, max_corner_height),
790 clip_rect: rect,
791 };
792 }
793 }
794 }
795 }
796 }
797
798 QuadRenderStrategy::Tiled {
799 x_tiles,
800 y_tiles,
801 }
802}
803
804fn add_render_task_with_mask(
805 pattern: &Pattern,
806 task_size: DeviceIntSize,
807 content_origin: DevicePoint,
808 clips_range: ClipNodeRange,
809 prim_spatial_node_index: SpatialNodeIndex,
810 raster_spatial_node_index: SpatialNodeIndex,
811 prim_address_f: GpuBufferAddress,
812 transform_id: TransformPaletteId,
813 aa_flags: EdgeAaSegmentMask,
814 quad_flags: QuadFlags,
815 device_pixel_scale: DevicePixelScale,
816 needs_scissor_rect: bool,
817 rg_builder: &mut RenderTaskGraphBuilder,
818 surface_builder: &mut SurfaceBuilder,
819) -> RenderTaskId {
820 let task_id = rg_builder.add().init(RenderTask::new_dynamic(
821 task_size,
822 RenderTaskKind::new_prim(
823 pattern.kind,
824 pattern.shader_input,
825 raster_spatial_node_index,
826 device_pixel_scale,
827 content_origin,
828 prim_address_f,
829 transform_id,
830 aa_flags,
831 quad_flags,
832 needs_scissor_rect,
833 pattern.texture_input.task_id,
834 ),
835 ));
836
837 if pattern.texture_input.task_id != RenderTaskId::INVALID {
840 rg_builder.add_dependency(task_id, pattern.texture_input.task_id);
841 }
842
843 if clips_range.count > 0 {
844 let masks = MaskSubPass {
845 clip_node_range: clips_range,
846 prim_spatial_node_index,
847 prim_address_f,
848 };
849
850 let task = rg_builder.get_task_mut(task_id);
851 task.add_sub_pass(SubPass::Masks { masks });
852 }
853
854 surface_builder.add_child_render_task(task_id, rg_builder);
855
856 task_id
857}
858
859fn add_pattern_prim(
860 pattern: &Pattern,
861 pattern_transform: ScaleOffset,
862 prim_instance_index: PrimitiveInstanceIndex,
863 rect: LayoutRect,
864 clip_rect: LayoutRect,
865 is_opaque: bool,
866 frame_state: &mut FrameBuildingState,
867 targets: &[CommandBufferIndex],
868 segments: &[QuadSegment],
869) {
870 let prim_address = write_prim_blocks(
871 &mut frame_state.frame_gpu_data.f32,
872 rect,
873 clip_rect,
874 pattern.base_color,
875 pattern.texture_input.task_id,
876 segments,
877 pattern_transform,
878 );
879
880 frame_state.set_segments(segments, targets);
881
882 let mut quad_flags = QuadFlags::IGNORE_DEVICE_PIXEL_SCALE
883 | QuadFlags::APPLY_RENDER_TASK_CLIP;
884
885 if is_opaque {
886 quad_flags |= QuadFlags::IS_OPAQUE;
887 }
888
889 frame_state.push_cmd(
890 &PrimitiveCommand::quad(
891 pattern.kind,
892 pattern.shader_input,
893 pattern.texture_input.task_id,
894 prim_instance_index,
895 prim_address,
896 TransformPaletteId::IDENTITY,
897 quad_flags,
898 EdgeAaSegmentMask::empty(),
900 ),
901 targets,
902 );
903}
904
905fn add_composite_prim(
906 base_color: ColorF,
907 prim_instance_index: PrimitiveInstanceIndex,
908 rect: LayoutRect,
909 frame_state: &mut FrameBuildingState,
910 targets: &[CommandBufferIndex],
911 segments: &[QuadSegment],
912) {
913 assert!(!segments.is_empty());
914
915 let composite_prim_address = write_prim_blocks(
916 &mut frame_state.frame_gpu_data.f32,
917 rect,
918 rect,
919 base_color,
925 RenderTaskId::INVALID,
926 segments,
927 ScaleOffset::identity(),
928 );
929
930 frame_state.set_segments(segments, targets);
931
932 let quad_flags = QuadFlags::IGNORE_DEVICE_PIXEL_SCALE
933 | QuadFlags::APPLY_RENDER_TASK_CLIP;
934
935 frame_state.push_cmd(
936 &PrimitiveCommand::quad(
937 PatternKind::ColorOrTexture,
938 PatternShaderInput::default(),
939 RenderTaskId::INVALID,
940 prim_instance_index,
941 composite_prim_address,
942 TransformPaletteId::IDENTITY,
943 quad_flags,
944 EdgeAaSegmentMask::empty(),
946 ),
947 targets,
948 );
949}
950
951pub fn write_prim_blocks(
952 builder: &mut GpuBufferBuilderF,
953 prim_rect: LayoutRect,
954 clip_rect: LayoutRect,
955 pattern_base_color: ColorF,
956 pattern_texture_input: RenderTaskId,
957 segments: &[QuadSegment],
958 scale_offset: ScaleOffset,
959) -> GpuBufferAddress {
960 let mut writer = builder.write_blocks(5 + segments.len() * 2);
961
962 writer.push_one(prim_rect);
963 writer.push_one(clip_rect);
964 writer.push_render_task(pattern_texture_input);
965 writer.push_one(scale_offset);
966 writer.push_one(pattern_base_color.premultiplied());
967
968 for segment in segments {
969 writer.push_one(segment.rect);
970 writer.push_render_task(segment.task_id)
971 }
972
973 writer.finish()
974}
975
976pub fn add_to_batch<F>(
977 kind: PatternKind,
978 pattern_input: PatternShaderInput,
979 dst_task_address: RenderTaskAddress,
980 transform_id: TransformPaletteId,
981 prim_address_f: GpuBufferAddress,
982 quad_flags: QuadFlags,
983 edge_flags: EdgeAaSegmentMask,
984 segment_index: u8,
985 src_task_id: RenderTaskId,
986 z_id: ZBufferId,
987 render_tasks: &RenderTaskGraph,
988 gpu_buffer_builder: &mut GpuBufferBuilder,
989 mut f: F,
990) where F: FnMut(BatchKey, PrimitiveInstanceData) {
991
992 #[repr(u8)]
994 enum PartIndex {
995 Center = 0,
996 Left = 1,
997 Top = 2,
998 Right = 3,
999 Bottom = 4,
1000 All = 5,
1001 }
1002
1003 let mut writer = gpu_buffer_builder.i32.write_blocks(1);
1005 writer.push_one([
1006 transform_id.0 as i32,
1007 z_id.0,
1008 pattern_input.0,
1009 pattern_input.1,
1010 ]);
1011 let prim_address_i = writer.finish();
1012
1013 let texture = match src_task_id {
1014 RenderTaskId::INVALID => TextureSource::Invalid,
1015 _ => {
1016 let texture = render_tasks
1017 .resolve_texture(src_task_id)
1018 .expect("bug: valid task id must be resolvable");
1019
1020 texture
1021 }
1022 };
1023
1024 let textures = BatchTextures::prim_textured(
1025 texture,
1026 TextureSource::Invalid,
1027 );
1028
1029 let default_blend_mode = if quad_flags.contains(QuadFlags::IS_OPAQUE) {
1030 BlendMode::None
1031 } else {
1032 BlendMode::PremultipliedAlpha
1033 };
1034
1035 let edge_flags_bits = edge_flags.bits();
1036
1037 let prim_batch_key = BatchKey {
1038 blend_mode: default_blend_mode,
1039 kind: BatchKind::Quad(kind),
1040 textures,
1041 };
1042
1043 let aa_batch_key = BatchKey {
1044 blend_mode: BlendMode::PremultipliedAlpha,
1045 kind: BatchKind::Quad(kind),
1046 textures,
1047 };
1048
1049 let mut instance = QuadInstance {
1050 dst_task_address,
1051 prim_address_i,
1052 prim_address_f,
1053 edge_flags: edge_flags_bits,
1054 quad_flags: quad_flags.bits(),
1055 part_index: PartIndex::All as u8,
1056 segment_index,
1057 };
1058
1059 if edge_flags.is_empty() {
1060 f(prim_batch_key, instance.into());
1062 } else if quad_flags.contains(QuadFlags::USE_AA_SEGMENTS) {
1063 if edge_flags.contains(EdgeAaSegmentMask::LEFT) {
1066 let instance = QuadInstance {
1067 part_index: PartIndex::Left as u8,
1068 ..instance
1069 };
1070 f(aa_batch_key, instance.into());
1071 }
1072 if edge_flags.contains(EdgeAaSegmentMask::RIGHT) {
1073 let instance = QuadInstance {
1074 part_index: PartIndex::Top as u8,
1075 ..instance
1076 };
1077 f(aa_batch_key, instance.into());
1078 }
1079 if edge_flags.contains(EdgeAaSegmentMask::TOP) {
1080 let instance = QuadInstance {
1081 part_index: PartIndex::Right as u8,
1082 ..instance
1083 };
1084 f(aa_batch_key, instance.into());
1085 }
1086 if edge_flags.contains(EdgeAaSegmentMask::BOTTOM) {
1087 let instance = QuadInstance {
1088 part_index: PartIndex::Bottom as u8,
1089 ..instance
1090 };
1091 f(aa_batch_key, instance.into());
1092 }
1093
1094 instance = QuadInstance {
1095 part_index: PartIndex::Center as u8,
1096 ..instance
1097 };
1098
1099 f(prim_batch_key, instance.into());
1100 } else {
1101 f(aa_batch_key, instance.into());
1103 }
1104}
1105
1106#[allow(dead_code)]
1108#[cfg_attr(feature = "capture", derive(Serialize))]
1109#[derive(Debug, Copy, Clone, PartialEq)]
1110pub enum QuadTileKind {
1111 Clipped,
1113 Pattern {
1115 has_mask: bool,
1116 },
1117}
1118
1119#[cfg_attr(feature = "capture", derive(Serialize))]
1120#[derive(Copy, Clone, Debug)]
1121pub struct QuadTileInfo {
1122 pub rect: LayoutRect,
1123 pub kind: QuadTileKind,
1124}
1125
1126impl Default for QuadTileInfo {
1127 fn default() -> Self {
1128 QuadTileInfo {
1129 rect: LayoutRect::zero(),
1130 kind: QuadTileKind::Pattern { has_mask: false },
1131 }
1132 }
1133}
1134
1135#[cfg_attr(feature = "capture", derive(Serialize))]
1138pub struct QuadTileClassifier {
1139 buffer: [QuadTileInfo; MAX_TILES_PER_QUAD * MAX_TILES_PER_QUAD],
1140 mask_regions: Vec<LayoutRect>,
1141 clip_in_regions: Vec<LayoutRect>,
1142 clip_out_regions: Vec<LayoutRect>,
1143 rect: LayoutRect,
1144 x_tiles: usize,
1145 y_tiles: usize,
1146}
1147
1148impl QuadTileClassifier {
1149 pub fn new() -> Self {
1150 QuadTileClassifier {
1151 buffer: [QuadTileInfo::default(); MAX_TILES_PER_QUAD * MAX_TILES_PER_QUAD],
1152 mask_regions: Vec::new(),
1153 clip_in_regions: Vec::new(),
1154 clip_out_regions: Vec::new(),
1155 rect: LayoutRect::zero(),
1156 x_tiles: 0,
1157 y_tiles: 0,
1158 }
1159 }
1160
1161 pub fn reset(
1162 &mut self,
1163 x_tiles: usize,
1164 y_tiles: usize,
1165 rect: LayoutRect,
1166 ) {
1167 assert_eq!(self.x_tiles, 0);
1168 assert_eq!(self.y_tiles, 0);
1169
1170 self.x_tiles = x_tiles;
1171 self.y_tiles = y_tiles;
1172 self.rect = rect;
1173 self.mask_regions.clear();
1174 self.clip_in_regions.clear();
1175 self.clip_out_regions.clear();
1176
1177 let tw = (rect.max.x - rect.min.x) / x_tiles as f32;
1181 let th = (rect.max.y - rect.min.y) / y_tiles as f32;
1182
1183 for y in 0 .. y_tiles {
1184 for x in 0 .. x_tiles {
1185 let info = &mut self.buffer[y * x_tiles + x];
1186
1187 let p0 = LayoutPoint::new(
1188 rect.min.x + x as f32 * tw,
1189 rect.min.y + y as f32 * th,
1190 );
1191 let p1 = LayoutPoint::new(
1192 p0.x + tw,
1193 p0.y + th,
1194 );
1195
1196 info.rect = LayoutRect::new(p0, p1);
1197 info.kind = QuadTileKind::Pattern { has_mask: false };
1198 }
1199 }
1200 }
1201
1202 pub fn add_mask_region(
1204 &mut self,
1205 mask_region: LayoutRect,
1206 ) {
1207 self.mask_regions.push(mask_region);
1208 }
1209
1210 pub fn add_clip_rect(
1212 &mut self,
1213 clip_rect: LayoutRect,
1214 clip_mode: ClipMode,
1215 ) {
1216 match clip_mode {
1217 ClipMode::Clip => {
1218 self.clip_in_regions.push(clip_rect);
1219 }
1220 ClipMode::ClipOut => {
1221 self.clip_out_regions.push(clip_rect);
1222
1223 self.add_mask_region(self.rect);
1224 }
1225 }
1226 }
1227
1228 pub fn classify(
1230 &mut self,
1231 ) -> &[QuadTileInfo] {
1232 assert_ne!(self.x_tiles, 0);
1233 assert_ne!(self.y_tiles, 0);
1234
1235 let tile_count = self.x_tiles * self.y_tiles;
1236 let tiles = &mut self.buffer[0 .. tile_count];
1237
1238 for info in tiles.iter_mut() {
1239 for clip_region in &self.clip_in_regions {
1241 match info.kind {
1242 QuadTileKind::Clipped => {},
1243 QuadTileKind::Pattern { .. } => {
1244 if !clip_region.intersects(&info.rect) {
1245 info.kind = QuadTileKind::Clipped;
1246 }
1247 }
1248 }
1249
1250 }
1251
1252 for clip_region in &self.clip_out_regions {
1254 match info.kind {
1255 QuadTileKind::Clipped => {},
1256 QuadTileKind::Pattern { .. } => {
1257 if clip_region.contains_box(&info.rect) {
1258 info.kind = QuadTileKind::Clipped;
1259 }
1260 }
1261 }
1262 }
1263
1264 for mask_region in &self.mask_regions {
1266 match info.kind {
1267 QuadTileKind::Clipped | QuadTileKind::Pattern { has_mask: true, .. } => {},
1268 QuadTileKind::Pattern { ref mut has_mask, .. } => {
1269 if mask_region.intersects(&info.rect) {
1270 *has_mask = true;
1271 }
1272 }
1273 }
1274 }
1275 }
1276
1277 self.x_tiles = 0;
1278 self.y_tiles = 0;
1279
1280 tiles
1281 }
1282}
1283
1284#[cfg(test)]
1285fn qc_new(xc: usize, yc: usize, x0: f32, y0: f32, w: f32, h: f32) -> QuadTileClassifier {
1286 let mut qc = QuadTileClassifier::new();
1287
1288 qc.reset(
1289 xc,
1290 yc,
1291 LayoutRect::new(LayoutPoint::new(x0, y0), LayoutPoint::new(x0 + w, y0 + h),
1292 ));
1293
1294 qc
1295}
1296
1297#[cfg(test)]
1298fn qc_verify(mut qc: QuadTileClassifier, expected: &[QuadTileKind]) {
1299 let tiles = qc.classify();
1300
1301 assert_eq!(tiles.len(), expected.len());
1302
1303 for (tile, ex) in tiles.iter().zip(expected.iter()) {
1304 assert_eq!(tile.kind, *ex, "Failed for tile {:?}", tile.rect.to_rect());
1305 }
1306}
1307
1308#[cfg(test)]
1309const P: QuadTileKind = QuadTileKind::Pattern { has_mask: false };
1310
1311#[cfg(test)]
1312const C: QuadTileKind = QuadTileKind::Clipped;
1313
1314#[cfg(test)]
1315const M: QuadTileKind = QuadTileKind::Pattern { has_mask: true };
1316
1317#[test]
1318fn quad_classify_1() {
1319 let qc = qc_new(3, 3, 0.0, 0.0, 100.0, 100.0);
1320 qc_verify(qc, &[
1321 P, P, P,
1322 P, P, P,
1323 P, P, P,
1324 ]);
1325}
1326
1327#[test]
1328fn quad_classify_2() {
1329 let mut qc = qc_new(3, 3, 0.0, 0.0, 100.0, 100.0);
1330
1331 let rect = LayoutRect::new(LayoutPoint::new(0.0, 0.0), LayoutPoint::new(100.0, 100.0));
1332 qc.add_clip_rect(rect, ClipMode::Clip);
1333
1334 qc_verify(qc, &[
1335 P, P, P,
1336 P, P, P,
1337 P, P, P,
1338 ]);
1339}
1340
1341#[test]
1342fn quad_classify_3() {
1343 let mut qc = qc_new(3, 3, 0.0, 0.0, 100.0, 100.0);
1344
1345 let rect = LayoutRect::new(LayoutPoint::new(40.0, 40.0), LayoutPoint::new(60.0, 60.0));
1346 qc.add_clip_rect(rect, ClipMode::Clip);
1347
1348 qc_verify(qc, &[
1349 C, C, C,
1350 C, P, C,
1351 C, C, C,
1352 ]);
1353}
1354
1355#[test]
1356fn quad_classify_4() {
1357 let mut qc = qc_new(3, 3, 0.0, 0.0, 100.0, 100.0);
1358
1359 let rect = LayoutRect::new(LayoutPoint::new(30.0, 30.0), LayoutPoint::new(70.0, 70.0));
1360 qc.add_clip_rect(rect, ClipMode::Clip);
1361
1362 qc_verify(qc, &[
1363 P, P, P,
1364 P, P, P,
1365 P, P, P,
1366 ]);
1367}
1368
1369#[test]
1370fn quad_classify_5() {
1371 let mut qc = qc_new(3, 3, 0.0, 0.0, 100.0, 100.0);
1372
1373 let rect = LayoutRect::new(LayoutPoint::new(30.0, 30.0), LayoutPoint::new(70.0, 70.0));
1374 qc.add_clip_rect(rect, ClipMode::ClipOut);
1375
1376 qc_verify(qc, &[
1377 M, M, M,
1378 M, C, M,
1379 M, M, M,
1380 ]);
1381}
1382
1383#[test]
1384fn quad_classify_6() {
1385 let mut qc = qc_new(3, 3, 0.0, 0.0, 100.0, 100.0);
1386
1387 let rect = LayoutRect::new(LayoutPoint::new(40.0, 40.0), LayoutPoint::new(60.0, 60.0));
1388 qc.add_clip_rect(rect, ClipMode::ClipOut);
1389
1390 qc_verify(qc, &[
1391 M, M, M,
1392 M, M, M,
1393 M, M, M,
1394 ]);
1395}
1396
1397#[test]
1398fn quad_classify_7() {
1399 let mut qc = qc_new(3, 3, 0.0, 0.0, 100.0, 100.0);
1400
1401 let rect = LayoutRect::new(LayoutPoint::new(20.0, 10.0), LayoutPoint::new(90.0, 80.0));
1402 qc.add_mask_region(rect);
1403
1404 qc_verify(qc, &[
1405 M, M, M,
1406 M, M, M,
1407 M, M, M,
1408 ]);
1409}
1410
1411#[test]
1412fn quad_classify_8() {
1413 let mut qc = qc_new(3, 3, 0.0, 0.0, 100.0, 100.0);
1414
1415 let rect = LayoutRect::new(LayoutPoint::new(40.0, 40.0), LayoutPoint::new(60.0, 60.0));
1416 qc.add_mask_region(rect);
1417
1418 qc_verify(qc, &[
1419 P, P, P,
1420 P, M, P,
1421 P, P, P,
1422 ]);
1423}
1424
1425#[test]
1426fn quad_classify_9() {
1427 let mut qc = qc_new(4, 4, 100.0, 200.0, 100.0, 100.0);
1428
1429 let rect = LayoutRect::new(LayoutPoint::new(90.0, 180.0), LayoutPoint::new(140.0, 240.0));
1430 qc.add_mask_region(rect);
1431
1432 qc_verify(qc, &[
1433 M, M, P, P,
1434 M, M, P, P,
1435 P, P, P, P,
1436 P, P, P, P,
1437 ]);
1438}
1439
1440#[test]
1441fn quad_classify_10() {
1442 let mut qc = qc_new(4, 4, 100.0, 200.0, 100.0, 100.0);
1443
1444 let mask_rect = LayoutRect::new(LayoutPoint::new(90.0, 180.0), LayoutPoint::new(140.0, 240.0));
1445 qc.add_mask_region(mask_rect);
1446
1447 let clip_rect = LayoutRect::new(LayoutPoint::new(120.0, 220.0), LayoutPoint::new(160.0, 280.0));
1448 qc.add_clip_rect(clip_rect, ClipMode::Clip);
1449
1450 qc_verify(qc, &[
1451 M, M, P, C,
1452 M, M, P, C,
1453 P, P, P, C,
1454 P, P, P, C,
1455 ]);
1456}
1457
1458#[test]
1459fn quad_classify_11() {
1460 let mut qc = qc_new(4, 4, 100.0, 200.0, 100.0, 100.0);
1461
1462 let mask_rect = LayoutRect::new(LayoutPoint::new(90.0, 180.0), LayoutPoint::new(140.0, 240.0));
1463 qc.add_mask_region(mask_rect);
1464
1465 let clip_rect = LayoutRect::new(LayoutPoint::new(120.0, 220.0), LayoutPoint::new(160.0, 280.0));
1466 qc.add_clip_rect(clip_rect, ClipMode::Clip);
1467
1468 let clip_out_rect = LayoutRect::new(LayoutPoint::new(130.0, 200.0), LayoutPoint::new(160.0, 240.0));
1469 qc.add_clip_rect(clip_out_rect, ClipMode::ClipOut);
1470
1471 qc_verify(qc, &[
1472 M, M, M, C,
1473 M, M, M, C,
1474 M, M, M, C,
1475 M, M, M, C,
1476 ]);
1477}
1478
1479#[test]
1480fn quad_classify_12() {
1481 let mut qc = qc_new(4, 4, 100.0, 200.0, 100.0, 100.0);
1482
1483 let clip_out_rect = LayoutRect::new(LayoutPoint::new(130.0, 200.0), LayoutPoint::new(160.0, 240.0));
1484 qc.add_clip_rect(clip_out_rect, ClipMode::ClipOut);
1485
1486 let clip_rect = LayoutRect::new(LayoutPoint::new(120.0, 220.0), LayoutPoint::new(160.0, 280.0));
1487 qc.add_clip_rect(clip_rect, ClipMode::Clip);
1488
1489 let mask_rect = LayoutRect::new(LayoutPoint::new(90.0, 180.0), LayoutPoint::new(140.0, 240.0));
1490 qc.add_mask_region(mask_rect);
1491
1492 qc_verify(qc, &[
1493 M, M, M, C,
1494 M, M, M, C,
1495 M, M, M, C,
1496 M, M, M, C,
1497 ]);
1498}