webrender/
quad.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5use 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/// Describes how clipping affects the rendering of a quad primitive.
31///
32/// As a general rule, parts of the quad that require masking are prerendered in an
33/// intermediate target and the mask is applied using multiplicative blending to
34/// the intermediate result before compositing it into the destination target.
35///
36/// Each segment can opt in or out of masking independently.
37#[derive(Debug, Copy, Clone)]
38pub enum QuadRenderStrategy {
39    /// The quad is not affected by any mask and is drawn directly in the destination
40    /// target.
41    Direct,
42    /// The quad is drawn entirely in an intermediate target and a mask is applied
43    /// before compositing in the destination target.
44    Indirect,
45    /// A rounded rectangle clip is applied to the quad primitive via a nine-patch.
46    /// The segments of the nine-patch that require a mask are rendered and masked in
47    /// an intermediate target, while other segments are drawn directly in the destination
48    /// target.
49    NinePatch {
50        radius: LayoutVector2D,
51        clip_rect: LayoutRect,
52    },
53    /// Split the primitive into coarse tiles so that each tile independently
54    /// has the opportunity to be drawn directly in the destination target or
55    /// via an intermediate target if it is affected by a mask.
56    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    // TODO(gw): Can't support 9-patch for box-shadows for now as should_create_task
110    //           assumes pattern is solid. This is a temporary hack until as once that's
111    //           fixed we can select 9-patch for box-shadows
112    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    // Only use AA edge instances if the primitive is large enough to require it
126    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    // TODO(gw): For now, we don't select per-edge AA at all if the primitive
137    //           has a 2d transform, which matches existing behavior. However,
138    //           as a follow up, we can now easily check if we have a 2d-aligned
139    //           primitive on a subpixel boundary, and enable AA along those edge(s).
140    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        // Render the primitive as a single instance. Coordinates are provided to the
176        // shader in layout space.
177        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 the pattern samples from a texture, add it as a dependency
193        // of the surface we're drawing directly on to.
194        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            // Render the primtive as a single instance in a render task, apply a mask
236            // and composite it in the current picture.
237            // The coordinates are provided to the shaders:
238            //  - in layout space for the render task,
239            //  - in device space for the instance that draw into the destination picture.
240            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            // Render the primtive as a grid of tiles decomposed in device space.
269            // Tiles that need it are drawn in a render task and then composited into the
270            // destination picture.
271            // The coordinates are provided to the shaders:
272            //  - in layout space for the render task,
273            //  - in device space for the instances that draw into the destination picture.
274            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            // Set up the tile classifier for the params of this quad
290            scratch.quad_tile_classifier.reset(
291                x_tiles as usize,
292                y_tiles as usize,
293                *local_rect,
294            );
295
296            // Walk each clip, extract the local mask regions and add them to the tile classifier.
297            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                // Construct a prim <-> clip space converter
302                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                // For now, we only handle axis-aligned mappings
310                let transform = match conversion {
311                    ClipSpaceConversion::Local => ScaleOffset::identity(),
312                    ClipSpaceConversion::ScaleOffset(scale_offset) => scale_offset,
313                    ClipSpaceConversion::Transform(..) => {
314                        // If the clip transform is not axis-aligned, just assume the entire primitive
315                        // local rect is affected by the clip, for now. It's no worse than what
316                        // we were doing previously for all tiles.
317                        scratch.quad_tile_classifier.add_mask_region(*local_rect);
318                        continue;
319                    }
320                };
321
322                // Add regions to the classifier depending on the clip kind
323                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                        // For rounded-rects with Clip mode, we need a mask for each corner,
330                        // and to add the clip rect itself (to cull tiles outside that rect)
331
332                        // Map the local rect and radii
333                        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                        // Construct the mask regions for each corner
340                        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                        // Try to find an inner rect within the clip-out rounded rect that we can
374                        // use to cull inner tiles. If we can't, the entire rect needs to be masked
375                        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            // Classify each tile within the quad to be Pattern / Mask / Clipped
395            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                    // Check whether this tile requires a mask
432                    let tile_info = &tile_info[y * x_tiles as usize + x];
433                    let is_direct = match tile_info.kind {
434                        QuadTileKind::Clipped => {
435                            // This tile was entirely clipped, so we can skip drawing it
436                            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                    // At extreme scales the rect can round to zero size due to
456                    // f32 precision, causing a panic in new_dynamic, so just
457                    // skip segments that would produce zero size tasks.
458                    // https://bugzilla.mozilla.org/show_bug.cgi?id=1941838#c13
459                    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            // Render the primtive as a nine-patch decomposed in device space.
558            // Nine-patch segments that need it are drawn in a render task and then composited into the
559            // destination picture.
560            // The coordinates are provided to the shaders:
561            //  - in layout space for the render task,
562            //  - in device space for the instances that draw into the destination picture.
563            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            // TODO: re-land clip-out mode.
610            let mode = ClipMode::Clip;
611
612            fn should_create_task(mode: ClipMode, x: usize, y: usize) -> bool {
613                match mode {
614                    // Only create render tasks for the corners.
615                    ClipMode::Clip => x != 1 && y != 1,
616                    // Create render tasks for all segments (the
617                    // center will be skipped).
618                    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 the pattern samples from a texture, add it as a dependency
839    // of the indirect render task that relies on it.
840    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            // TODO(gw): No AA on composite, unless we use it to apply 2d clips
900            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        // TODO: The base color for composite prim should be opaque white
921        // (or white with some transparency to support an opacity directly
922        // in the quad primitive). However, passing opaque white
923        // here causes glitches with Adreno GPUs on Windows specifically
924        // (See bug 1897444).
925        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            // TODO(gw): No AA on composite, unless we use it to apply 2d clips
946            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    // See the corresponfing #defines in ps_quad.glsl
994    #[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    // See QuadHeader in ps_quad.glsl
1005    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        // No antialisaing.
1062        f(prim_batch_key, instance.into());
1063    } else if quad_flags.contains(QuadFlags::USE_AA_SEGMENTS) {
1064        // Add instances for the antialisaing. This gives the center part
1065        // an opportunity to stay in the opaque pass.
1066        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        // Render the anti-aliased quad with a single primitive.
1103        f(aa_batch_key, instance.into());
1104    }
1105}
1106
1107/// Classification result for a tile within a quad
1108#[allow(dead_code)]
1109#[cfg_attr(feature = "capture", derive(Serialize))]
1110#[derive(Debug, Copy, Clone, PartialEq)]
1111pub enum QuadTileKind {
1112    // Clipped out - can be skipped
1113    Clipped,
1114    // Requires the pattern only, can draw directly
1115    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/// A helper struct for classifying a set of tiles within a quad depending on
1137/// what strategy they can be used to draw them.
1138#[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        // TODO(gw): Might be some f32 accuracy issues with how we construct these,
1179        //           should be more robust here...
1180
1181        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    /// Add an area that needs a clip mask / indirect area
1204    pub fn add_mask_region(
1205        &mut self,
1206        mask_region: LayoutRect,
1207    ) {
1208        self.mask_regions.push(mask_region);
1209    }
1210
1211    // TODO(gw): Make use of this to skip tiles that are completely clipped out in a follow up!
1212    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    /// Classify all the tiles in to categories, based on the provided masks and clip regions
1230    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            // If a clip region contains the entire tile, it's clipped
1241            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            // If a tile doesn't intersect with a clip-out region, it's clipped
1254            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            // If a tile intersects with a mask region, and isn't clipped, it needs a mask
1266            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}