tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

quad.rs (61047B)


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