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