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