Skip to main content

webrender/
prepare.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
5//! # Prepare pass
6//!
7//! TODO: document this!
8
9use api::{BoxShadowClipMode, ColorF, DebugFlags, ExtendMode, GradientStop};
10use api::ClipMode;
11use crate::pattern::cutout::Cutout;
12use crate::util::clamp_to_scale_factor;
13use crate::box_shadow::{BoxShadowCacheKey, BLUR_SAMPLE_SCALE};
14use crate::pattern::box_shadow::BoxShadowPatternData;
15use crate::pattern::gradient::linear_gradient_pattern;
16use crate::pattern::{Pattern, PatternBuilder, PatternBuilderContext, PatternBuilderState};
17use crate::prim_store::gradient::{decompose_axis_aligned_gradient, linear_gradient_decomposes};
18use crate::segment::EdgeMask;
19use api::units::*;
20use euclid::Scale;
21use smallvec::SmallVec;
22use crate::composite::CompositorSurfaceKind;
23use crate::command_buffer::{CommandBufferIndex, PrimitiveCommand};
24use crate::border;
25use crate::clip::{ClipStore, ClipNodeRange};
26use crate::renderer::{GpuBufferAddress, GpuBufferWriterF};
27use crate::spatial_tree::SpatialNodeIndex;
28use crate::clip::{clamped_radius, ClipNodeFlags, ClipChainInstance, ClipItemKind};
29use crate::frame_builder::{FrameBuildingContext, FrameBuildingState, PictureContext, PictureState};
30use crate::gpu_types::{BrushFlags, BlurEdgeMode};
31use crate::render_target::RenderTargetKind;
32use crate::internal_types::{FastHashMap, PlaneSplitAnchor, Filter};
33use crate::picture::{ClusterFlags, PictureCompositeMode, PictureInstance, PictureScratch};
34use crate::picture::{PrimitiveList, PrimitiveCluster, SurfaceIndex, SubpixelMode, Picture3DContext};
35use crate::tile_cache::{SliceId, TileCacheInstance};
36use crate::prim_store::*;
37use crate::prim_store::backdrop::BackdropRenderScratch;
38use crate::prim_store::borders::{ImageBorderScratch, NormalBorderScratch};
39use crate::prim_store::line_dec::LineDecorationScratch;
40use crate::quad::{self, QuadTransformState};
41use crate::render_backend::DataStores;
42use crate::render_task_cache::RenderTaskCacheKeyKind;
43use crate::render_task_cache::{RenderTaskCacheKey, to_cache_size, RenderTaskParent};
44use crate::render_task::{EmptyTask, RenderTask, RenderTaskKind, MAX_BLUR_STD_DEVIATION};
45use crate::segment::SegmentBuilder;
46use crate::space::SpaceSnapper;
47use crate::visibility::{DrawState, KindScratchHandle};
48
49
50const MAX_MASK_SIZE: i32 = 4096;
51
52const MIN_BRUSH_SPLIT_AREA: f32 = 128.0 * 128.0;
53
54/// The entry point of the preapre pass.
55pub fn prepare_picture(
56    pic_index: PictureIndex,
57    store: &mut PrimitiveStore,
58    surface_index: Option<SurfaceIndex>,
59    subpixel_mode: SubpixelMode,
60    frame_context: &FrameBuildingContext,
61    frame_state: &mut FrameBuildingState,
62    data_stores: &mut DataStores,
63    scratch: &mut PrimitiveScratchBuffer,
64    tile_caches: &mut FastHashMap<SliceId, Box<TileCacheInstance>>,
65    prim_instances: &mut Vec<PrimitiveInstance>,
66) -> Option<storage::Index<PictureScratch>> {
67    if let Some(handle) = frame_state.picture_scratch_handles[pic_index.0] {
68        return Some(handle);
69    }
70
71    let pic = &mut store.pictures[pic_index.0];
72    let Some((pic_context, mut pic_state, mut prim_list, scratch_handle)) = pic.take_context(
73        pic_index,
74        surface_index,
75        subpixel_mode,
76        frame_state,
77        frame_context,
78        data_stores,
79        scratch,
80        tile_caches,
81    ) else {
82        // Mark as visited-without-scratch so subsequent visits short-circuit
83        // with the same INVALID handle the existing code already exposed via
84        // PictureInstance.primary_render_task_id == None.
85        frame_state.picture_scratch_handles[pic_index.0] = Some(storage::Index::INVALID);
86        return None;
87    };
88
89    frame_state.picture_scratch_handles[pic_index.0] = Some(scratch_handle);
90
91    prepare_primitives(
92        store,
93        &mut prim_list,
94        &pic_context,
95        &mut pic_state,
96        frame_context,
97        frame_state,
98        data_stores,
99        scratch,
100        tile_caches,
101        prim_instances,
102    );
103
104    // Restore the dependencies (borrow check dance)
105    store.pictures[pic_context.pic_index.0].restore_context(
106        pic_context.pic_index,
107        prim_list,
108        pic_context,
109        frame_context,
110        frame_state,
111        scratch,
112    );
113
114    Some(scratch_handle)
115}
116
117fn prepare_primitives(
118    store: &mut PrimitiveStore,
119    prim_list: &mut PrimitiveList,
120    pic_context: &PictureContext,
121    pic_state: &mut PictureState,
122    frame_context: &FrameBuildingContext,
123    frame_state: &mut FrameBuildingState,
124    data_stores: &mut DataStores,
125    scratch: &mut PrimitiveScratchBuffer,
126    tile_caches: &mut FastHashMap<SliceId, Box<TileCacheInstance>>,
127    prim_instances: &mut Vec<PrimitiveInstance>,
128) {
129    profile_scope!("prepare_primitives");
130    let mut cmd_buffer_targets = Vec::new();
131
132    let mut quad_transform = QuadTransformState::new();
133
134    for cluster in &mut prim_list.clusters {
135        if !cluster.flags.contains(ClusterFlags::IS_VISIBLE) {
136            continue;
137        }
138        profile_scope!("cluster");
139        pic_state.map_local_to_pic.set_target_spatial_node(
140            cluster.spatial_node_index,
141            frame_context.spatial_tree,
142        );
143
144        let device_pixel_scale = frame_state.surfaces[pic_context.surface_index.0].device_pixel_scale;
145        quad_transform.set(
146            cluster.spatial_node_index,
147            pic_context.raster_spatial_node_index,
148            frame_context.spatial_tree,
149            device_pixel_scale,
150        );
151
152        for prim_instance_index in cluster.prim_range() {
153            if frame_state.surface_builder.get_cmd_buffer_targets_for_prim(
154                &scratch.frame.draws[prim_instance_index],
155                &mut cmd_buffer_targets,
156            ) {
157                let plane_split_anchor = PlaneSplitAnchor::new(
158                    cluster.spatial_node_index,
159                    PrimitiveInstanceIndex(prim_instance_index as u32),
160                );
161
162                prepare_prim_for_render(
163                    store,
164                    prim_instance_index,
165                    cluster,
166                    &mut quad_transform,
167                    pic_context,
168                    pic_state,
169                    frame_context,
170                    frame_state,
171                    plane_split_anchor,
172                    data_stores,
173                    scratch,
174                    tile_caches,
175                    prim_instances,
176                    &cmd_buffer_targets,
177                );
178
179                frame_state.num_visible_primitives += 1;
180                continue;
181            }
182
183            // TODO(gw): Technically no need to clear visibility here, since from this point it
184            //           only matters if it got added to a command buffer. Kept here for now to
185            //           make debugging simpler, but perhaps we can remove / tidy this up.
186            scratch.frame.draws[prim_instance_index].reset();
187        }
188    }
189}
190
191fn can_use_clip_chain_for_quad_path(
192    clip_chain: &ClipChainInstance,
193    clip_store: &ClipStore,
194    data_stores: &DataStores,
195) -> bool {
196    if !clip_chain.needs_mask {
197        return true;
198    }
199
200    for i in 0 .. clip_chain.clips_range.count {
201        let clip_instance = clip_store.get_instance_from_range(&clip_chain.clips_range, i);
202        let clip_node = &data_stores.clip[clip_instance.handle];
203
204        match clip_node.item.kind {
205            ClipItemKind::RoundedRectangle { .. } | ClipItemKind::Rectangle { .. } => {}
206            ClipItemKind::Image { .. } => {
207                panic!("bug: image-masks not expected on rect/quads");
208            }
209        }
210    }
211
212    true
213}
214
215fn prepare_prim_for_render(
216    store: &mut PrimitiveStore,
217    prim_instance_index: usize,
218    cluster: &mut PrimitiveCluster,
219    quad_transform: &mut QuadTransformState,
220    pic_context: &PictureContext,
221    pic_state: &mut PictureState,
222    frame_context: &FrameBuildingContext,
223    frame_state: &mut FrameBuildingState,
224    plane_split_anchor: PlaneSplitAnchor,
225    data_stores: &mut DataStores,
226    scratch: &mut PrimitiveScratchBuffer,
227    tile_caches: &mut FastHashMap<SliceId, Box<TileCacheInstance>>,
228    prim_instances: &mut Vec<PrimitiveInstance>,
229    targets: &[CommandBufferIndex],
230) {
231    profile_scope!("prepare_prim_for_render");
232
233    // If we have dependencies, we need to prepare them first, in order
234    // to know the actual rect of this primitive.
235    // For example, scrolling may affect the location of an item in
236    // local space, which may force us to render this item on a larger
237    // picture target, if being composited.
238    let mut is_passthrough = false;
239    if let PrimitiveKind::Picture { pic_index, .. } = prim_instances[prim_instance_index].kind {
240        let Some(scratch_handle) = prepare_picture(
241            pic_index,
242            store,
243            Some(pic_context.surface_index),
244            pic_context.subpixel_mode,
245            frame_context,
246            frame_state,
247            data_stores,
248            scratch,
249            tile_caches,
250            prim_instances,
251        ) else {
252            return;
253        };
254
255        scratch.frame.draws[prim_instance_index].kind_scratch =
256            KindScratchHandle::Picture(scratch_handle);
257
258        is_passthrough = store
259            .pictures[pic_index.0]
260            .composite_mode
261            .is_none();
262    }
263
264    let prim_instance = &mut prim_instances[prim_instance_index];
265    let mut use_legacy_path = true;
266    if !is_passthrough {
267        match &prim_instance.kind {
268            PrimitiveKind::Rectangle { .. }
269            | PrimitiveKind::RadialGradient { .. }
270            | PrimitiveKind::ConicGradient { .. }
271            | PrimitiveKind::LinearGradient { .. }
272            | PrimitiveKind::Image { .. }
273            => {
274                use_legacy_path = false;
275            }
276            PrimitiveKind::YuvImage { .. } => {
277                let prim_info = scratch.frame.draws[prim_instance_index];
278                use_legacy_path = prim_info.compositor_surface_kind != CompositorSurfaceKind::Underlay;
279            }
280            _ => {}
281        };
282
283        // In this initial patch, we only support non-masked primitives through the new
284        // quad rendering path. Follow up patches will extend this to support masks, and
285        // then use by other primitives. In the new quad rendering path, we'll still want
286        // to skip the entry point to `update_clip_task` as that does old-style segmenting
287        // and mask generation.
288        let should_update_clip_task = match &mut prim_instance.kind {
289            PrimitiveKind::Rectangle { .. }
290            | PrimitiveKind::RadialGradient { .. }
291            | PrimitiveKind::ConicGradient { .. }
292            | PrimitiveKind::LinearGradient { .. }
293            | PrimitiveKind::Image { .. }
294            | PrimitiveKind::YuvImage { .. }
295            => {
296                use_legacy_path |= !can_use_clip_chain_for_quad_path(
297                    &scratch.frame.draws[prim_instance_index].clip_chain,
298                    frame_state.clip_store,
299                    data_stores,
300                );
301
302                use_legacy_path
303            }
304            PrimitiveKind::BoxShadow { .. } |
305            PrimitiveKind::Picture { .. } => false,
306            _ => true,
307        };
308
309        // Per-frame, per-kind segment construction that has to run
310        // before update_clip_task (which reads the segments via
311        // update_clip_task_for_brush).
312        let snapped_local_rect = scratch.frame.draws[prim_instance_index].snapped_local_rect;
313        match prim_instance.kind {
314            PrimitiveKind::NormalBorder { data_handle } => {
315                NormalBorderScratch::build_for_prim(
316                    data_handle,
317                    PrimitiveInstanceIndex(prim_instance_index as u32),
318                    snapped_local_rect.size(),
319                    data_stores,
320                    scratch,
321                );
322            }
323            PrimitiveKind::ImageBorder { data_handle } => {
324                ImageBorderScratch::build_for_prim(
325                    data_handle,
326                    PrimitiveInstanceIndex(prim_instance_index as u32),
327                    snapped_local_rect.size(),
328                    data_stores,
329                    scratch,
330                );
331            }
332            _ => {}
333        }
334
335        if should_update_clip_task {
336            let prim_rect = data_stores.get_local_prim_rect(
337                prim_instance,
338                scratch.frame.draws[prim_instance_index].snapped_local_rect,
339                &store.pictures,
340                frame_state.surfaces,
341            );
342
343            if !update_clip_task(
344                prim_instance,
345                PrimitiveInstanceIndex(prim_instance_index as u32),
346                &prim_rect.min,
347                cluster.spatial_node_index,
348                pic_context.raster_spatial_node_index,
349                pic_context.visibility_spatial_node_index,
350                pic_context,
351                pic_state,
352                frame_context,
353                frame_state,
354                store,
355                data_stores,
356                scratch,
357            ) {
358                return;
359            }
360        }
361    }
362
363    prepare_interned_prim_for_render(
364        store,
365        use_legacy_path,
366        PrimitiveInstanceIndex(prim_instance_index as u32),
367        prim_instance,
368        cluster,
369        plane_split_anchor,
370        quad_transform,
371        pic_context,
372        pic_state,
373        frame_context,
374        frame_state,
375        data_stores,
376        scratch,
377        targets,
378    )
379}
380
381/// Prepare an interned primitive for rendering, by requesting
382/// resources, render tasks etc. This is equivalent to the
383/// prepare_prim_for_render_inner call for old style primitives.
384fn prepare_interned_prim_for_render(
385    store: &mut PrimitiveStore,
386    use_legacy_path: bool,
387    prim_instance_index: PrimitiveInstanceIndex,
388    prim_instance: &mut PrimitiveInstance,
389    cluster: &mut PrimitiveCluster,
390    plane_split_anchor: PlaneSplitAnchor,
391    quad_transform: &mut QuadTransformState,
392    pic_context: &PictureContext,
393    pic_state: &mut PictureState,
394    frame_context: &FrameBuildingContext,
395    frame_state: &mut FrameBuildingState,
396    data_stores: &mut DataStores,
397    scratch: &mut PrimitiveScratchBuffer,
398    targets: &[CommandBufferIndex],
399) {
400    let prim_spatial_node_index = cluster.spatial_node_index;
401    let device_pixel_scale = frame_state.surfaces[pic_context.surface_index.0].device_pixel_scale;
402    // Snapshot of the per-frame draw header for this prim. Copy is fine here
403    // because the only field this function writes (clip_task_index, in the
404    // segmented-clip path) isn't read again in this function — and the other
405    // fields (state, clip_chain) aren't written by it.
406    let prim_info = scratch.frame.draws[prim_instance_index.0 as usize];
407
408    match &mut prim_instance.kind {
409        PrimitiveKind::BoxShadow { data_handle, .. } => {
410            profile_scope!("BoxShadow");
411
412            let prim_data = &data_stores.box_shadow[*data_handle];
413            let shadow_data = &prim_data.kind;
414            let blur_radius = shadow_data.blur_radius;
415
416            // Build snapped element/inner/outer rects. The shader expects
417            // `inner = element.translate(offset).inflate(spread)` and
418            // `outer = inner.inflate(blur_offset)`, with element snapped to
419            // the device pixel grid. Because the inflations can have
420            // fractional components, snapping the prim's whole rect and
421            // then deflating is not equivalent to snapping the element rect
422            // directly, so we always snap the element rect itself and
423            // re-inflate.
424            //
425            // The element rect's relation to the per-instance
426            // `unsnapped_prim_rect` differs by clip_mode (set up in
427            // `box_shadow::add_box_shadow`):
428            //   - Outset: prim rect = element.translate.inflate(spread)
429            //                                  .inflate(blur_offset);
430            //             recover element by reversing the construction.
431            //   - Inset:  prim rect = element directly.
432            let blur_offset = (BLUR_SAMPLE_SCALE * blur_radius).ceil();
433            let unsnapped_element_rect = match shadow_data.clip_mode {
434                BoxShadowClipMode::Outset => prim_instance.unsnapped_prim_rect
435                    .inflate(-blur_offset, -blur_offset)
436                    .inflate(-shadow_data.spread_amount, -shadow_data.spread_amount)
437                    .translate(-shadow_data.box_offset),
438                BoxShadowClipMode::Inset => prim_instance.unsnapped_prim_rect,
439            };
440            let element_rect = {
441                let mut snapper = SpaceSnapper::new(
442                    frame_context.spatial_tree.root_reference_frame_index(),
443                    RasterPixelScale::new(1.0),
444                );
445                snapper.set_target_spatial_node(prim_spatial_node_index, frame_context.spatial_tree);
446                snapper.snap_rect(&unsnapped_element_rect)
447            };
448            let inner_shadow_rect = element_rect
449                .translate(shadow_data.box_offset)
450                .inflate(shadow_data.spread_amount, shadow_data.spread_amount);
451            let outer_shadow_rect = inner_shadow_rect.inflate(blur_offset, blur_offset);
452            // The shader-facing prim rect mirrors the (re-derived) outer for
453            // Outset and the element for Inset — i.e. whichever rect the
454            // scene-build path originally registered as `info.rect`. This is
455            // what the rest of this block, plus `prepare_quad` below, expects
456            // as the prim local-space rect.
457            let prim_rect = match shadow_data.clip_mode {
458                BoxShadowClipMode::Outset => outer_shadow_rect,
459                BoxShadowClipMode::Inset => element_rect,
460            };
461
462            let shadow_rect_size = inner_shadow_rect.size();
463            let mut shadow_radius = shadow_data.shadow_radius;
464            border::ensure_no_corner_overlap(&mut shadow_radius, shadow_rect_size);
465
466            let blur_region = (BLUR_SAMPLE_SCALE * blur_radius).ceil();
467
468            let max_corner_width = shadow_radius.top_left.width
469                .max(shadow_radius.bottom_left.width)
470                .max(shadow_radius.top_right.width)
471                .max(shadow_radius.bottom_right.width);
472            let max_corner_height = shadow_radius.top_left.height
473                .max(shadow_radius.bottom_left.height)
474                .max(shadow_radius.top_right.height)
475                .max(shadow_radius.bottom_right.height);
476
477            let used_corner_width = max_corner_width.max(blur_region);
478            let used_corner_height = max_corner_height.max(blur_region);
479
480            let min_shadow_rect_size = LayoutSize::new(
481                2.0 * used_corner_width + blur_region,
482                2.0 * used_corner_height + blur_region,
483            );
484
485            // Compute the nine-patch source rect size per axis (= min_shadow_rect_size when
486            // the shadow is large enough to stretch, = shadow_rect_size when corners overlap).
487            let src_rect_size = LayoutSize::new(
488                if shadow_rect_size.width >= min_shadow_rect_size.width {
489                    min_shadow_rect_size.width
490                } else {
491                    shadow_rect_size.width
492                },
493                if shadow_rect_size.height >= min_shadow_rect_size.height {
494                    min_shadow_rect_size.height
495                } else {
496                    shadow_rect_size.height
497                },
498            );
499
500            // The full blur alloc size in local pixels. This is the UV denominator passed to
501            // the shader: the nine-patch maps shadow_pos/alloc_size so that shadow_pos=blur_region
502            // maps exactly to the shadow edge in the texture (preserving the blur falloff).
503            let shadow_rect_alloc_size = LayoutSize::new(
504                2.0 * blur_region + src_rect_size.width,
505                2.0 * blur_region + src_rect_size.height,
506            );
507
508            // Scale to device pixels for the render task.
509            let blur_radius_dp = blur_radius * 0.5;
510            let mut content_scale = LayoutToWorldScale::new(1.0) * device_pixel_scale;
511            content_scale.0 = clamp_to_scale_factor(content_scale.0, false);
512
513            // Opt B: pre-reduce content_scale so the blur sigma is already within
514            // MAX_BLUR_STD_DEVIATION, eliminating downscale passes inside new_blur.
515            //
516            // Use the same rounding as the old code (round to nearest integer) to determine
517            // n_downscales, so mask scale exactly matches what old new_blur downscaling would
518            // have produced. Exception: if rounded sigma is 0 (tiny sigma from to_cache_size
519            // downscaling), use the float sigma to avoid a zero-blur regression.
520            let sigma_rounded = (blur_radius_dp * content_scale.0).round();
521            let sigma_for_n = if sigma_rounded == 0.0 { blur_radius_dp * content_scale.0 } else { sigma_rounded };
522            let n_downscales = if sigma_for_n > MAX_BLUR_STD_DEVIATION {
523                (sigma_for_n / MAX_BLUR_STD_DEVIATION).log2().ceil() as u32
524            } else {
525                0
526            };
527            content_scale.0 /= (1u32 << n_downscales) as f32;
528
529            // Safety cap: reduces content_scale further only for pathological
530            // small-blur-huge-element cases where the alloc would exceed the max task size.
531            let cache_size = to_cache_size(shadow_rect_alloc_size, &mut content_scale);
532
533            // Blur sigma to pass to new_blur. Use the same rounded value as the old code
534            // (now divided by 2^n instead of being halved inside new_blur), so the blur
535            // intensity is byte-for-byte identical to the old pipeline.
536            let blur_std_dev = if sigma_rounded == 0.0 {
537                blur_radius_dp * content_scale.0
538            } else {
539                sigma_rounded / (1u32 << n_downscales) as f32
540            };
541            debug_assert!(
542                blur_std_dev <= MAX_BLUR_STD_DEVIATION + 1e-3,
543                "BoxShadow sigma {blur_std_dev} exceeds MAX_BLUR_STD_DEVIATION after Opt B \
544                 (n_downscales={n_downscales}, content_scale={})",
545                content_scale.0,
546            );
547
548            let bs_cache_key = BoxShadowCacheKey {
549                blur_radius_dp: Au::from_f32_px(blur_std_dev),
550                clip_mode: shadow_data.clip_mode,
551                original_alloc_size: (shadow_rect_alloc_size * content_scale).round().to_i32(),
552                br_top_left: (shadow_radius.top_left * content_scale).round().to_i32(),
553                br_top_right: (shadow_radius.top_right * content_scale).round().to_i32(),
554                br_bottom_right: (shadow_radius.bottom_right * content_scale).round().to_i32(),
555                br_bottom_left: (shadow_radius.bottom_left * content_scale).round().to_i32(),
556                device_pixel_scale: Au::from_f32_px(content_scale.0),
557            };
558
559            let clip_data = ClipData::rounded_rect(
560                src_rect_size,
561                &shadow_radius,
562                ClipMode::Clip,
563            );
564
565            // The shadow shape is offset by blur_region within the alloc task (local pixels).
566            // device_pixel_scale_for_task scales it to the mask resolution.
567            let minimal_shadow_rect_origin = LayoutPoint::new(blur_region, blur_region);
568            let device_pixel_scale_for_task = DevicePixelScale::new(content_scale.0);
569
570            let task_id = frame_state.resource_cache.request_render_task(
571                Some(RenderTaskCacheKey {
572                    origin: DeviceIntPoint::zero(),
573                    size: cache_size,
574                    kind: RenderTaskCacheKeyKind::BoxShadow(bs_cache_key),
575                }),
576                false,
577                RenderTaskParent::Surface,
578                &mut frame_state.frame_gpu_data.f32,
579                frame_state.rg_builder,
580                &mut frame_state.surface_builder,
581                &mut |rg_builder, _| {
582                    let mask_task_id = rg_builder.add().init(RenderTask::new_dynamic(
583                        cache_size,
584                        RenderTaskKind::new_rounded_rect_mask(
585                            minimal_shadow_rect_origin,
586                            clip_data.clone(),
587                            device_pixel_scale_for_task,
588                            frame_context.fb_config,
589                        ),
590                    ));
591
592                    RenderTask::new_blur(
593                        DeviceSize::new(blur_std_dev, blur_std_dev),
594                        mask_task_id,
595                        rg_builder,
596                        RenderTargetKind::Alpha,
597                        None,
598                        cache_size,
599                        BlurEdgeMode::Duplicate,
600                    )
601                }
602            );
603
604            // Compensate for the rounding `create_quad_primitive` applies to
605            // prim_rect when `aa_flags` is empty: the shader receives the
606            // rounded p0 as `local_prim_rect.p0` (after the round-trip through
607            // device space and back via `pattern_scale_offset`) and
608            // reconstructs absolute positions via `local_prim_rect.p0 +
609            // offset`. Computing offsets against the un-rounded p0 mismatches
610            // by up to half a device pixel and produces a one-pixel seam on
611            // trailing edges (bug 2035734). The round must be done in device
612            // space to match `create_quad_primitive` for non-identity
613            // transforms (e.g. Gecko at 125% display scaling).
614            let prim_min_rounded = match quad_transform.as_2d_scale_offset() {
615                Some(local_to_device) => {
616                    // Use Point2D::round (euclid's Round trait, defined as
617                    // (n+0.5).floor()) to match what create_quad_primitive
618                    // uses on the rendered quad bounds. f32::round here would
619                    // round half-away-from-zero and disagree at negative
620                    // half-integer device-x values, causing a 1-pixel shift
621                    // when the shader reconstructs dest_rect.min as
622                    // local_prim_rect.p0 + dest_rect_offset.
623                    let dev: DevicePoint = local_to_device.map_point(&prim_rect.min);
624                    local_to_device.unmap_point::<DevicePixel, LayoutPixel>(&dev.round())
625                }
626                None => prim_rect.min,
627            };
628
629            // For outset, prim_rect == dest_rect so offset is zero.
630            // For inset, prim_rect is the element rect; dest_rect (outer_shadow_rect)
631            // may be offset and smaller, so we pass its size and offset separately.
632            let dest_rect = outer_shadow_rect;
633            let dest_rect_offset = LayoutVector2D::new(
634                dest_rect.min.x - prim_min_rounded.x,
635                dest_rect.min.y - prim_min_rounded.y,
636            );
637            let dest_rect_size = dest_rect.size();
638
639            let mut element_radius = shadow_data.element_radius;
640            border::ensure_no_corner_overlap(&mut element_radius, element_rect.size());
641            let element_offset_rel_prim = LayoutVector2D::new(
642                element_rect.min.x - prim_min_rounded.x,
643                element_rect.min.y - prim_min_rounded.y,
644            );
645
646            let pattern = BoxShadowPatternData {
647                color: shadow_data.color,
648                render_task: task_id,
649                shadow_rect_alloc_size,
650                dest_rect_size,
651                dest_rect_offset,
652                clip_mode: shadow_data.clip_mode,
653                element_offset_rel_prim,
654                element_size: element_rect.size(),
655                element_radius,
656            };
657
658            quad::prepare_quad(
659                &pattern,
660                &prim_rect,
661                &prim_info.clip_chain.local_clip_rect,
662                prim_data.common.aligned_aa_edges,
663                prim_data.common.transformed_aa_edges,
664                prim_instance_index,
665                &None,
666                &prim_info.clip_chain,
667                quad_transform,
668                frame_context,
669                pic_context,
670                targets,
671                &data_stores.clip,
672                frame_state,
673                scratch,
674            );
675
676            return;
677        }
678        PrimitiveKind::LineDecoration { data_handle } => {
679            profile_scope!("LineDecoration");
680            let prim_data = &data_stores.line_decoration[*data_handle];
681
682            let (task_id, gpu_address) = prim_data.kind.prepare(
683                prim_info.snapped_local_rect.size(),
684                prim_spatial_node_index,
685                frame_context,
686                frame_state,
687            );
688
689            let line_dec_handle = scratch.frame.line_decoration.push(LineDecorationScratch {
690                task_id,
691                gpu_address,
692            });
693            scratch.frame.draws[prim_instance_index.0 as usize].kind_scratch =
694                KindScratchHandle::LineDecoration(line_dec_handle);
695        }
696        PrimitiveKind::TextRun { data_handle } => {
697            profile_scope!("TextRun");
698
699            let prim_data = &data_stores.text_run[*data_handle];
700
701            // The transform has to match the prim -> raster transform applied
702            // by "ps_text_run" via `transform.m` + `device_pixel_scale`.
703            // `request_resources` uses it to map glyph pen positions into
704            // absolute device space for snapping.
705            let transform = frame_context.spatial_tree
706                .get_relative_transform(
707                    prim_spatial_node_index,
708                    pic_context.raster_spatial_node_index,
709                )
710                .into_fast_transform();
711
712            // The run anchor is the normalized prim rect origin; glyph
713            // positions in the template are stored relative to it. Use the
714            // unsnapped rect so the anchor matches what the shader receives in
715            // `PrimitiveHeader.local_rect`.
716            let local_rect = prim_instance.unsnapped_prim_rect;
717
718            let surface = &frame_state.surfaces[pic_context.surface_index.0];
719
720            // If subpixel AA is disabled due to the backing surface the glyphs
721            // are being drawn onto, disable it (unless we are using the
722            // specifial subpixel mode that estimates background color).
723            let allow_subpixel = match prim_info.state {
724                DrawState::Culled |
725                DrawState::Unset |
726                DrawState::PassThrough => {
727                    panic!("bug: invalid visibility state");
728                }
729                DrawState::Visible { sub_slice_index, .. } => {
730                    // For now, we only allow subpixel AA on primary sub-slices. In future we
731                    // may support other sub-slices if we find content that does this.
732                    if sub_slice_index.is_primary() {
733                        match pic_context.subpixel_mode {
734                            SubpixelMode::Allow => true,
735                            SubpixelMode::Deny => false,
736                            SubpixelMode::Conditional { allowed_rect, prohibited_rect } => {
737                                // Conditional mode allows subpixel AA to be enabled for this
738                                // text run, so long as it's inside the allowed rect.
739                                allowed_rect.contains_box(&prim_info.clip_chain.pic_coverage_rect) &&
740                                !prohibited_rect.intersects(&prim_info.clip_chain.pic_coverage_rect)
741                            }
742                        }
743                    } else {
744                        false
745                    }
746                }
747            };
748
749            let text_run_handle = prim_data.request_resources(
750                local_rect,
751                &transform.to_transform().with_destination::<_>(),
752                surface,
753                prim_spatial_node_index,
754                allow_subpixel,
755                frame_context.fb_config.low_quality_pinch_zoom,
756                frame_state.resource_cache,
757                &mut frame_state.frame_gpu_data.f32,
758                frame_context.spatial_tree,
759                scratch,
760            );
761            scratch.frame.draws[prim_instance_index.0 as usize].kind_scratch =
762                KindScratchHandle::TextRun(text_run_handle);
763        }
764        PrimitiveKind::NormalBorder { data_handle } => {
765            profile_scope!("NormalBorder");
766            let prim_data = &mut data_stores.normal_border[*data_handle];
767            let common_data = &mut prim_data.common;
768            let border_data = &mut prim_data.kind;
769
770            // The per-frame brush + border segments and task-id slot
771            // were allocated in prepare_prim_for_render before
772            // update_clip_task; the kind_scratch handle on this prim's
773            // PrimitiveDrawHeader points to the NormalBorderScratch.
774            let nb_handle = scratch.frame.draws[prim_instance_index.0 as usize]
775                .kind_scratch
776                .unwrap_normal_border();
777            let nb_scratch = scratch.frame.normal_border[nb_handle];
778
779            let brush_segments = &scratch.frame.segments[nb_scratch.brush_segments_range];
780            let gpu_address = border_data.write_brush_gpu_blocks(
781                common_data,
782                prim_info.snapped_local_rect.size(),
783                brush_segments,
784                frame_state,
785            );
786            scratch.frame.normal_border[nb_handle].gpu_address = gpu_address;
787
788            // Hold split borrows on distinct fields of scratch.frame so
789            // we can pass the border_segments slice and the task_ids
790            // mutable slice into update() without copying either out.
791            let PrimitiveFrameScratch {
792                ref border_segments,
793                ref mut border_task_ids,
794                ..
795            } = scratch.frame;
796            border_data.update(
797                &border_segments[nb_scratch.border_segments_range],
798                prim_spatial_node_index,
799                device_pixel_scale,
800                frame_context,
801                frame_state,
802                &mut border_task_ids[nb_scratch.task_ids],
803            );
804        }
805        PrimitiveKind::ImageBorder { data_handle, .. } => {
806            profile_scope!("ImageBorder");
807            let prim_data = &mut data_stores.image_border[*data_handle];
808
809            // The per-frame brush segments were allocated in
810            // prepare_prim_for_render before update_clip_task; the
811            // kind_scratch handle on this prim's PrimitiveDrawHeader
812            // points to the ImageBorderScratch.
813            let ib_handle = scratch.frame.draws[prim_instance_index.0 as usize]
814                .kind_scratch
815                .unwrap_image_border();
816            let brush_segments_range =
817                scratch.frame.image_border[ib_handle].brush_segments_range;
818            let brush_segments = &scratch.frame.segments[brush_segments_range];
819
820            // Update the template this instance references, which may refresh the GPU
821            // cache with any shared template data.
822            let gpu_address = prim_data.kind.update(
823                &mut prim_data.common,
824                prim_info.snapped_local_rect.size(),
825                brush_segments,
826                frame_state,
827            );
828            scratch.frame.image_border[ib_handle].gpu_address = gpu_address;
829        }
830        PrimitiveKind::Rectangle { data_handle, .. } => {
831            profile_scope!("Rectangle");
832
833            if use_legacy_path {
834                let prim_data = &mut data_stores.prim[*data_handle];
835
836                // Update the template this instane references, which may refresh the GPU
837                // cache with any shared template data.
838                prim_data.update(
839                    frame_state,
840                    frame_context.scene_properties,
841                );
842
843                write_segment(
844                    prim_info.segment_instance_index,
845                    frame_state,
846                    &mut scratch.frame.segments,
847                    &mut scratch.frame.segment_instances,
848                    |request| {
849                        request.push_one(frame_context.scene_properties.resolve_color(&prim_data.kind.color).premultiplied());
850                    }
851                );
852            } else {
853                let prim_data = &data_stores.prim[*data_handle];
854                let prim_rect = prim_info.snapped_local_rect;
855                let color = prim_data.resolve(frame_context.scene_properties);
856
857                quad::prepare_quad(
858                    &color,
859                    &prim_rect,
860                    &prim_info.clip_chain.local_clip_rect,
861                    prim_data.common.aligned_aa_edges,
862                    prim_data.common.transformed_aa_edges,
863                    prim_instance_index,
864                    &None,
865                    &prim_info.clip_chain,
866                    quad_transform,
867                    frame_context,
868                    pic_context,
869                    targets,
870                    &data_stores.clip,
871                    frame_state,
872                    scratch,
873                );
874
875                return;
876            }
877        }
878        PrimitiveKind::YuvImage { data_handle, .. } => {
879            profile_scope!("YuvImage");
880            let prim_data = &mut data_stores.yuv_image[*data_handle];
881            let common_data = &mut prim_data.common;
882            let yuv_image_data = &mut prim_data.kind;
883
884            if !use_legacy_path {
885                if prim_info.compositor_surface_kind == CompositorSurfaceKind::Underlay {
886                    quad::prepare_quad(
887                        &Cutout,
888                        &prim_info.snapped_local_rect,
889                        &prim_info.clip_chain.local_clip_rect,
890                        common_data.aligned_aa_edges,
891                        common_data.transformed_aa_edges,
892                        prim_instance_index,
893                        &None,
894                        &prim_info.clip_chain,
895                        quad_transform,
896                        frame_context,
897                        pic_context,
898                        targets,
899                        &data_stores.clip,
900                        frame_state,
901                        scratch,
902                    );
903
904                    return;
905                }
906            }
907
908            // Update the template this instane references, which may refresh the GPU
909            // cache with any shared template data.
910            yuv_image_data.update(
911                common_data,
912                prim_info.compositor_surface_kind.is_composited(),
913                frame_state,
914            );
915
916            write_segment(
917                prim_info.segment_instance_index,
918                frame_state,
919                &mut scratch.frame.segments,
920                &mut scratch.frame.segment_instances,
921                |writer| {
922                    yuv_image_data.write_prim_gpu_blocks(writer);
923                }
924            );
925        }
926        PrimitiveKind::Image { data_handle, .. } => {
927            profile_scope!("Image");
928
929            let prim_data = &mut data_stores.image[*data_handle];
930            let common_data = &mut prim_data.common;
931            let image_data = &mut prim_data.kind;
932
933            if !use_legacy_path {
934                let prim_rect = prim_info.snapped_local_rect;
935
936                if prim_info.compositor_surface_kind == CompositorSurfaceKind::Underlay {
937                    quad::prepare_quad(
938                        &Cutout,
939                        &prim_rect,
940                        &prim_info.clip_chain.local_clip_rect,
941                        common_data.aligned_aa_edges,
942                        common_data.transformed_aa_edges,
943                        prim_instance_index,
944                        &None,
945                        &prim_info.clip_chain,
946                        quad_transform,
947                        frame_context,
948                        pic_context,
949                        targets,
950                        &data_stores.clip,
951                        frame_state,
952                        scratch,
953                    );
954
955                    return;
956                }
957
958                crate::prim_store::image::prepare_image_quads(
959                    &prim_rect,
960                    common_data,
961                    image_data,
962                    &prim_info.clip_chain,
963                    prim_instance_index,
964                    quad_transform,
965                    frame_context,
966                    pic_context,
967                    targets,
968                    &data_stores.clip,
969                    frame_state,
970                    scratch,
971                );
972
973                return;
974            }
975
976            // Update the template this instance references, which may refresh the GPU
977            // cache with any shared template data.
978            let img_scratch_handle = image_data.update(
979                common_data,
980                prim_instance_index,
981                prim_spatial_node_index,
982                frame_state,
983                frame_context,
984                prim_info.snapped_local_rect,
985                scratch,
986            );
987            scratch.frame.draws[prim_instance_index.0 as usize].kind_scratch =
988                KindScratchHandle::Image(img_scratch_handle);
989            let image_adjustment = scratch.frame.images[img_scratch_handle].adjustment;
990            let effective_stretch_size =
991                image_data.stretch_size.resolve(&prim_info.snapped_local_rect);
992
993            write_segment(
994                prim_info.segment_instance_index,
995                frame_state,
996                &mut scratch.frame.segments,
997                &mut scratch.frame.segment_instances,
998                |request| {
999                    image_data.write_prim_gpu_blocks(&image_adjustment, effective_stretch_size, request);
1000                },
1001            );
1002        }
1003        PrimitiveKind::LinearGradient { data_handle, .. } => {
1004            profile_scope!("LinearGradient");
1005            let prim_data = &data_stores.linear_grad[*data_handle];
1006            let prim_rect = prim_info.snapped_local_rect;
1007            let stretch_size = LayoutSize::new(
1008                prim_data.stretch_ratio.width * prim_rect.size().width,
1009                prim_data.stretch_ratio.height * prim_rect.size().height,
1010            );
1011
1012            if let Some(nine_patch) = &prim_data.border_nine_patch {
1013                quad::prepare_border_image_nine_patch(
1014                    &*nine_patch,
1015                    prim_data,
1016                    &prim_rect,
1017                    stretch_size,
1018                    prim_data.common.aligned_aa_edges,
1019                    prim_data.common.transformed_aa_edges,
1020                    prim_instance_index,
1021                    &prim_info.clip_chain,
1022                    quad_transform,
1023                    frame_context,
1024                    pic_context,
1025                    targets,
1026                    &data_stores.clip,
1027                    frame_state,
1028                    scratch,
1029                );
1030                return;
1031            }
1032
1033            // Fast-path: axis-aligned non-repeating gradients with multiple
1034            // stops decompose into per-segment two-stop quads so the GPU can
1035            // take the `sample_gradient_stops_fast` shader path. The
1036            // decomposition runs at frame-build (against the snapped prim
1037            // rect) so adjacent segments tile end-to-end at the snapped
1038            // outer-prim grid, even when the frame-time snap pass nudges
1039            // the outer rect at fractional DPR.
1040            //
1041            // `create_linear_gradient_prim` canonicalises the stored
1042            // start/end by swapping them when the original gradient line
1043            // ran "backwards" (and recording that in `reverse_stops`).
1044            // `LinearGradientTemplate::build` swaps them back at render
1045            // time; we have to do the same here so the decomposition sees
1046            // the gecko-original gradient orientation -- otherwise the
1047            // segment loop produces a gradient with stops in reverse
1048            // order (e.g. `linear-gradient(to top, red, blue)` rendering
1049            // as red-on-top instead of red-on-bottom).
1050            let (effective_start, effective_end) = if prim_data.reverse_stops {
1051                (prim_data.end_point, prim_data.start_point)
1052            } else {
1053                (prim_data.start_point, prim_data.end_point)
1054            };
1055            if linear_gradient_decomposes(
1056                &prim_rect,
1057                stretch_size,
1058                prim_data.tile_spacing,
1059                effective_start,
1060                effective_end,
1061                prim_data.extend_mode,
1062                &prim_data.stops,
1063                frame_context.fb_config.enable_dithering,
1064            ) {
1065                decompose_axis_aligned_gradient(
1066                    &prim_rect,
1067                    stretch_size,
1068                    effective_start,
1069                    effective_end,
1070                    &prim_data.stops,
1071                    &prim_info.clip_chain.local_clip_rect,
1072                    |seg_rect, seg_start, seg_end, seg_stops, edge_aa_mask| {
1073                        let pattern = LinearGradientSegmentPattern {
1074                            start: seg_start,
1075                            end: seg_end,
1076                            stops: seg_stops,
1077                        };
1078                        quad::prepare_quad(
1079                            &pattern,
1080                            seg_rect,
1081                            &prim_info.clip_chain.local_clip_rect,
1082                            EdgeMask::empty(),
1083                            edge_aa_mask,
1084                            prim_instance_index,
1085                            &None,
1086                            &prim_info.clip_chain,
1087                            quad_transform,
1088                            frame_context,
1089                            pic_context,
1090                            targets,
1091                            &data_stores.clip,
1092                            frame_state,
1093                            scratch,
1094                        );
1095                    },
1096                );
1097                return;
1098            }
1099
1100            // For SWGL, evaluating the gradient is faster than reading from the texture cache.
1101            let mut should_cache = !frame_context.fb_config.is_software
1102                && frame_state.resource_cache.texture_cache.allocated_color_bytes() < 10_000_000;
1103            if should_cache {
1104                let surface = &frame_state.surfaces[pic_context.surface_index.0];
1105                let clipped_surface_rect = surface.get_surface_rect(
1106                    &prim_info.clip_chain.pic_coverage_rect,
1107                    frame_context.spatial_tree,
1108                );
1109
1110                should_cache = if let Some(rect) = clipped_surface_rect {
1111                    rect.width() < 512 && rect.height() < 512
1112                } else {
1113                    false
1114                };
1115            }
1116
1117            let cache_key = if should_cache {
1118                quad::cache_key(
1119                    data_handle.uid(),
1120                    quad_transform,
1121                    &prim_info.clip_chain,
1122                    frame_state.clip_store,
1123                )
1124            } else {
1125                None
1126            };
1127
1128            let local_rect = prim_info.snapped_local_rect;
1129            quad::prepare_repeatable_quad(
1130                prim_data,
1131                &local_rect,
1132                &prim_info.clip_chain.local_clip_rect,
1133                stretch_size,
1134                prim_data.tile_spacing,
1135                prim_data.common.aligned_aa_edges,
1136                prim_data.common.transformed_aa_edges,
1137                prim_instance_index,
1138                &cache_key,
1139                &prim_info.clip_chain,
1140                quad_transform,
1141                frame_context,
1142                pic_context,
1143                targets,
1144                &data_stores.clip,
1145                frame_state,
1146                scratch,
1147            );
1148
1149            return;
1150        }
1151        PrimitiveKind::RadialGradient { data_handle, .. } => {
1152            profile_scope!("RadialGradient");
1153            let prim_data = &mut data_stores.radial_grad[*data_handle];
1154            let local_rect = prim_info.snapped_local_rect;
1155            let stretch_size = LayoutSize::new(
1156                prim_data.stretch_ratio.width * local_rect.size().width,
1157                prim_data.stretch_ratio.height * local_rect.size().height,
1158            );
1159
1160            if let Some(nine_patch) = &prim_data.border_nine_patch {
1161                quad::prepare_border_image_nine_patch(
1162                    &*nine_patch,
1163                    prim_data,
1164                    &local_rect,
1165                    stretch_size,
1166                    prim_data.common.aligned_aa_edges,
1167                    prim_data.common.transformed_aa_edges,
1168                    prim_instance_index,
1169                    &prim_info.clip_chain,
1170                    quad_transform,
1171                    frame_context,
1172                    pic_context,
1173                    targets,
1174                    &data_stores.clip,
1175                    frame_state,
1176                    scratch,
1177                );
1178                return;
1179            }
1180
1181            quad::prepare_repeatable_quad(
1182                prim_data,
1183                &local_rect,
1184                &prim_info.clip_chain.local_clip_rect,
1185                stretch_size,
1186                prim_data.tile_spacing,
1187                prim_data.common.aligned_aa_edges,
1188                prim_data.common.transformed_aa_edges,
1189                prim_instance_index,
1190                &None,
1191                &prim_info.clip_chain,
1192                quad_transform,
1193                frame_context,
1194                pic_context,
1195                targets,
1196                &data_stores.clip,
1197                frame_state,
1198                scratch,
1199            );
1200            return;
1201        }
1202        PrimitiveKind::ConicGradient { data_handle, .. } => {
1203            profile_scope!("ConicGradient");
1204            let prim_data = &mut data_stores.conic_grad[*data_handle];
1205            let prim_rect = prim_info.snapped_local_rect;
1206            let stretch_size = LayoutSize::new(
1207                prim_data.stretch_ratio.width * prim_rect.size().width,
1208                prim_data.stretch_ratio.height * prim_rect.size().height,
1209            );
1210
1211            if let Some(nine_patch) = &prim_data.border_nine_patch {
1212                quad::prepare_border_image_nine_patch(
1213                    &*nine_patch,
1214                    prim_data,
1215                    &prim_rect,
1216                    stretch_size,
1217                    prim_data.common.aligned_aa_edges,
1218                    prim_data.common.transformed_aa_edges,
1219                    prim_instance_index,
1220                    &prim_info.clip_chain,
1221                    quad_transform,
1222                    frame_context,
1223                    pic_context,
1224                    targets,
1225                    &data_stores.clip,
1226                    frame_state,
1227                    scratch,
1228                );
1229                return;
1230            }
1231
1232            // Conic gradients are quite slow with SWGL, so we want to cache
1233            // them as much as we can, even large ones.
1234            // TODO: get_surface_rect is not always cheap. We should reorganize
1235            // the code so that we only call it as much as we really need it,
1236            // while avoiding this much boilerplate for each primitive that uses
1237            // caching.
1238            let mut should_cache = frame_context.fb_config.is_software
1239                && frame_state.resource_cache.texture_cache.allocated_color_bytes() < 30_000_000;
1240            if should_cache {
1241                let surface = &frame_state.surfaces[pic_context.surface_index.0];
1242                let clipped_surface_rect = surface.get_surface_rect(
1243                    &prim_info.clip_chain.pic_coverage_rect,
1244                    frame_context.spatial_tree,
1245                );
1246
1247                should_cache = if let Some(rect) = clipped_surface_rect {
1248                    rect.width() < 4096 && rect.height() < 4096
1249                } else {
1250                    false
1251                };
1252            }
1253
1254            let cache_key = if should_cache {
1255                quad::cache_key(
1256                    data_handle.uid(),
1257                    quad_transform,
1258                    &prim_info.clip_chain,
1259                    frame_state.clip_store,
1260                )
1261            } else {
1262                None
1263            };
1264
1265            let local_rect = prim_info.snapped_local_rect;
1266            quad::prepare_repeatable_quad(
1267                prim_data,
1268                &local_rect,
1269                &prim_info.clip_chain.local_clip_rect,
1270                stretch_size,
1271                prim_data.tile_spacing,
1272                prim_data.common.aligned_aa_edges,
1273                prim_data.common.transformed_aa_edges,
1274                prim_instance_index,
1275                &cache_key,
1276                &prim_info.clip_chain,
1277                quad_transform,
1278                frame_context,
1279                pic_context,
1280                targets,
1281                &data_stores.clip,
1282                frame_state,
1283                scratch,
1284            );
1285            return;
1286        }
1287        PrimitiveKind::Picture { pic_index, .. } => {
1288            profile_scope!("Picture");
1289            let pic_scratch_handle = prim_info.kind_scratch.unwrap_picture();
1290            let pic = &mut store.pictures[pic_index.0];
1291
1292            if prim_info.clip_chain.needs_mask {
1293                // TODO(gw): Much of the code in this branch could be moved in to a common
1294                //           function as we move more primitives to the new clip-mask paths.
1295
1296                // We are going to split the clip mask tasks in to a list to be rendered
1297                // on the source picture, and those to be rendered in to a mask for
1298                // compositing the picture in to the target.
1299                let mut source_masks = Vec::new();
1300                let mut target_masks = Vec::new();
1301
1302                // For some composite modes, we force target mask due to limitations. That
1303                // might results in artifacts for these modes (which are already an existing
1304                // problem) but we can handle these cases as follow ups.
1305                let force_target_mask = match pic.composite_mode {
1306                    // We can't currently render over top of these filters as their size
1307                    // may have changed due to downscaling. We could handle this separate
1308                    // case as a follow up.
1309                    Some(PictureCompositeMode::Filter(Filter::Blur { .. })) |
1310                    Some(PictureCompositeMode::Filter(Filter::DropShadows { .. })) |
1311                    Some(PictureCompositeMode::SVGFEGraph( .. )) => {
1312                        true
1313                    }
1314                    _ => {
1315                        false
1316                    }
1317                };
1318
1319                // Work out which clips get drawn in to the source / target mask
1320                for i in 0 .. prim_info.clip_chain.clips_range.count {
1321                    let clip_instance = frame_state.clip_store.get_instance_from_range(&prim_info.clip_chain.clips_range, i);
1322
1323                    if !force_target_mask && clip_instance.flags.contains(ClipNodeFlags::SAME_COORD_SYSTEM) {
1324                        source_masks.push(i);
1325                    } else {
1326                        target_masks.push(i);
1327                    }
1328                }
1329
1330                let pic_surface_index = pic.raster_config.as_ref().unwrap().surface_index;
1331                let prim_local_rect: LayoutRect = frame_state
1332                    .surfaces[pic_surface_index.0]
1333                    .clipped_local_rect
1334                    .cast_unit();
1335
1336                // Handle masks on the source. This is the common case, and occurs for:
1337                // (a) Any masks in the same coord space as the surface
1338                // (b) All masks if the surface and parent are axis-aligned
1339                if !source_masks.is_empty() {
1340                    let first_clip_node_index = frame_state.clip_store.clip_node_instances.len() as u32;
1341                    let parent_task_id = scratch.frame.pictures[pic_scratch_handle].primary_render_task_id.expect("bug: no composite mode");
1342
1343                    // Construct a new clip node range, also add image-mask dependencies as needed
1344                    for instance in source_masks {
1345                        let clip_instance = frame_state.clip_store.get_instance_from_range(&prim_info.clip_chain.clips_range, instance);
1346
1347                        for tile in frame_state.clip_store.visible_mask_tiles(clip_instance) {
1348                            frame_state.rg_builder.add_dependency(
1349                                parent_task_id,
1350                                tile.task_id,
1351                            );
1352                        }
1353
1354                        frame_state.clip_store.clip_node_instances.push(clip_instance.clone());
1355                    }
1356
1357                    let clip_node_range = ClipNodeRange {
1358                        first: first_clip_node_index,
1359                        count: frame_state.clip_store.clip_node_instances.len() as u32 - first_clip_node_index,
1360                    };
1361
1362                    // Add the mask as a sub-pass of the picture
1363                    let pic_task_id = scratch.frame.pictures[pic_scratch_handle].primary_render_task_id.expect("uh oh");
1364                    let pic_task = frame_state.rg_builder.get_task_mut(pic_task_id);
1365
1366                    let RenderTaskKind::Picture(info) = &pic_task.kind else { unreachable!() };
1367
1368                    let task_rect = DeviceRect::from_origin_and_size(
1369                        info.content_origin,
1370                        pic_task.get_target_size().to_f32(),
1371                    );
1372
1373                    quad::prepare_clip_range(
1374                        clip_node_range,
1375                        pic_task_id,
1376                        &task_rect,
1377                        &prim_local_rect,
1378                        prim_spatial_node_index,
1379                        info.raster_spatial_node_index,
1380                        info.device_pixel_scale,
1381                        &data_stores.clip,
1382                        frame_state.clip_store,
1383                        frame_context.spatial_tree,
1384                        frame_state.rg_builder,
1385                        &mut frame_state.frame_gpu_data.f32,
1386                        frame_state.transforms,
1387                    );
1388                }
1389
1390                // Handle masks on the target. This is the rare case, and occurs for:
1391                // Masks in parent space when non-axis-aligned to source space
1392                if !target_masks.is_empty() {
1393                    let surface = &frame_state.surfaces[pic_context.surface_index.0];
1394                    let coverage_rect = prim_info.clip_chain.pic_coverage_rect;
1395
1396                    let device_pixel_scale = surface.device_pixel_scale;
1397                    let raster_spatial_node_index = surface.raster_spatial_node_index;
1398
1399                    let Some(clipped_surface_rect) = surface.get_surface_rect(
1400                        &coverage_rect,
1401                        frame_context.spatial_tree,
1402                    ) else {
1403                        return;
1404                    };
1405
1406                    // Draw a normal screens-space mask to an alpha target that
1407                    // can be sampled when compositing this picture.
1408                    let empty_task = EmptyTask {
1409                        content_origin: clipped_surface_rect.min.to_f32(),
1410                        device_pixel_scale,
1411                        raster_spatial_node_index,
1412                    };
1413
1414                    let task_size = clipped_surface_rect.size();
1415
1416                    let clip_task_id = frame_state.rg_builder.add().init(RenderTask::new_dynamic(
1417                        task_size,
1418                        RenderTaskKind::Empty(empty_task),
1419                    ));
1420
1421                    // Construct a new clip node range, also add image-mask dependencies as needed
1422                    let first_clip_node_index = frame_state.clip_store.clip_node_instances.len() as u32;
1423                    for instance in target_masks {
1424                        let clip_instance = frame_state.clip_store.get_instance_from_range(&prim_info.clip_chain.clips_range, instance);
1425
1426                        for tile in frame_state.clip_store.visible_mask_tiles(clip_instance) {
1427                            frame_state.rg_builder.add_dependency(
1428                                clip_task_id,
1429                                tile.task_id,
1430                            );
1431                        }
1432
1433                        frame_state.clip_store.clip_node_instances.push(clip_instance.clone());
1434                    }
1435
1436                    let clip_node_range = ClipNodeRange {
1437                        first: first_clip_node_index,
1438                        count: frame_state.clip_store.clip_node_instances.len() as u32 - first_clip_node_index,
1439                    };
1440
1441                    let task_rect = clipped_surface_rect.to_f32();
1442
1443                    quad::prepare_clip_range(
1444                        clip_node_range,
1445                        clip_task_id,
1446                        &task_rect,
1447                        &prim_local_rect,
1448                        prim_spatial_node_index,
1449                        raster_spatial_node_index,
1450                        device_pixel_scale,
1451                        &data_stores.clip,
1452                        frame_state.clip_store,
1453                        frame_context.spatial_tree,
1454                        frame_state.rg_builder,
1455                        &mut frame_state.frame_gpu_data.f32,
1456                        frame_state.transforms,
1457                    );
1458
1459                    let clip_task_index = ClipTaskIndex(scratch.frame.clip_mask_instances.len() as _);
1460                    scratch.frame.clip_mask_instances.push(ClipMaskKind::Mask(clip_task_id));
1461                    scratch.frame.draws[prim_instance_index.0 as usize].clip_task_index = clip_task_index;
1462                    frame_state.surface_builder.add_child_render_task(
1463                        clip_task_id,
1464                        frame_state.rg_builder,
1465                    );
1466                }
1467            }
1468
1469            pic.write_gpu_blocks(
1470                frame_state,
1471                data_stores,
1472                &mut scratch.frame.pictures[pic_scratch_handle],
1473            );
1474
1475            if let Picture3DContext::In { root_data: None, plane_splitter_index, ancestor_index, .. } = pic.context_3d {
1476                let dirty_rect = frame_state.current_dirty_region().combined;
1477                let visibility_spatial_node = frame_state.current_dirty_region().visibility_spatial_node;
1478
1479                let splitter = &mut frame_state.plane_splitters[plane_splitter_index.0];
1480                let surface_index = pic.raster_config.as_ref().unwrap().surface_index;
1481                let surface = &frame_state.surfaces[surface_index.0];
1482                let local_prim_rect = surface.clipped_local_rect.cast_unit();
1483
1484                PictureInstance::add_split_plane(
1485                    splitter,
1486                    frame_context.spatial_tree,
1487                    prim_spatial_node_index,
1488                    ancestor_index,
1489                    visibility_spatial_node,
1490                    local_prim_rect,
1491                    &prim_info.clip_chain.local_clip_rect,
1492                    dirty_rect,
1493                    plane_split_anchor,
1494                );
1495            }
1496        }
1497        PrimitiveKind::BackdropCapture { .. } => {
1498            // Register the owner picture of this backdrop primitive as the
1499            // target for resolve of the sub-graph
1500            frame_state.surface_builder.register_resolve_source();
1501
1502            if frame_context.debug_flags.contains(DebugFlags::HIGHLIGHT_BACKDROP_FILTERS) {
1503                if let Some(world_rect) = pic_state.map_pic_to_vis.map(&prim_info.clip_chain.pic_coverage_rect) {
1504                    scratch.push_debug_rect(
1505                        world_rect.cast_unit(),
1506                        2,
1507                        crate::debug_colors::MAGENTA,
1508                        ColorF::TRANSPARENT,
1509                    );
1510                }
1511            }
1512        }
1513        PrimitiveKind::BackdropRender { pic_index, .. } => {
1514            match frame_state.surface_builder.sub_graph_output_map.get(pic_index).cloned() {
1515                Some(sub_graph_output_id) => {
1516                    frame_state.surface_builder.add_child_render_task(
1517                        sub_graph_output_id,
1518                        frame_state.rg_builder,
1519                    );
1520                    let backdrop_handle = scratch.frame.backdrop_render.push(BackdropRenderScratch {
1521                        src_task_id: sub_graph_output_id,
1522                    });
1523                    scratch.frame.draws[prim_instance_index.0 as usize].kind_scratch =
1524                        KindScratchHandle::BackdropRender(backdrop_handle);
1525                }
1526                None => {
1527                    // Backdrop capture was found not visible, didn't produce a sub-graph
1528                    // so we can just skip drawing
1529                    scratch.frame.draws[prim_instance_index.0 as usize].reset();
1530                }
1531            }
1532        }
1533    }
1534
1535    match prim_info.state {
1536        DrawState::Unset => {
1537            panic!("bug: invalid vis state");
1538        }
1539        DrawState::Visible { .. } => {
1540            frame_state.push_prim(
1541                &PrimitiveCommand::simple(storage::Index::from_u32(prim_instance_index.0)),
1542                prim_spatial_node_index,
1543                targets,
1544            );
1545        }
1546        DrawState::PassThrough | DrawState::Culled => {}
1547    }
1548}
1549
1550
1551fn write_segment<F>(
1552    segment_instance_index: SegmentInstanceIndex,
1553    frame_state: &mut FrameBuildingState,
1554    segments: &mut SegmentStorage,
1555    segment_instances: &mut SegmentInstanceStorage,
1556    f: F,
1557) where F: Fn(&mut GpuBufferWriterF) {
1558    debug_assert_ne!(segment_instance_index, SegmentInstanceIndex::INVALID);
1559    if segment_instance_index != SegmentInstanceIndex::UNUSED {
1560        let segment_instance = &mut segment_instances[segment_instance_index];
1561
1562        let segments = &segments[segment_instance.segments_range];
1563        let mut writer = frame_state.frame_gpu_data.f32.write_blocks(3 + segments.len() * VECS_PER_SEGMENT);
1564
1565        f(&mut writer);
1566
1567        for segment in segments {
1568            segment.write_gpu_blocks(&mut writer);
1569        }
1570
1571        segment_instance.gpu_data = writer.finish();
1572    }
1573}
1574
1575fn update_clip_task_for_brush(
1576    instance: &PrimitiveInstance,
1577    prim_segment_instance_index: SegmentInstanceIndex,
1578    prim_brush_segments_range: storage::Range<BrushSegment>,
1579    prim_clip_chain: &ClipChainInstance,
1580    prim_origin: &LayoutPoint,
1581    prim_spatial_node_index: SpatialNodeIndex,
1582    root_spatial_node_index: SpatialNodeIndex,
1583    visibility_spatial_node_index: SpatialNodeIndex,
1584    pic_context: &PictureContext,
1585    pic_state: &mut PictureState,
1586    frame_context: &FrameBuildingContext,
1587    frame_state: &mut FrameBuildingState,
1588    data_stores: &mut DataStores,
1589    segments_store: &mut SegmentStorage,
1590    segment_instances_store: &mut SegmentInstanceStorage,
1591    clip_mask_instances: &mut Vec<ClipMaskKind>,
1592    device_pixel_scale: DevicePixelScale,
1593) -> Option<ClipTaskIndex> {
1594    let segments = match instance.kind {
1595        PrimitiveKind::BoxShadow { .. } => {
1596            unreachable!("BUG: box-shadows should not hit legacy brush clip path");
1597        }
1598        PrimitiveKind::Picture { .. } |
1599        PrimitiveKind::TextRun { .. } |
1600        PrimitiveKind::LineDecoration { .. } |
1601        PrimitiveKind::BackdropCapture { .. } |
1602        PrimitiveKind::BackdropRender { .. } => {
1603            return None;
1604        }
1605        PrimitiveKind::Image { .. } |
1606        PrimitiveKind::YuvImage { .. } |
1607        PrimitiveKind::Rectangle { .. } => {
1608            if prim_segment_instance_index == SegmentInstanceIndex::UNUSED {
1609                return None;
1610            }
1611
1612            let segment_instance = &segment_instances_store[prim_segment_instance_index];
1613
1614            &segments_store[segment_instance.segments_range]
1615        }
1616        PrimitiveKind::NormalBorder { .. } |
1617        PrimitiveKind::ImageBorder { .. } => {
1618            // Per-frame brush segments live in scratch.frame.segments;
1619            // the range was captured in prepare_prim_for_render and is
1620            // stored on the prim's per-kind scratch. The caller
1621            // resolves the range from there and passes it through.
1622            if prim_brush_segments_range.is_empty() {
1623                return None;
1624            }
1625            &segments_store[prim_brush_segments_range]
1626        }
1627        PrimitiveKind::LinearGradient { .. } => {
1628            unreachable!("BUG: linear gradients should always use quad path");
1629        }
1630        PrimitiveKind::RadialGradient { .. } => {
1631            unreachable!("BUG: radial gradients should always use quad path");
1632        }
1633        PrimitiveKind::ConicGradient { .. } => {
1634            unreachable!("BUG: conic gradients should always use quad path");
1635        }
1636    };
1637
1638    // If there are no segments, early out to avoid setting a valid
1639    // clip task instance location below.
1640    if segments.is_empty() {
1641        return None;
1642    }
1643
1644    // Set where in the clip mask instances array the clip mask info
1645    // can be found for this primitive. Each segment will push the
1646    // clip mask information for itself in update_clip_task below.
1647    let clip_task_index = ClipTaskIndex(clip_mask_instances.len() as _);
1648
1649    // If we only built 1 segment, there is no point in re-running
1650    // the clip chain builder. Instead, just use the clip chain
1651    // instance that was built for the main primitive. This is a
1652    // significant optimization for the common case.
1653    if segments.len() == 1 {
1654        let clip_mask_kind = update_brush_segment_clip_task(
1655            &segments[0],
1656            Some(prim_clip_chain),
1657            root_spatial_node_index,
1658            pic_context.surface_index,
1659            frame_context,
1660            frame_state,
1661            device_pixel_scale,
1662        );
1663        clip_mask_instances.push(clip_mask_kind);
1664    } else {
1665        let dirty_rect = frame_state.current_dirty_region().combined;
1666
1667        for segment in segments {
1668            // Build a clip chain for the smaller segment rect. This will
1669            // often manage to eliminate most/all clips, and sometimes
1670            // clip the segment completely.
1671            frame_state.clip_store.set_active_clips_from_clip_chain(
1672                prim_clip_chain,
1673                prim_spatial_node_index,
1674                visibility_spatial_node_index,
1675                &frame_context.spatial_tree,
1676            );
1677
1678            let segment_clip_chain = frame_state
1679                .clip_store
1680                .build_clip_chain_instance(
1681                    segment.local_rect.translate(prim_origin.to_vector()),
1682                    &pic_state.map_local_to_pic,
1683                    &pic_state.map_pic_to_vis,
1684                    &frame_context.spatial_tree,
1685                    &mut frame_state.frame_gpu_data.f32,
1686                    frame_state.resource_cache,
1687                    &dirty_rect,
1688                    &mut data_stores.clip,
1689                    frame_state.rg_builder,
1690                    false,
1691                );
1692
1693            let clip_mask_kind = update_brush_segment_clip_task(
1694                &segment,
1695                segment_clip_chain.as_ref(),
1696                root_spatial_node_index,
1697                pic_context.surface_index,
1698                frame_context,
1699                frame_state,
1700                device_pixel_scale,
1701            );
1702            clip_mask_instances.push(clip_mask_kind);
1703        }
1704    }
1705
1706    Some(clip_task_index)
1707}
1708
1709pub fn update_clip_task(
1710    instance: &mut PrimitiveInstance,
1711    prim_instance_index: PrimitiveInstanceIndex,
1712    prim_origin: &LayoutPoint,
1713    prim_spatial_node_index: SpatialNodeIndex,
1714    root_spatial_node_index: SpatialNodeIndex,
1715    visibility_spatial_node_index: SpatialNodeIndex,
1716    pic_context: &PictureContext,
1717    pic_state: &mut PictureState,
1718    frame_context: &FrameBuildingContext,
1719    frame_state: &mut FrameBuildingState,
1720    prim_store: &mut PrimitiveStore,
1721    data_stores: &mut DataStores,
1722    scratch: &mut PrimitiveScratchBuffer,
1723) -> bool {
1724    let device_pixel_scale = frame_state.surfaces[pic_context.surface_index.0].device_pixel_scale;
1725
1726    let clip_chain_snapshot = scratch.frame.draws[prim_instance_index.0 as usize].clip_chain;
1727    build_segments_if_needed(
1728        instance,
1729        prim_instance_index,
1730        &clip_chain_snapshot,
1731        frame_state,
1732        prim_store,
1733        data_stores,
1734        scratch,
1735    );
1736
1737    // First try to  render this primitive's mask using optimized brush rendering.
1738    let prim_segment_instance_index = scratch.frame.draws[prim_instance_index.0 as usize].segment_instance_index;
1739    // For prim kinds with per-frame brush segments, resolve the range
1740    // from the prim's per-kind scratch (allocated in
1741    // prepare_prim_for_render before this point). Empty range for any
1742    // other kind.
1743    let prim_brush_segments_range = match instance.kind {
1744        PrimitiveKind::NormalBorder { .. } => {
1745            let nb_handle = scratch.frame.draws[prim_instance_index.0 as usize]
1746                .kind_scratch
1747                .unwrap_normal_border();
1748            scratch.frame.normal_border[nb_handle].brush_segments_range
1749        }
1750        PrimitiveKind::ImageBorder { .. } => {
1751            let ib_handle = scratch.frame.draws[prim_instance_index.0 as usize]
1752                .kind_scratch
1753                .unwrap_image_border();
1754            scratch.frame.image_border[ib_handle].brush_segments_range
1755        }
1756        _ => storage::Range::empty(),
1757    };
1758    let new_clip_task_index = if let Some(clip_task_index) = update_clip_task_for_brush(
1759        instance,
1760        prim_segment_instance_index,
1761        prim_brush_segments_range,
1762        &clip_chain_snapshot,
1763        prim_origin,
1764        prim_spatial_node_index,
1765        root_spatial_node_index,
1766        visibility_spatial_node_index,
1767        pic_context,
1768        pic_state,
1769        frame_context,
1770        frame_state,
1771        data_stores,
1772        &mut scratch.frame.segments,
1773        &mut scratch.frame.segment_instances,
1774        &mut scratch.frame.clip_mask_instances,
1775        device_pixel_scale,
1776    ) {
1777        clip_task_index
1778    } else if scratch.frame.draws[prim_instance_index.0 as usize].clip_chain.needs_mask {
1779        // Get a minimal device space rect, clipped to the screen that we
1780        // need to allocate for the clip mask, as well as interpolated
1781        // snap offsets.
1782        let unadjusted_device_rect = match frame_state.surfaces[pic_context.surface_index.0].get_surface_rect(
1783            &scratch.frame.draws[prim_instance_index.0 as usize].clip_chain.pic_coverage_rect,
1784            frame_context.spatial_tree,
1785        ) {
1786            Some(rect) => rect,
1787            None => return false,
1788        };
1789
1790        let (device_rect, device_pixel_scale) = adjust_mask_scale_for_max_size(
1791            unadjusted_device_rect,
1792            device_pixel_scale,
1793        );
1794
1795        if device_rect.size().to_i32().is_empty() {
1796            log::warn!("Bad adjusted clip task size {:?} (was {:?})", device_rect.size(), unadjusted_device_rect.size());
1797            return false;
1798        }
1799
1800        let clip_task_id = RenderTaskKind::new_mask(
1801            device_rect,
1802            scratch.frame.draws[prim_instance_index.0 as usize].clip_chain.clips_range,
1803            root_spatial_node_index,
1804            frame_state.rg_builder,
1805            device_pixel_scale,
1806            frame_context.fb_config,
1807        );
1808        // Set the global clip mask instance for this primitive.
1809        let clip_task_index = ClipTaskIndex(scratch.frame.clip_mask_instances.len() as _);
1810        scratch.frame.clip_mask_instances.push(ClipMaskKind::Mask(clip_task_id));
1811        frame_state.surface_builder.add_child_render_task(
1812            clip_task_id,
1813            frame_state.rg_builder,
1814        );
1815        clip_task_index
1816    } else {
1817        ClipTaskIndex::INVALID
1818    };
1819    scratch.frame.draws[prim_instance_index.0 as usize].clip_task_index = new_clip_task_index;
1820
1821    true
1822}
1823
1824/// Write out to the clip mask instances array the correct clip mask
1825/// config for this segment.
1826pub fn update_brush_segment_clip_task(
1827    segment: &BrushSegment,
1828    clip_chain: Option<&ClipChainInstance>,
1829    root_spatial_node_index: SpatialNodeIndex,
1830    surface_index: SurfaceIndex,
1831    frame_context: &FrameBuildingContext,
1832    frame_state: &mut FrameBuildingState,
1833    device_pixel_scale: DevicePixelScale,
1834) -> ClipMaskKind {
1835    let clip_chain = match clip_chain {
1836        Some(chain) => chain,
1837        None => return ClipMaskKind::Clipped,
1838    };
1839    if !clip_chain.needs_mask ||
1840       (!segment.may_need_clip_mask && !clip_chain.has_non_local_clips) {
1841        return ClipMaskKind::None;
1842    }
1843
1844    let unadjusted_device_rect = match frame_state.surfaces[surface_index.0].get_surface_rect(
1845        &clip_chain.pic_coverage_rect,
1846        frame_context.spatial_tree,
1847    ) {
1848        Some(rect) => rect,
1849        None => return ClipMaskKind::Clipped,
1850    };
1851
1852    let (device_rect, device_pixel_scale) = adjust_mask_scale_for_max_size(unadjusted_device_rect, device_pixel_scale);
1853
1854    if device_rect.size().to_i32().is_empty() {
1855        log::warn!("Bad adjusted mask size {:?} (was {:?})", device_rect.size(), unadjusted_device_rect.size());
1856        return ClipMaskKind::Clipped;
1857    }
1858
1859    let clip_task_id = RenderTaskKind::new_mask(
1860        device_rect,
1861        clip_chain.clips_range,
1862        root_spatial_node_index,
1863        frame_state.rg_builder,
1864        device_pixel_scale,
1865        frame_context.fb_config,
1866    );
1867
1868    frame_state.surface_builder.add_child_render_task(
1869        clip_task_id,
1870        frame_state.rg_builder,
1871    );
1872    ClipMaskKind::Mask(clip_task_id)
1873}
1874
1875
1876fn write_brush_segment_description(
1877    prim_local_rect: LayoutRect,
1878    prim_local_clip_rect: LayoutRect,
1879    clip_chain: &ClipChainInstance,
1880    segment_builder: &mut SegmentBuilder,
1881    clip_store: &ClipStore,
1882    data_stores: &DataStores,
1883) -> bool {
1884    // If the brush is small, we want to skip building segments
1885    // and just draw it as a single primitive with clip mask.
1886    if prim_local_rect.area() < MIN_BRUSH_SPLIT_AREA {
1887        return false;
1888    }
1889
1890    // NOTE: The local clip rect passed to the segment builder must be the unmodified
1891    //       local clip rect from the clip leaf, not the local_clip_rect from the
1892    //       clip-chain instance. The clip-chain instance may have been reduced by
1893    //       clips that are in the same coordinate system, but not the same spatial
1894    //       node as the primitive. This can result in the clip for the segment building
1895    //       being affected by scrolling clips, which we can't handle (since the segments
1896    //       are not invalidated during frame building after being built).
1897    segment_builder.initialize(
1898        prim_local_rect,
1899        None,
1900        prim_local_clip_rect,
1901    );
1902
1903    // Segment the primitive on all the local-space clip sources that we can.
1904    for i in 0 .. clip_chain.clips_range.count {
1905        let clip_instance = clip_store
1906            .get_instance_from_range(&clip_chain.clips_range, i);
1907        let clip_node = &data_stores.clip[clip_instance.handle];
1908
1909        // If this clip item is positioned by another positioning node, its relative position
1910        // could change during scrolling. This means that we would need to resegment. Instead
1911        // of doing that, only segment with clips that have the same positioning node.
1912        // TODO(mrobinson, #2858): It may make sense to include these nodes, resegmenting only
1913        // when necessary while scrolling.
1914        if !clip_instance.flags.contains(ClipNodeFlags::SAME_SPATIAL_NODE) {
1915            continue;
1916        }
1917
1918        let (local_clip_rect, radius, mode) = match clip_node.item.kind {
1919            ClipItemKind::RoundedRectangle { radius, mode } => {
1920                let radius = clamped_radius(&radius, clip_instance.clip_rect.size());
1921                (clip_instance.clip_rect, Some(radius), mode)
1922            }
1923            ClipItemKind::Rectangle { mode } => {
1924                (clip_instance.clip_rect, None, mode)
1925            }
1926            ClipItemKind::Image { .. } => {
1927                panic!("bug: masks not supported on old segment path");
1928            }
1929        };
1930
1931        segment_builder.push_clip_rect(local_clip_rect, radius, mode);
1932    }
1933
1934    true
1935}
1936
1937fn build_segments_if_needed(
1938    instance: &mut PrimitiveInstance,
1939    prim_instance_index: PrimitiveInstanceIndex,
1940    prim_clip_chain: &ClipChainInstance,
1941    frame_state: &mut FrameBuildingState,
1942    prim_store: &mut PrimitiveStore,
1943    data_stores: &DataStores,
1944    scratch: &mut PrimitiveScratchBuffer,
1945) {
1946
1947    // Usually, the primitive rect can be found from information
1948    // in the instance and primitive template.
1949    let prim_local_rect = data_stores.get_local_prim_rect(
1950        instance,
1951        scratch.frame.draws[prim_instance_index.0 as usize].snapped_local_rect,
1952        &prim_store.pictures,
1953        frame_state.surfaces,
1954    );
1955
1956    // Decide whether this kind opts in to segmentation this frame. If
1957    // not, leave the per-draw segment_instance_index as its initialized
1958    // UNUSED value and bail.
1959    match instance.kind {
1960        PrimitiveKind::Rectangle { .. } => {
1961            // Always opts in.
1962        }
1963        PrimitiveKind::YuvImage { .. } => {
1964            // Only use segments for YUV images if not drawing as a compositor surface
1965            let csk = scratch.frame.draws[prim_instance_index.0 as usize].compositor_surface_kind;
1966            if !csk.supports_segments() {
1967                return;
1968            }
1969        }
1970        PrimitiveKind::Image { data_handle, .. } => {
1971            let image_data = &data_stores.image[data_handle].kind;
1972            let csk = scratch.frame.draws[prim_instance_index.0 as usize].compositor_surface_kind;
1973
1974            //Note: tiled images don't support automatic segmentation,
1975            // they strictly produce one segment per visible tile instead.
1976            if !csk.supports_segments() ||
1977                frame_state.resource_cache
1978                    .get_image_properties(image_data.key)
1979                    .and_then(|properties| properties.tiling)
1980                    .is_some()
1981            {
1982                return;
1983            }
1984        }
1985        PrimitiveKind::Picture { .. } |
1986        PrimitiveKind::TextRun { .. } |
1987        PrimitiveKind::NormalBorder { .. } |
1988        PrimitiveKind::ImageBorder { .. } |
1989        PrimitiveKind::LinearGradient { .. } |
1990        PrimitiveKind::RadialGradient { .. } |
1991        PrimitiveKind::ConicGradient { .. } |
1992        PrimitiveKind::LineDecoration { .. } |
1993        PrimitiveKind::BackdropCapture { .. } |
1994        PrimitiveKind::BackdropRender { .. } => {
1995            // These primitives don't support / need segments.
1996            return;
1997        }
1998        PrimitiveKind::BoxShadow { .. } => {
1999            unreachable!("BUG: box-shadows should not hit legacy brush clip path");
2000        }
2001    };
2002
2003    // Per-frame, unconditional segment build. The previous
2004    // INVALID-sentinel skip is gone — segments + segment_instances are
2005    // per-frame now, so they start empty each frame and we always
2006    // rebuild for every visible segmented prim.
2007    let mut segments: SmallVec<[BrushSegment; 8]> = SmallVec::new();
2008    let clip_leaf = frame_state.clip_tree.get_leaf(instance.clip_leaf_id);
2009
2010    if write_brush_segment_description(
2011        prim_local_rect,
2012        clip_leaf.snapped_local_clip_rect,
2013        prim_clip_chain,
2014        &mut frame_state.segment_builder,
2015        frame_state.clip_store,
2016        data_stores,
2017    ) {
2018        frame_state.segment_builder.build(|segment| {
2019            segments.push(
2020                BrushSegment::new(
2021                    segment.rect.translate(-prim_local_rect.min.to_vector()),
2022                    segment.has_mask,
2023                    segment.edge_flags,
2024                    [0.0; 4],
2025                    BrushFlags::PERSPECTIVE_INTERPOLATION,
2026                ),
2027            );
2028        });
2029    }
2030
2031    // If only a single segment is produced, there is no benefit to writing
2032    // a segment instance array. Instead, just use the main primitive rect
2033    // written into the GPU cache.
2034    // TODO(gw): This is (sortof) a bandaid - due to a limitation in the current
2035    //           brush encoding, we can only support a total of up to 2^16 segments.
2036    //           This should be (more than) enough for any real world case, so for
2037    //           now we can handle this by skipping cases where we were generating
2038    //           segments where there is no benefit. The long term / robust fix
2039    //           for this is to move the segment building to be done as a more
2040    //           limited nine-patch system during scene building, removing arbitrary
2041    //           segmentation during frame-building (see bug #1617491).
2042    if segments.len() <= 1 {
2043        // Leave the per-draw index as its initialized UNUSED value.
2044        return;
2045    }
2046
2047    let segments_range = scratch.frame.segments.extend(segments);
2048    let new_index = scratch.frame.segment_instances.push(BrushSegmentation {
2049        segments_range,
2050        gpu_data: GpuBufferAddress::INVALID,
2051    });
2052    scratch.frame.draws[prim_instance_index.0 as usize].segment_instance_index = new_index;
2053}
2054
2055// Ensures that the size of mask render tasks are within MAX_MASK_SIZE.
2056fn adjust_mask_scale_for_max_size(device_rect: DeviceIntRect, device_pixel_scale: DevicePixelScale) -> (DeviceIntRect, DevicePixelScale) {
2057    if device_rect.width() > MAX_MASK_SIZE || device_rect.height() > MAX_MASK_SIZE {
2058        // round_out will grow by 1 integer pixel if origin is on a
2059        // fractional position, so keep that margin for error with -1:
2060        let device_rect_f = device_rect.to_f32();
2061        let scale = (MAX_MASK_SIZE - 1) as f32 /
2062            f32::max(device_rect_f.width(), device_rect_f.height());
2063        let new_device_pixel_scale = device_pixel_scale * Scale::new(scale);
2064        let new_device_rect = (device_rect_f * Scale::new(scale))
2065            .round_out()
2066            .to_i32();
2067        (new_device_rect, new_device_pixel_scale)
2068    } else {
2069        (device_rect, device_pixel_scale)
2070    }
2071}
2072
2073impl CompositorSurfaceKind {
2074    /// Returns true if the compositor surface strategy supports segment rendering
2075    fn supports_segments(&self) -> bool {
2076        match self {
2077            CompositorSurfaceKind::Underlay | CompositorSurfaceKind::Overlay => false,
2078            CompositorSurfaceKind::Blit => true,
2079        }
2080    }
2081}
2082
2083/// Pattern builder for a single fast-path two-stop segment emitted by
2084/// `decompose_axis_aligned_gradient`. Holds the segment's gradient line and
2085/// stop colors (in segment-local coords); `build` translates start/end into
2086/// the prim's spatial-node space by adding `ctx.prim_origin`.
2087struct LinearGradientSegmentPattern {
2088    start: LayoutPoint,
2089    end: LayoutPoint,
2090    stops: [GradientStop; 2],
2091}
2092
2093impl PatternBuilder for LinearGradientSegmentPattern {
2094    fn build(
2095        &self,
2096        _sub_rect: Option<DeviceRect>,
2097        offset: LayoutVector2D,
2098        ctx: &PatternBuilderContext,
2099        state: &mut PatternBuilderState,
2100    ) -> Pattern {
2101        let prim_offset = offset + ctx.prim_origin.to_vector();
2102        linear_gradient_pattern(
2103            self.start + prim_offset,
2104            self.end + prim_offset,
2105            ExtendMode::Clamp,
2106            &self.stops,
2107            ctx.fb_config.is_software,
2108            state.frame_gpu_data,
2109        )
2110    }
2111}