Skip to main content

webrender/
quad.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5use api::{units::*, ClipMode, ColorF};
6use euclid::{Scale, point2};
7
8use crate::ItemUid;
9use crate::gpu_types::ClipSpace;
10use crate::pattern::repeat::RepeatedPattern;
11use crate::render_task::{SubTask, RectangleClipSubTask, ImageClipSubTask};
12use crate::transform::TransformPalette;
13use crate::batch::{BatchKey, BatchKind, BatchTextures};
14use crate::clip::{clamped_radius, ClipChainInstance, ClipIntern, ClipItemKind, ClipNodeRange, ClipStore, ClipNodeInstance, ClipItem};
15use crate::command_buffer::{CommandBufferIndex, PrimitiveCommand, QuadFlags};
16use crate::frame_builder::{FrameBuildingContext, FrameBuildingState, PictureContext};
17use crate::gpu_types::{PrimitiveInstanceData, QuadHeader, QuadInstance, QuadPrimitive, QuadSegment, ZBufferId};
18use crate::intern::DataStore;
19use crate::internal_types::TextureSource;
20use crate::pattern::{Pattern, PatternBuilder, PatternBuilderContext, PatternBuilderState, PatternKind, PatternShaderInput};
21use crate::prim_store::{NinePatchDescriptor, PrimitiveInstanceIndex, PrimitiveScratchBuffer};
22use crate::render_task::{RenderTask, RenderTaskAddress, RenderTaskKind};
23use crate::render_task_cache::{RenderTaskCacheKey, RenderTaskCacheKeyKind, RenderTaskParent};
24use crate::render_task_graph::{RenderTaskGraph, RenderTaskGraphBuilder, RenderTaskId, SubTaskRange};
25use crate::renderer::{BlendMode, GpuBufferAddress, GpuBufferBuilder, GpuBufferBuilderF, GpuBufferDataI};
26use crate::segment::EdgeMask;
27use crate::space::SpaceMapper;
28use crate::spatial_tree::{CoordinateSpaceMapping, SpatialNodeIndex, SpatialTree};
29use crate::transform::GpuTransformId;
30use crate::util::{extract_inner_rect_k, MaxRect, ScaleOffset};
31use crate::visibility::compute_conservative_visible_rect;
32
33/// This type reflects the unfortunate situation with quad coordinates where we
34/// sometimes use layout and sometimes device coordinates.
35pub type LayoutOrDeviceRect = api::euclid::default::Box2D<f32>;
36
37const MIN_AA_SEGMENTS_SIZE: f32 = 4.0;
38const MIN_QUAD_SPLIT_SIZE: f32 = 256.0;
39// We merge compatible neighbor tiles in the same rows which means that allowing
40// more tiles on the x axis doesn't generally produce more tiles, but it allows
41// more precise segmentation.
42// The segmentation right now is still quite coarse.
43const MAX_TILES_PER_QUAD_X: usize = 8;
44const MAX_TILES_PER_QUAD_Y: usize = 4;
45
46
47#[derive(Clone, Debug, Hash, PartialEq, Eq)]
48#[cfg_attr(feature = "capture", derive(Serialize))]
49#[cfg_attr(feature = "replay", derive(Deserialize))]
50pub struct QuadCacheKey {
51    pub prim: u64,
52    pub clips: [u64; 3],
53    pub transform: [u32; 4],
54}
55
56/// Contains some transform-related information that is computed
57/// per primitive cluster.
58pub struct QuadTransformState {
59    map_prim_to_raster: CoordinateSpaceMapping<LayoutPixel, LayoutPixel>,
60    as_scale_offset: Option<ScaleOffset>, // local-to-device
61    is_2d_axis_aligned: bool,
62    prim_spatial_node: SpatialNodeIndex,
63    raster_spatial_node: SpatialNodeIndex,
64    device_pixel_scale: DevicePixelScale,
65}
66
67impl QuadTransformState {
68    pub fn new() -> QuadTransformState {
69        QuadTransformState {
70            map_prim_to_raster: CoordinateSpaceMapping::Local,
71            as_scale_offset: Some(ScaleOffset::identity()),
72            is_2d_axis_aligned: true,
73            prim_spatial_node: SpatialNodeIndex::INVALID,
74            raster_spatial_node: SpatialNodeIndex::INVALID,
75            device_pixel_scale: DevicePixelScale::identity(),
76        }
77    }
78
79    pub fn set(
80        &mut self,
81        src_node: SpatialNodeIndex,
82        dst_node: SpatialNodeIndex,
83        spatial_tree: &SpatialTree,
84        scale: DevicePixelScale,
85    ) {
86        if self.prim_spatial_node == src_node && self.raster_spatial_node == dst_node {
87            return;
88        }
89
90        self.map_prim_to_raster = spatial_tree.get_relative_transform(src_node, dst_node);
91
92        self.as_scale_offset = self.map_prim_to_raster
93            .as_2d_scale_offset()
94            .map(|t| t.then_scale(scale.0));
95
96        self.is_2d_axis_aligned = self.as_scale_offset.is_some()
97            || self.map_prim_to_raster.is_2d_axis_aligned();
98
99        self.prim_spatial_node = src_node;
100        self.raster_spatial_node = dst_node;
101        self.device_pixel_scale = scale;
102    }
103
104    pub fn is_2d_scale_offset(&self) -> bool {
105        self.as_scale_offset.is_some()
106    }
107
108    pub fn is_2d_axis_aligned(&self) -> bool {
109        self.is_2d_axis_aligned
110    }
111
112    // The local to device transform as a scale+offset transform if it
113    // can be represented as such.
114    pub fn as_2d_scale_offset(&self) -> Option<&ScaleOffset> {
115        self.as_scale_offset.as_ref()
116    }
117
118    // X and Y scale facotrs of the local to device transform.
119    pub fn scale_factors(&self) -> (f32, f32) {
120        let s = self.map_prim_to_raster.scale_factors();
121
122        (s.0 * self.device_pixel_scale().0, s.1 * self.device_pixel_scale().0)
123    }
124
125    pub fn prim_spatial_node_index(&self) -> SpatialNodeIndex {
126        self.prim_spatial_node
127    }
128
129    pub fn raster_spatial_node_index(&self) -> SpatialNodeIndex {
130        self.raster_spatial_node
131    }
132
133    pub fn device_pixel_scale(&self) -> DevicePixelScale {
134        self.device_pixel_scale
135    }
136}
137
138/// Describes how clipping affects the rendering of a quad primitive.
139///
140/// As a general rule, parts of the quad that require masking are prerendered in an
141/// intermediate target and the mask is applied using multiplicative blending to
142/// the intermediate result before compositing it into the destination target.
143///
144/// Each segment can opt in or out of masking independently.
145#[derive(Debug, Copy, Clone)]
146pub enum QuadRenderStrategy {
147    /// The quad is not affected by any mask and is drawn directly in the destination
148    /// target.
149    Direct,
150    /// The quad is drawn entirely in an intermediate target and a mask is applied
151    /// before compositing in the destination target.
152    Indirect,
153    /// A rounded rectangle clip is applied to the quad primitive via a nine-patch.
154    /// The segments of the nine-patch that require a mask are rendered and masked in
155    /// an intermediate target, while other segments are drawn directly in the destination
156    /// target.
157    NinePatch {
158        radius: LayoutVector2D,
159        clip_rect: LayoutRect,
160    },
161    /// Split the primitive into coarse tiles so that each tile independently
162    /// has the opportunity to be drawn directly in the destination target or
163    /// via an intermediate target if it is affected by a mask.
164    Tiled,
165}
166
167pub fn prepare_quad(
168    pattern_builder: &dyn PatternBuilder,
169    local_rect: &LayoutRect,
170    local_clip_rect: &LayoutRect,
171    aligned_aa_edges: EdgeMask,
172    transfomed_aa_edges: EdgeMask,
173    prim_instance_index: PrimitiveInstanceIndex,
174    cache_key: &Option<QuadCacheKey>,
175    clip_chain: &ClipChainInstance,
176    transform: &mut QuadTransformState,
177
178    frame_context: &FrameBuildingContext,
179    pic_context: &PictureContext,
180    targets: &[CommandBufferIndex],
181    interned_clips: &DataStore<ClipIntern>,
182
183    frame_state: &mut FrameBuildingState,
184    scratch: &mut PrimitiveScratchBuffer,
185) {
186    let pattern_ctx = PatternBuilderContext {
187        spatial_tree: frame_context.spatial_tree,
188        fb_config: frame_context.fb_config,
189        prim_origin: local_rect.min,
190    };
191
192    let pattern = pattern_builder.build(
193        None,
194        LayoutVector2D::zero(),
195        &pattern_ctx,
196        &mut PatternBuilderState {
197            frame_gpu_data: frame_state.frame_gpu_data,
198            transforms: frame_state.transforms,
199        },
200    );
201
202    let strategy = match cache_key {
203        Some(_) => QuadRenderStrategy::Indirect,
204        None => get_prim_render_strategy(
205            transform.prim_spatial_node_index(),
206            clip_chain,
207            frame_state.clip_store,
208            interned_clips,
209            transform.is_2d_scale_offset(),
210            pattern_ctx.spatial_tree,
211        ),
212    };
213
214    prepare_quad_impl(
215        strategy,
216        &pattern,
217        local_rect,
218        local_clip_rect,
219        aligned_aa_edges,
220        transfomed_aa_edges,
221        prim_instance_index,
222        cache_key,
223        clip_chain,
224
225        transform,
226        &pattern_ctx,
227        pic_context,
228        targets,
229        interned_clips,
230
231        frame_state,
232        scratch,
233    )
234}
235
236pub fn prepare_repeatable_quad(
237    pattern_builder: &dyn PatternBuilder,
238    local_rect: &LayoutRect,
239    local_clip_rect: &LayoutRect,
240    stretch_size: LayoutSize,
241    tile_spacing: LayoutSize,
242    aligned_aa_edges: EdgeMask,
243    transfomed_aa_edges: EdgeMask,
244    prim_instance_index: PrimitiveInstanceIndex,
245    cache_key: &Option<QuadCacheKey>,
246    clip_chain: &ClipChainInstance,
247    transform: &mut QuadTransformState,
248
249    frame_context: &FrameBuildingContext,
250    pic_context: &PictureContext,
251    targets: &[CommandBufferIndex],
252    interned_clips: &DataStore<ClipIntern>,
253
254    frame_state: &mut FrameBuildingState,
255    scratch: &mut PrimitiveScratchBuffer,
256) {
257    let pattern_ctx = PatternBuilderContext {
258        spatial_tree: frame_context.spatial_tree,
259        fb_config: frame_context.fb_config,
260        prim_origin: local_rect.min,
261    };
262
263    let pattern = pattern_builder.build(
264        None,
265        LayoutVector2D::zero(),
266        &pattern_ctx,
267        &mut PatternBuilderState {
268            frame_gpu_data: frame_state.frame_gpu_data,
269            transforms: frame_state.transforms,
270        },
271    );
272
273    // This could move back into preapre_quad_impl if it took the tile's
274    // coverage rect into account rather than the whole primitive's, but
275    // for now it does the latter so we might as well not do the work
276    // multiple times.
277    let strategy = match cache_key {
278        Some(_) => QuadRenderStrategy::Indirect,
279        None => get_prim_render_strategy(
280            transform.prim_spatial_node_index(),
281            clip_chain,
282            frame_state.clip_store,
283            interned_clips,
284            transform.is_2d_scale_offset(),
285            pattern_ctx.spatial_tree,
286        ),
287    };
288
289    let needs_repetition = stretch_size.width < local_rect.width()
290        || stretch_size.height < local_rect.height();
291
292    if !needs_repetition {
293        // The stretch size may be larger than the local rect's size which
294        // should resut in some stretching (without repetitions). However,
295        // the non-repeated quad code paths don't take a stretch_size, so
296        // we bake it into the local rect and make sure that the local clip
297        // prevents the primitive from overflowing its initial bounds.
298        let local_clip_rect = local_clip_rect.intersection_unchecked(&local_rect);
299        let local_rect = LayoutRect::from_origin_and_size(
300            local_rect.min,
301            stretch_size,
302        );
303
304        // Most common path.
305        prepare_quad_impl(
306            strategy,
307            &pattern,
308            &local_rect,
309            &local_clip_rect,
310            aligned_aa_edges,
311            transfomed_aa_edges,
312            prim_instance_index,
313            &cache_key,
314            clip_chain,
315            transform,
316            &pattern_ctx,
317            pic_context,
318            targets,
319            interned_clips,
320            frame_state,
321            scratch,
322        );
323
324        return;
325    }
326
327    let pattern_rect = LayoutRect::from_origin_and_size(
328        local_rect.min,
329        stretch_size,
330    );
331
332    let scales = transform.scale_factors();
333    let mut indirect_transform = ScaleOffset::from_scale(scales.into());
334    let mut surface_rect: DeviceRect = indirect_transform.map_rect(&pattern_rect);
335
336    // If the source pattern is an image, we can repeat it directly using the repeat
337    // shader, without an extra render task.
338    let src_task_id = pattern.as_render_task();
339
340    // If the number of repetitions is high, we are better off using the repeat shader,
341    // but we want to avoid the extra render task if it is large.
342    let num_repetitions = local_rect.area() / stretch_size.area();
343    let repeat_using_a_shader = src_task_id.is_some()
344        || (num_repetitions > 16.0 && surface_rect.width() < 1024.0 && surface_rect.height() < 1024.0)
345        || (num_repetitions > 64.0 && surface_rect.area() < 1024.0 * 1024.0);
346
347    if repeat_using_a_shader {
348        let (src_task_id, base_color) = match src_task_id {
349            Some(task) => (task, pattern.base_color),
350            None => {
351                // The source is not an image. Make it one by rendering
352                // the pattern in a render task.
353
354                adjust_indirect_pattern_resolution(
355                    &pattern_rect,
356                    2048.0,
357                    &mut surface_rect,
358                    &mut indirect_transform,
359                );
360
361                let Some(task_id) = prepare_indirect_pattern(
362                    transform.prim_spatial_node_index(),
363                    transform.raster_spatial_node_index(),
364                    &pattern_rect,
365                    &pattern_rect,
366                    &surface_rect,
367                    Some(&indirect_transform),
368                    DevicePixelScale::identity(),
369                    GpuTransformId::IDENTITY,
370                    &pattern,
371                    QuadFlags::empty(),
372                    EdgeMask::empty(),
373                    cache_key,
374                    None,
375                    &pattern_ctx,
376                    interned_clips,
377                    frame_state,
378                ) else {
379                    return;
380                };
381
382                (task_id, ColorF::WHITE)
383            }
384        };
385
386        let repetitions = RepeatedPattern {
387            stretch_size,
388            spacing: tile_spacing,
389            src_task_id,
390            src_is_opaque: pattern.is_opaque,
391        };
392
393        let repeat_pattern = repetitions.build(
394            None,
395            LayoutVector2D::zero(),
396            &pattern_ctx,
397            &mut PatternBuilderState {
398                frame_gpu_data: frame_state.frame_gpu_data,
399                transforms: frame_state.transforms,
400            },
401        ).with_base_color(base_color);
402
403        // Note: caching is disabled when using the repeating shader.
404        // The cache key would need more information about the repetition.
405        prepare_quad_impl(
406            strategy,
407            &repeat_pattern,
408            local_rect,
409            local_clip_rect,
410            aligned_aa_edges,
411            transfomed_aa_edges,
412            prim_instance_index,
413            &None,
414            clip_chain,
415            transform,
416            &pattern_ctx,
417            pic_context,
418            targets,
419            interned_clips,
420            frame_state,
421            scratch,
422        );
423
424        return;
425    }
426
427    // Repeat by duplicating the primitive.
428
429    let visible_rect = compute_conservative_visible_rect(
430        clip_chain,
431        frame_state.current_dirty_region().combined,
432        frame_state.current_dirty_region().visibility_spatial_node,
433        transform.prim_spatial_node_index(),
434        frame_context.spatial_tree,
435    ).intersection_unchecked(local_clip_rect);
436
437    let stride = stretch_size + tile_spacing;
438    let repetitions = crate::image_tiling::repetitions(&local_rect, &visible_rect, stride);
439    for tile in repetitions {
440        let tile_rect = LayoutRect::from_origin_and_size(tile.origin, stretch_size);
441        let clip_rect = local_clip_rect.intersection_unchecked(&tile_rect);
442        let pattern_offset = tile.origin - local_rect.min;
443        let pattern = pattern_builder.build(
444            None,
445            pattern_offset,
446            &pattern_ctx,
447            &mut PatternBuilderState {
448                frame_gpu_data: frame_state.frame_gpu_data,
449                transforms: frame_state.transforms,
450            },
451        );
452
453        prepare_quad_impl(
454            strategy,
455            &pattern,
456            &tile_rect,
457            &clip_rect,
458            aligned_aa_edges & tile.edge_flags,
459            transfomed_aa_edges & tile.edge_flags,
460            prim_instance_index,
461            // Bug 2017832 - Caching breaks manually repeated patterns
462            // with SWGL for some reason.
463            &None,
464            clip_chain,
465            transform,
466            &pattern_ctx,
467            pic_context,
468            targets,
469            interned_clips,
470            frame_state,
471            scratch,
472        );
473    }
474}
475
476pub fn prepare_border_image_nine_patch(
477    nine_patch: &NinePatchDescriptor,
478    pattern_builder: &dyn PatternBuilder,
479    local_rect: &LayoutRect,
480    stretch_size: LayoutSize,
481    aligned_aa_edges: EdgeMask,
482    transfomed_aa_edges: EdgeMask,
483    prim_instance_index: PrimitiveInstanceIndex,
484    clip_chain: &ClipChainInstance,
485    transform: &mut QuadTransformState,
486
487    frame_context: &FrameBuildingContext,
488    pic_context: &PictureContext,
489    targets: &[CommandBufferIndex],
490    interned_clips: &DataStore<ClipIntern>,
491
492    frame_state: &mut FrameBuildingState,
493    scratch: &mut PrimitiveScratchBuffer,
494) {
495    let pattern_ctx = PatternBuilderContext {
496        spatial_tree: frame_context.spatial_tree,
497        fb_config: frame_context.fb_config,
498        prim_origin: local_rect.min,
499    };
500
501    let pattern = pattern_builder.build(
502        None,
503        LayoutVector2D::zero(),
504        &pattern_ctx,
505        &mut PatternBuilderState {
506            frame_gpu_data: frame_state.frame_gpu_data,
507            transforms: frame_state.transforms,
508        },
509    );
510
511    let strategy = get_prim_render_strategy(
512        transform.prim_spatial_node_index(),
513        clip_chain,
514        frame_state.clip_store,
515        interned_clips,
516        transform.is_2d_scale_offset(),
517        pattern_ctx.spatial_tree,
518    );
519
520    // The indirect transform drives the resolution at which each segment is going
521    // going to be rasterized in intermediate render tasks.
522    let scales = transform.scale_factors();
523    let base_indirect_transform = ScaleOffset::from_scale(scales.into());
524
525    nine_patch.for_each_segment(local_rect, &mut|dst_rect, src_rect, side, _repeat_h, _repeat_v| {
526        // First find the sub-rect of the source pattern that this segment is using.
527        let min_x = local_rect.min.x + stretch_size.width * src_rect.uv0.x;
528        let min_y = local_rect.min.y + stretch_size.height * src_rect.uv0.y;
529        let max_x = local_rect.min.x + stretch_size.width * src_rect.uv1.x;
530        let max_y = local_rect.min.y + stretch_size.height * src_rect.uv1.y;
531        let pattern_rect = LayoutRect {
532            min: point2(min_x, min_y),
533            max: point2(max_x, max_y),
534        };
535
536        // Rasterize the source pattern into a render task.
537
538        // We could get away without the intermediate task in some cases, for example
539        // if the segment does not repeat the pattern. However this is fiddly due to
540        // how the nine-patch's source slicing distorts the local space of the pattern.
541        // Always using an intermediate render task lets us easily handle the additional
542        // stretching effect on the image instead of introducing an additional transform
543        // for the pattern's coordinate space. On the other hand it means that we have
544        // to handle large source patterns and potentially down-scale them.
545
546        let mut indirect_transform = base_indirect_transform;
547        let mut surface_rect = indirect_transform.map_rect(&pattern_rect);
548
549        adjust_indirect_pattern_resolution(
550            &pattern_rect,
551            2048.0,
552            &mut surface_rect,
553            &mut indirect_transform,
554        );
555
556        let Some(task_id) = prepare_indirect_pattern(
557            transform.prim_spatial_node_index(),
558            transform.raster_spatial_node_index(),
559            &pattern_rect,
560            &pattern_rect,
561            &surface_rect,
562            Some(&indirect_transform),
563            DevicePixelScale::identity(),
564            GpuTransformId::IDENTITY,
565            &pattern,
566            QuadFlags::empty(),
567            EdgeMask::empty(),
568            &None,
569            None,
570            &pattern_ctx,
571            interned_clips,
572            frame_state,
573        ) else {
574            return;
575        };
576
577        let img_pattern = Pattern::texture(task_id, pattern.is_opaque);
578
579        prepare_quad_impl(
580            strategy,
581            &img_pattern,
582            &dst_rect,
583            &clip_chain.local_clip_rect,
584            aligned_aa_edges & side,
585            transfomed_aa_edges & side,
586            prim_instance_index,
587            &None,
588            clip_chain,
589
590            transform,
591            &pattern_ctx,
592            pic_context,
593            targets,
594            interned_clips,
595
596            frame_state,
597            scratch,
598        )
599    });
600}
601
602fn prepare_quad_impl(
603    strategy: QuadRenderStrategy,
604    pattern: &Pattern,
605    local_rect: &LayoutRect,
606    local_clip_rect: &LayoutRect,
607    aligned_aa_edges: EdgeMask,
608    transfomed_aa_edges: EdgeMask,
609    prim_instance_index: PrimitiveInstanceIndex,
610    cache_key: &Option<QuadCacheKey>,
611    clip_chain: &ClipChainInstance,
612
613    transform: &mut QuadTransformState,
614    ctx: &PatternBuilderContext,
615    pic_context: &PictureContext,
616    targets: &[CommandBufferIndex],
617    interned_clips: &DataStore<ClipIntern>,
618
619    frame_state: &mut FrameBuildingState,
620    scratch: &mut PrimitiveScratchBuffer,
621) {
622    // If the local-to-device transform can be expressed as a 2D scale-offset,
623    // We'll apply the transformation on the CPU and submit geometry in device
624    // space to the shaders. Otherwise, the geometry is sent to the shaders in
625    // layout space along with a transform.
626
627    let transform_id = if transform.is_2d_scale_offset() {
628        GpuTransformId::IDENTITY
629    } else {
630        frame_state.transforms.gpu.get_id_with_post_scale(
631            transform.prim_spatial_node_index(),
632            transform.raster_spatial_node_index(),
633            transform.device_pixel_scale().get(),
634            ctx.spatial_tree,
635        )
636    };
637
638    let prim_is_2d_scale_translation = transform.is_2d_scale_offset();
639    let prim_is_2d_axis_aligned = transform.is_2d_axis_aligned();
640
641    let mut quad_flags = QuadFlags::empty();
642
643    // Only use AA edge instances if the primitive is large enough to require it
644    let prim_size = local_rect.size();
645    if prim_size.width > MIN_AA_SEGMENTS_SIZE && prim_size.height > MIN_AA_SEGMENTS_SIZE {
646        quad_flags |= QuadFlags::USE_AA_SEGMENTS;
647    }
648
649    let needs_scissor = !prim_is_2d_scale_translation;
650    if !needs_scissor {
651        quad_flags |= QuadFlags::APPLY_RENDER_TASK_CLIP;
652    }
653
654    let aa_flags = if prim_is_2d_axis_aligned {
655        aligned_aa_edges
656    } else {
657        transfomed_aa_edges
658    };
659
660    // We round the coordinates of non-antialiased edges of the primitive.
661    // This allows us to ensure that indirect axis-aligned primitives cover the render
662    // task exactly. Since we do this for indirect primitives, we have to also do it for
663    // other rendering strategies to avoid cracks between side-by-side primitives.
664    let round_edges = !aa_flags;
665
666    if let QuadRenderStrategy::Direct = strategy {
667        if pattern.is_opaque {
668            quad_flags |= QuadFlags::IS_OPAQUE;
669        }
670
671        let quad = create_quad_primitive(
672            local_rect,
673            local_clip_rect,
674            &DeviceRect::max_rect(),
675            transform.as_2d_scale_offset(),
676            round_edges,
677            pattern,
678        );
679
680        let main_prim_address = frame_state.frame_gpu_data.f32.push(&quad);
681
682        // Render the primitive as a single instance. Coordinates are provided to the
683        // shader in layout space.
684        frame_state.push_prim(
685            &PrimitiveCommand::quad(
686                pattern.kind,
687                pattern.shader_input,
688                pattern.texture_input.task_id,
689                crate::prim_store::storage::Index::from_u32(prim_instance_index.0),
690                main_prim_address,
691                transform_id,
692                quad_flags,
693                aa_flags,
694                pattern.blend_mode,
695            ),
696            transform.prim_spatial_node_index(),
697            targets,
698        );
699
700        // If the pattern samples from a texture, add it as a dependency
701        // of the surface we're drawing directly on to.
702        if pattern.texture_input.task_id != RenderTaskId::INVALID {
703            frame_state
704                .surface_builder
705                .add_child_render_task(pattern.texture_input.task_id, frame_state.rg_builder);
706        }
707
708        return;
709    }
710
711    let surface = &mut frame_state.surfaces[pic_context.surface_index.0];
712    let clipped_pic_rect = clip_chain.pic_coverage_rect.intersection_unchecked(&surface.clipping_rect);
713
714    let pic_to_raster = SpaceMapper::new_with_target(
715        surface.raster_spatial_node_index,
716        surface.surface_spatial_node_index,
717        RasterRect::max_rect(),
718        ctx.spatial_tree,
719    );
720    let Some(clipped_raster_rect) = pic_to_raster.map(&clipped_pic_rect) else { return; };
721
722    // TODO: we are making the assumption that raster space and world space have the same
723    // scale. I think that it is the case, but it's not super clean.
724    let device_scale: Scale<f32, RasterPixel, DevicePixel> = Scale::new(transform.device_pixel_scale.0);
725
726    // Rounding is important here because clipped_surface_rect.min may be used as the origin
727    // of render tasks. Fractional values would introduce fractional offsets in the render tasks.
728    let mut clipped_surface_rect = (clipped_raster_rect * device_scale).round();
729
730    if let Some(t) = transform.as_2d_scale_offset() {
731        let clipped_local_rect = local_rect.intersection_unchecked(local_clip_rect);
732        clipped_surface_rect = clipped_surface_rect.intersection_unchecked(
733            &t.map_rect(&clipped_local_rect).round_out(),
734        );
735    }
736
737
738    if clipped_surface_rect.is_empty() {
739        return;
740    }
741
742    match strategy {
743        QuadRenderStrategy::Direct => {}
744        QuadRenderStrategy::Indirect => {
745            let Some(task_id) = prepare_indirect_pattern(
746                transform.prim_spatial_node_index(),
747                transform.raster_spatial_node_index(),
748                local_rect,
749                local_clip_rect,
750                &clipped_surface_rect,
751                transform.as_2d_scale_offset(),
752                transform.device_pixel_scale(),
753                transform_id,
754                pattern,
755                quad_flags,
756                aa_flags,
757                cache_key,
758                Some(clip_chain),
759                ctx,
760                interned_clips,
761                frame_state,
762            ) else {
763                return;
764            };
765
766            add_composite_prim(
767                pattern.blend_mode,
768                prim_instance_index,
769                &clipped_surface_rect,
770                frame_state,
771                targets,
772                &[QuadSegment { rect: clipped_surface_rect.to_untyped(), task_id }],
773            );
774        }
775        QuadRenderStrategy::Tiled => {
776            prepare_tiles(
777                prim_instance_index,
778                local_rect,
779                local_clip_rect,
780                &clipped_surface_rect,
781                pattern,
782                quad_flags,
783                aa_flags,
784                clip_chain,
785                transform_id,
786                transform,
787                pic_context,
788                ctx,
789                interned_clips,
790                frame_state,
791                scratch,
792                targets,
793            );
794        }
795        QuadRenderStrategy::NinePatch { clip_rect, radius } => {
796            prepare_nine_patch(
797                prim_instance_index,
798                local_rect,
799                local_clip_rect,
800                &clipped_surface_rect,
801                &clip_rect,
802                radius,
803                pattern,
804                quad_flags,
805                aa_flags,
806                clip_chain.clips_range,
807                transform,
808                transform_id,
809                ctx,
810                interned_clips,
811                frame_state,
812                scratch,
813                targets,
814            );
815        }
816    }
817}
818
819fn prepare_indirect_pattern(
820    prim_spatial_node_index: SpatialNodeIndex,
821    raster_spatial_node_index: SpatialNodeIndex,
822    local_rect: &LayoutRect,
823    local_clip_rect: &LayoutRect,
824    clipped_surface_rect: &DeviceRect,
825    local_to_device_scale_offset: Option<&ScaleOffset>,
826    device_pixel_scale: DevicePixelScale,
827    transform_id: GpuTransformId,
828    pattern: &Pattern,
829    mut quad_flags: QuadFlags,
830    aa_flags: EdgeMask,
831    cache_key: &Option<QuadCacheKey>,
832    clip_chain: Option<&ClipChainInstance>,
833    ctx: &PatternBuilderContext,
834    interned_clips: &DataStore<ClipIntern>,
835    frame_state: &mut FrameBuildingState,
836) -> Option<RenderTaskId> {
837    let round_edges = !aa_flags;
838    let quad = create_quad_primitive(
839        local_rect,
840        local_clip_rect,
841        clipped_surface_rect,
842        local_to_device_scale_offset,
843        round_edges,
844        pattern,
845    );
846
847    let main_prim_address = frame_state.frame_gpu_data.f32.push(&quad);
848
849    let mut clipped_surface_rect = *clipped_surface_rect;
850    if local_to_device_scale_offset.is_some() && aa_flags.is_empty() {
851        // If the primitive has a simple transform, then quad.clip is in device space
852        // and is a strict subset of clipped_surface_rect. If there is no anti-aliasing,
853        // and the pattern is opaque, we want to ensure that the primitive covers the
854        // entire render task so that we can safely skip clearing it.
855        // In this situation, create_quad_primitive has rounded the edges of quad.clip
856        // so we are not introducing a fractional offset in clipped_surface_rect.
857        clipped_surface_rect = quad.clip.cast_unit();
858    }
859
860    let task_size = clipped_surface_rect.size().to_i32();
861    if task_size.is_empty() {
862        return None;
863    }
864
865    let cache_key = cache_key.as_ref().map(|key| {
866        RenderTaskCacheKey {
867            origin: clipped_surface_rect.min.to_i32(),
868            size: task_size,
869            kind: RenderTaskCacheKeyKind::Quad(key.clone()),
870        }
871    });
872
873    if pattern.is_opaque {
874        quad_flags |= QuadFlags::IS_OPAQUE;
875    }
876
877    let needs_scissor = local_to_device_scale_offset.is_none();
878
879    let mut local_coverage_rect = *local_rect;
880    let mut clips_range = ClipNodeRange { first: 0, count: 0 };
881    if let Some(clip_chain) = clip_chain {
882        local_coverage_rect = local_coverage_rect.intersection_unchecked(local_clip_rect);
883        clips_range = clip_chain.clips_range;
884    }
885
886    Some(add_render_task_with_mask(
887        &pattern,
888        &local_coverage_rect,
889        task_size,
890        clipped_surface_rect.min,
891        clips_range,
892        prim_spatial_node_index,
893        raster_spatial_node_index,
894        main_prim_address,
895        transform_id,
896        aa_flags,
897        quad_flags,
898        device_pixel_scale,
899        needs_scissor,
900        cache_key.as_ref(),
901        ctx.spatial_tree,
902        interned_clips,
903        frame_state,
904    ))
905}
906
907fn prepare_nine_patch(
908    prim_instance_index: PrimitiveInstanceIndex,
909    local_rect: &LayoutRect,
910    local_clip_rect: &LayoutRect,
911    clipped_surface_rect: &DeviceRect,
912    ninepatch_rect: &LayoutRect,
913    radius: LayoutVector2D,
914    pattern: &Pattern,
915    mut quad_flags: QuadFlags,
916    aa_flags: EdgeMask,
917    clips_range: ClipNodeRange,
918    transform: &mut QuadTransformState,
919    gpu_transform: GpuTransformId,
920    ctx: &PatternBuilderContext,
921    interned_clips: &DataStore<ClipIntern>,
922    frame_state: &mut FrameBuildingState,
923    scratch: &mut PrimitiveScratchBuffer,
924    targets: &[CommandBufferIndex],
925) {
926    // Render the primtive as a nine-patch decomposed in device space.
927    // Nine-patch segments that need it are drawn in a render task and then composited into the
928    // destination picture.
929
930    let local_to_device = transform.as_2d_scale_offset().unwrap();
931    let mut device_prim_rect: DeviceRect = local_to_device.map_rect(&local_rect);
932    let mut device_clip_rect: DeviceRect = local_to_device
933        .map_rect(&local_clip_rect)
934        .intersection_unchecked(clipped_surface_rect);
935
936    let rounded_edges = !aa_flags;
937    device_prim_rect = rounded_edges.select(device_prim_rect.round(), device_prim_rect);
938    device_clip_rect = rounded_edges
939        .select(device_clip_rect.round(), device_clip_rect)
940        .intersection_unchecked(&device_prim_rect);
941    let clipped_surface_rect = rounded_edges
942        .select(device_clip_rect, *clipped_surface_rect);
943
944
945    let local_corner_0 = LayoutRect::new(
946        ninepatch_rect.min,
947        ninepatch_rect.min + radius,
948    );
949
950    let local_corner_1 = LayoutRect::new(
951        ninepatch_rect.max - radius,
952        ninepatch_rect.max,
953    );
954
955    let surface_rect_0: DeviceRect = local_to_device
956        .map_rect(&local_corner_0)
957        .round_out();
958    let surface_rect_1: DeviceRect = local_to_device
959        .map_rect(&local_corner_1)
960        .round_out();
961
962    let p0 = surface_rect_0.min;
963    let p1 = surface_rect_0.max;
964    let p2 = surface_rect_1.min;
965    let p3 = surface_rect_1.max;
966
967    let mut x_coords = [p0.x, p1.x, p2.x, p3.x];
968    let mut y_coords = [p0.y, p1.y, p2.y, p3.y];
969
970    x_coords.sort_by(|a, b| a.partial_cmp(b).unwrap());
971    y_coords.sort_by(|a, b| a.partial_cmp(b).unwrap());
972
973    scratch.frame.quad_direct_segments.clear();
974    scratch.frame.quad_indirect_segments.clear();
975
976    // TODO: re-land clip-out mode.
977    let mode = ClipMode::Clip;
978
979    if pattern.is_opaque {
980        quad_flags |= QuadFlags::IS_OPAQUE;
981    }
982
983    fn should_create_task(mode: ClipMode, x: usize, y: usize) -> bool {
984        match mode {
985            // Only create render tasks for the corners.
986            ClipMode::Clip => x != 1 && y != 1,
987            // Create render tasks for all segments (the
988            // center will be skipped).
989            ClipMode::ClipOut => true,
990        }
991    }
992
993    let indirect_prim_address = write_device_prim_blocks(
994        &mut frame_state.frame_gpu_data.f32,
995        &device_prim_rect,
996        &device_clip_rect,
997        pattern.base_color,
998        pattern.texture_input.task_id,
999        &[],
1000        local_to_device.inverse(),
1001    );
1002
1003    for y in 0 .. y_coords.len()-1 {
1004        let y0 = y_coords[y];
1005        let y1 = y_coords[y+1];
1006
1007        if y1 <= y0 {
1008            continue;
1009        }
1010
1011        for x in 0 .. x_coords.len()-1 {
1012            if mode == ClipMode::ClipOut && x == 1 && y == 1 {
1013                continue;
1014            }
1015
1016            let x0 = x_coords[x];
1017            let x1 = x_coords[x+1];
1018
1019            if x1 <= x0 {
1020                continue;
1021            }
1022
1023            let segment = DeviceRect::new(point2(x0, y0), point2(x1, y1));
1024            let segment_device_rect = match segment.intersection(&clipped_surface_rect) {
1025                Some(rect) => rect,
1026                None => {
1027                    continue;
1028                }
1029            };
1030
1031            if should_create_task(mode, x, y) {
1032                let task_size = segment_device_rect.size().to_i32();
1033                let task_id = add_render_task_with_mask(
1034                    pattern,
1035                    &local_rect,
1036                    task_size,
1037                    segment_device_rect.min,
1038                    clips_range,
1039                    transform.prim_spatial_node_index(),
1040                    transform.raster_spatial_node_index(),
1041                    indirect_prim_address,
1042                    gpu_transform,
1043                    aa_flags,
1044                    quad_flags,
1045                    transform.device_pixel_scale(),
1046                    false,
1047                    None,
1048                    ctx.spatial_tree,
1049                    interned_clips,
1050                    frame_state,
1051                );
1052                scratch.frame.quad_indirect_segments.push(QuadSegment {
1053                    rect: segment_device_rect.to_f32().cast_unit(),
1054                    task_id,
1055                });
1056            } else {
1057                scratch.frame.quad_direct_segments.push(QuadSegment {
1058                    rect: segment_device_rect.to_f32().cast_unit(),
1059                    task_id: pattern.texture_input.task_id,
1060                });
1061            };
1062        }
1063    }
1064
1065    if !scratch.frame.quad_direct_segments.is_empty() {
1066        add_pattern_prim(
1067            pattern,
1068            local_to_device.inverse(),
1069            prim_instance_index,
1070            &device_prim_rect,
1071            &device_clip_rect,
1072            pattern.is_opaque,
1073            frame_state,
1074            targets,
1075            &scratch.frame.quad_direct_segments,
1076        );
1077    }
1078
1079    if !scratch.frame.quad_indirect_segments.is_empty() {
1080        add_composite_prim(
1081            pattern.blend_mode,
1082            prim_instance_index,
1083            &device_clip_rect,
1084            frame_state,
1085            targets,
1086            &scratch.frame.quad_indirect_segments,
1087        );
1088    }
1089}
1090
1091fn prepare_tiles(
1092    prim_instance_index: PrimitiveInstanceIndex,
1093    local_rect: &LayoutRect,
1094    local_clip_rect: &LayoutRect,
1095    device_clip_rect: &DeviceRect,
1096    pattern: &Pattern,
1097    mut quad_flags: QuadFlags,
1098    aa_flags: EdgeMask,
1099    clip_chain: &ClipChainInstance,
1100    gpu_transform: GpuTransformId,
1101    transform: &mut QuadTransformState,
1102    pic_context: &PictureContext,
1103    ctx: &PatternBuilderContext,
1104    interned_clips: &DataStore<ClipIntern>,
1105    frame_state: &mut FrameBuildingState,
1106    scratch: &mut PrimitiveScratchBuffer,
1107    targets: &[CommandBufferIndex],
1108) {
1109    // Render the primtive as a grid of tiles decomposed in device space.
1110    // Tiles that need it are drawn in a render task and then composited into the
1111    // destination picture.
1112    // The coordinates are provided to the shaders:
1113    //  - in layout space for the render task,
1114    //  - in device space for the instances that draw into the destination picture.
1115
1116    let surface = &mut frame_state.surfaces[pic_context.surface_index.0];
1117    surface.map_local_to_picture.set_target_spatial_node(
1118        transform.prim_spatial_node_index(),
1119        ctx.spatial_tree,
1120    );
1121
1122    let unclipped_surface_rect = device_clip_rect.round_out();
1123
1124    let force_masks = !transform.is_2d_scale_offset();
1125    // Set up the tile classifier for the params of this quad
1126    scratch.retained.quad_tile_classifier.reset(
1127        unclipped_surface_rect,
1128        force_masks,
1129    );
1130
1131    let mut clip_to_raster = SpaceMapper::<LayoutPixel, RasterPixel>::new(
1132        transform.raster_spatial_node_index(),
1133        RasterRect::max_rect(),
1134    );
1135
1136    // Walk each clip, extract the local mask regions and add them to the tile classifier.
1137    for i in 0 .. clip_chain.clips_range.count {
1138        let clip_instance = frame_state.clip_store.get_instance_from_range(&clip_chain.clips_range, i);
1139        let clip_node = &interned_clips[clip_instance.handle];
1140
1141        clip_to_raster.set_target_spatial_node(clip_instance.spatial_node_index, ctx.spatial_tree);
1142
1143        let transform = match clip_to_raster.as_2d_scale_offset() {
1144            Some(t) => t.then_scale(transform.device_pixel_scale.0),
1145            None => {
1146                // If the clip transform is not axis-aligned, just assume the entire primitive
1147                // is affected by the clip, for now.
1148                // TODO: If we take this path, it means that we would have been better-off using
1149                // the indirect rendering strategy.
1150                scratch.retained.quad_tile_classifier.add_mask_region(unclipped_surface_rect);
1151                continue;
1152            }
1153        };
1154
1155        // Add regions to the classifier depending on the clip kind
1156        match clip_node.item.kind {
1157            ClipItemKind::Rectangle { mode } => {
1158                let rect = transform.map_rect(&clip_instance.clip_rect);
1159                scratch.retained.quad_tile_classifier.add_clip_rect(rect, mode);
1160            }
1161            ClipItemKind::RoundedRectangle { mode: ClipMode::Clip, ref radius } => {
1162                // For rounded-rects with Clip mode, we need a mask for each corner,
1163                // and to add the clip rect itself (to cull tiles outside that rect)
1164
1165                // Map the local rect and radii
1166                let radius = clamped_radius(radius, clip_instance.clip_rect.size());
1167                let clip_device_rect = transform.map_rect(&clip_instance.clip_rect);
1168                // If the transform has a negative scale, the rect will be correctly
1169                // flipped by the transform so that it isn't empty, but the sizes will
1170                // be negative. Make sure that the size stay positive.
1171                let r_tl = transform.map_size(&radius.top_left).abs();
1172                let r_tr = transform.map_size(&radius.top_right).abs();
1173                let r_br = transform.map_size(&radius.bottom_right).abs();
1174                let r_bl = transform.map_size(&radius.bottom_left).abs();
1175
1176                // Construct the mask regions for each corner
1177                let c_tl = DeviceRect::from_origin_and_size(
1178                    clip_device_rect.min,
1179                    r_tl,
1180                );
1181                let c_tr = DeviceRect::from_origin_and_size(
1182                    DevicePoint::new(
1183                        clip_device_rect.max.x - r_tr.width,
1184                        clip_device_rect.min.y,
1185                    ),
1186                    r_tr,
1187                );
1188                let c_br = DeviceRect::from_origin_and_size(
1189                    DevicePoint::new(
1190                        clip_device_rect.max.x - r_br.width,
1191                        clip_device_rect.max.y - r_br.height,
1192                    ),
1193                    r_br,
1194                );
1195                let c_bl = DeviceRect::from_origin_and_size(
1196                    DevicePoint::new(
1197                        clip_device_rect.min.x,
1198                        clip_device_rect.max.y - r_bl.height,
1199                    ),
1200                    r_bl,
1201                );
1202
1203                scratch.retained.quad_tile_classifier.add_clip_rect(clip_device_rect, ClipMode::Clip);
1204                scratch.retained.quad_tile_classifier.add_mask_region(c_tl);
1205                scratch.retained.quad_tile_classifier.add_mask_region(c_tr);
1206                scratch.retained.quad_tile_classifier.add_mask_region(c_br);
1207                scratch.retained.quad_tile_classifier.add_mask_region(c_bl);
1208            }
1209            ClipItemKind::RoundedRectangle { mode: ClipMode::ClipOut, ref radius } => {
1210                let radius = clamped_radius(radius, clip_instance.clip_rect.size());
1211                // Try to find an inner rect within the clip-out rounded rect that we can
1212                // use to cull inner tiles. If we can't, the entire rect needs to be masked
1213                match extract_inner_rect_k(&clip_instance.clip_rect, &radius, 0.5) {
1214                    Some(ref inner_rect) => {
1215                        let rect = transform.map_rect(inner_rect);
1216                        scratch.retained.quad_tile_classifier.add_clip_rect(rect, ClipMode::ClipOut);
1217                    }
1218                    None => {
1219                        let clip_device_rect = transform.map_rect(&clip_instance.clip_rect);
1220                        scratch.retained.quad_tile_classifier.add_mask_region(clip_device_rect);
1221                    }
1222                }
1223            }
1224            ClipItemKind::Image { .. } => {
1225                panic!("bug: image clips unexpected in this path");
1226            }
1227        }
1228    }
1229
1230    let indirect_prim_address = write_prim_blocks(
1231        &mut frame_state.frame_gpu_data.f32,
1232        local_rect,
1233        local_clip_rect,
1234        device_clip_rect,
1235        transform.as_2d_scale_offset(),
1236        !aa_flags,
1237        pattern,
1238    );
1239
1240    // Classify each tile within the quad to be Pattern / Mask / Clipped
1241    scratch.frame.quad_direct_segments.clear();
1242    scratch.frame.quad_indirect_segments.clear();
1243
1244    let tiles = scratch.retained.quad_tile_classifier.classify();
1245    for tile in tiles {
1246        // Check whether this tile requires a mask
1247        let is_direct = match tile.kind {
1248            QuadTileKind::Clipped => {
1249                // Note: We shouldn't take this branch since clipped tiles are
1250                // filtered out by the iterator.
1251                continue;
1252            }
1253            QuadTileKind::Pattern { has_mask } => !has_mask,
1254        };
1255
1256        // At extreme scales the rect can round to zero size due to
1257        // f32 precision, causing a panic in new_dynamic, so just
1258        // skip segments that would produce zero size tasks.
1259        // https://bugzilla.mozilla.org/show_bug.cgi?id=1941838#c13
1260        let tile_size = tile.rect.size().to_i32();
1261        if tile_size.is_empty() {
1262            continue;
1263        }
1264
1265        if is_direct {
1266            scratch.frame.quad_direct_segments.push(QuadSegment {
1267                rect: tile.rect.cast_unit(),
1268                task_id: RenderTaskId::INVALID
1269            });
1270        } else {
1271            if pattern.is_opaque {
1272                quad_flags |= QuadFlags::IS_OPAQUE;
1273            }
1274
1275            let needs_scissor = !transform.is_2d_scale_offset();
1276            let task_id = add_render_task_with_mask(
1277                pattern,
1278                local_rect,
1279                tile_size,
1280                tile.rect.min,
1281                clip_chain.clips_range,
1282                transform.prim_spatial_node_index(),
1283                transform.raster_spatial_node_index(),
1284                indirect_prim_address,
1285                gpu_transform,
1286                aa_flags,
1287                quad_flags,
1288                transform.device_pixel_scale(),
1289                needs_scissor,
1290                None,
1291                ctx.spatial_tree,
1292                interned_clips,
1293                frame_state,
1294            );
1295
1296            scratch.frame.quad_indirect_segments.push(QuadSegment {
1297                rect: tile.rect.cast_unit(),
1298                task_id,
1299            });
1300        }
1301    }
1302
1303    if !scratch.frame.quad_direct_segments.is_empty() {
1304        // Nine-patch segments are only allowed for axis-aligned primitives.
1305        let local_to_device = transform.as_2d_scale_offset().unwrap();
1306
1307        let device_prim_rect: DeviceRect = local_to_device.map_rect(&local_rect);
1308
1309        if pattern.texture_input.task_id != RenderTaskId::INVALID {
1310            for segment in &mut scratch.frame.quad_direct_segments {
1311                segment.task_id = pattern.texture_input.task_id;
1312            }
1313        }
1314
1315        add_pattern_prim(
1316            pattern,
1317            local_to_device.inverse(),
1318            prim_instance_index,
1319            &device_prim_rect,
1320            &device_clip_rect,
1321            pattern.is_opaque,
1322            frame_state,
1323            targets,
1324            &scratch.frame.quad_direct_segments,
1325        );
1326    }
1327
1328    if !scratch.frame.quad_indirect_segments.is_empty() {
1329        add_composite_prim(
1330            pattern.blend_mode,
1331            prim_instance_index,
1332            device_clip_rect,
1333            frame_state,
1334            targets,
1335            &scratch.frame.quad_indirect_segments,
1336        );
1337    }
1338}
1339
1340fn get_prim_render_strategy(
1341    prim_spatial_node_index: SpatialNodeIndex,
1342    clip_chain: &ClipChainInstance,
1343    clip_store: &ClipStore,
1344    interned_clips: &DataStore<ClipIntern>,
1345    prim_is_scale_offset: bool,
1346    spatial_tree: &SpatialTree,
1347) -> QuadRenderStrategy {
1348    if !clip_chain.needs_mask {
1349        return QuadRenderStrategy::Direct
1350    }
1351
1352    // Both the nine-patch and tiled paths rely on axis-aligned primitive for now.
1353    // In the case of nine-patch this is currently a hard requirement, while the
1354    // tiling path works with non-axis-aligned primitives but less efficiently than
1355    // the indirect path since all tiles end up treated as masks.
1356    let try_split_prim = if prim_is_scale_offset {
1357        // TODO: we should compute this based on the (tightest possible)
1358        // rect in device space instead of a rect in picture space.
1359        let size = clip_chain.pic_coverage_rect.size();
1360        size.width > MIN_QUAD_SPLIT_SIZE || size.height > MIN_QUAD_SPLIT_SIZE
1361    } else {
1362        false
1363    };
1364
1365    if !try_split_prim {
1366        return QuadRenderStrategy::Indirect;
1367    }
1368
1369    if prim_is_scale_offset && clip_chain.clips_range.count == 1 {
1370        let clip_instance = clip_store.get_instance_from_range(&clip_chain.clips_range, 0);
1371        let clip_node = &interned_clips[clip_instance.handle];
1372
1373        if let ClipItemKind::RoundedRectangle { ref radius, mode: ClipMode::Clip, .. } = clip_node.item.kind {
1374            let size = clip_instance.clip_rect.size();
1375            let radius = clamped_radius(radius, size);
1376            let max_corner_width = radius.top_left.width
1377                                        .max(radius.bottom_left.width)
1378                                        .max(radius.top_right.width)
1379                                        .max(radius.bottom_right.width);
1380            let max_corner_height = radius.top_left.height
1381                                        .max(radius.bottom_left.height)
1382                                        .max(radius.top_right.height)
1383                                        .max(radius.bottom_right.height);
1384
1385            if max_corner_width <= 0.5 * size.width &&
1386                max_corner_height <= 0.5 * size.height {
1387
1388                let clip_prim_coords_match = spatial_tree.is_matching_coord_system(
1389                    prim_spatial_node_index,
1390                    clip_instance.spatial_node_index,
1391                );
1392
1393                if clip_prim_coords_match {
1394                    let map_clip_to_prim = SpaceMapper::new_with_target(
1395                        prim_spatial_node_index,
1396                        clip_instance.spatial_node_index,
1397                        LayoutRect::max_rect(),
1398                        spatial_tree,
1399                    );
1400
1401                    if let Some(clip_rect) = map_clip_to_prim.map(&clip_instance.clip_rect) {
1402                        let radius = map_clip_to_prim.map_vector(
1403                            LayoutVector2D::new(max_corner_width, max_corner_height)
1404                        );
1405                        return QuadRenderStrategy::NinePatch {
1406                            radius,
1407                            clip_rect,
1408                        };
1409                    }
1410                }
1411            }
1412        }
1413    }
1414
1415    QuadRenderStrategy::Tiled
1416}
1417
1418/// Adjust the transform and device rect until the latter fits the provided
1419/// maximum size.
1420/// Also ensure that near-zero size tasks do are at least
1421fn adjust_indirect_pattern_resolution(
1422    local_rect: &LayoutRect,
1423    max_device_size: f32,
1424    device_rect: &mut DeviceRect,
1425    indirect_transform: &mut ScaleOffset,
1426) {
1427    // This catches invalid cases such as NaNs or zeroes that would have caused us
1428    // to loop forever.
1429    let valid = local_rect.width() > 0.0
1430        && local_rect.height() > 0.0
1431        && indirect_transform.scale.x != 0.0
1432        && indirect_transform.scale.y != 0.0;
1433
1434    if !valid {
1435        return;
1436    }
1437
1438    // Down-scale until the render task fits in the provided maximum size.
1439    while device_rect.width() > max_device_size {
1440        indirect_transform.scale.x *= 0.5;
1441        *device_rect = indirect_transform.map_rect(local_rect);
1442    }
1443    while device_rect.height() > max_device_size {
1444        indirect_transform.scale.y *= 0.5;
1445        *device_rect = indirect_transform.map_rect(local_rect);
1446    }
1447
1448    // Up-scale until the render task size rounds to at least one pixel.
1449    while device_rect.width() <= 0.5 {
1450        indirect_transform.scale.x *= 2.0;
1451        *device_rect = indirect_transform.map_rect(local_rect);
1452    }
1453    while device_rect.height() <= 0.5 {
1454        indirect_transform.scale.y *= 2.0;
1455        *device_rect = indirect_transform.map_rect(local_rect);
1456    }
1457}
1458
1459pub fn cache_key(
1460    prim_uid: ItemUid,
1461    transform: &QuadTransformState,
1462    clip_chain: &ClipChainInstance,
1463    clip_store: &ClipStore,
1464) -> Option<QuadCacheKey> {
1465    const CACHE_MAX_CLIPS: usize = 3;
1466
1467    if (clip_chain.clips_range.count as usize) >= CACHE_MAX_CLIPS {
1468        return None;
1469    }
1470
1471    let prim_spatial_node_index = transform.prim_spatial_node_index();
1472    // The assumption is here is that the vast majority of transforms
1473    // are 2d scale offsets and that 3d ones tend to be animated, so
1474    // in order to keep the key small, we only attempt to cache when
1475    // the transform is a 2d scale offset.
1476    // This will miss some caching opportunities, but they should
1477    // hopefully be rare.
1478    let Some(transform) = transform.as_2d_scale_offset() else {
1479        return None;
1480    };
1481
1482    let mut clip_uids = [!0; CACHE_MAX_CLIPS];
1483
1484    for i in 0 .. clip_chain.clips_range.count {
1485        let clip_instance = clip_store.get_instance_from_range(&clip_chain.clips_range, i);
1486        clip_uids[i as usize] = clip_instance.handle.uid().get_uid();
1487        if clip_instance.spatial_node_index != prim_spatial_node_index {
1488            return None;
1489        }
1490    }
1491
1492    Some(QuadCacheKey {
1493        prim: prim_uid.get_uid(),
1494        clips: clip_uids,
1495        transform: [
1496            transform.scale.x.to_bits(),
1497            transform.scale.y.to_bits(),
1498            transform.offset.x.to_bits(),
1499            transform.offset.y.to_bits(),
1500        ],
1501    })
1502}
1503
1504fn add_render_task_with_mask(
1505    pattern: &Pattern,
1506    prim_local_coverage_rect: &LayoutRect,
1507    task_size: DeviceIntSize,
1508    content_origin: DevicePoint,
1509    clips_range: ClipNodeRange,
1510    prim_spatial_node_index: SpatialNodeIndex,
1511    raster_spatial_node_index: SpatialNodeIndex,
1512    prim_address_f: GpuBufferAddress,
1513    transform_id: GpuTransformId,
1514    aa_flags: EdgeMask,
1515    quad_flags: QuadFlags,
1516    device_pixel_scale: DevicePixelScale,
1517    needs_scissor_rect: bool,
1518    cache_key: Option<&RenderTaskCacheKey>,
1519    spatial_tree: &SpatialTree,
1520    interned_clips: &DataStore<ClipIntern>,
1521    frame_state: &mut FrameBuildingState,
1522) -> RenderTaskId {
1523    let transforms = &mut frame_state.transforms;
1524    let clip_store = &frame_state.clip_store;
1525    let is_opaque = pattern.is_opaque && clips_range.count == 0;
1526    frame_state.resource_cache.request_render_task(
1527        cache_key.cloned(),
1528        is_opaque,
1529        RenderTaskParent::Surface,
1530        &mut frame_state.frame_gpu_data.f32,
1531        frame_state.rg_builder,
1532        &mut frame_state.surface_builder,
1533        &mut|rg_builder, gpu_buffer| {
1534            let task_id = rg_builder.add().init(RenderTask::new_dynamic(
1535                task_size,
1536                RenderTaskKind::new_prim(
1537                    pattern.kind,
1538                    pattern.shader_input,
1539                    content_origin,
1540                    prim_address_f,
1541                    transform_id,
1542                    aa_flags,
1543                    quad_flags,
1544                    needs_scissor_rect,
1545                    pattern.texture_input.task_id,
1546                ),
1547            ));
1548
1549            // If the pattern samples from a texture, add it as a dependency
1550            // of the indirect render task that relies on it.
1551            if pattern.texture_input.task_id != RenderTaskId::INVALID {
1552                rg_builder.add_dependency(task_id, pattern.texture_input.task_id);
1553            }
1554
1555            if clips_range.count > 0 {
1556                let task_rect = DeviceRect::from_origin_and_size(
1557                    content_origin,
1558                    task_size.to_f32(),
1559                );
1560
1561                prepare_clip_range(
1562                    clips_range,
1563                    task_id,
1564                    &task_rect,
1565                    prim_local_coverage_rect,
1566                    prim_spatial_node_index,
1567                    raster_spatial_node_index,
1568                    device_pixel_scale,
1569                    interned_clips,
1570                    clip_store,
1571                    spatial_tree,
1572                    rg_builder,
1573                    gpu_buffer,
1574                    transforms,
1575                );
1576            }
1577
1578            task_id
1579        }
1580    )
1581}
1582
1583fn add_pattern_prim(
1584    pattern: &Pattern,
1585    pattern_transform: ScaleOffset,
1586    prim_instance_index: PrimitiveInstanceIndex,
1587    rect: &DeviceRect,
1588    clip_rect: &DeviceRect,
1589    is_opaque: bool,
1590    frame_state: &mut FrameBuildingState,
1591    targets: &[CommandBufferIndex],
1592    segments: &[QuadSegment],
1593) {
1594    let prim_address = write_device_prim_blocks(
1595        &mut frame_state.frame_gpu_data.f32,
1596        rect,
1597        clip_rect,
1598        pattern.base_color,
1599        pattern.texture_input.task_id,
1600        segments,
1601        pattern_transform,
1602    );
1603
1604    frame_state.set_segments(segments, targets);
1605
1606    let mut quad_flags = QuadFlags::APPLY_RENDER_TASK_CLIP;
1607
1608    if is_opaque {
1609        quad_flags |= QuadFlags::IS_OPAQUE;
1610    }
1611
1612    frame_state.push_cmd(
1613        &PrimitiveCommand::quad(
1614            pattern.kind,
1615            pattern.shader_input,
1616            pattern.texture_input.task_id,
1617            crate::prim_store::storage::Index::from_u32(prim_instance_index.0),
1618            prim_address,
1619            GpuTransformId::IDENTITY,
1620            quad_flags,
1621            // TODO(gw): No AA on composite, unless we use it to apply 2d clips
1622            EdgeMask::empty(),
1623            pattern.blend_mode,
1624        ),
1625        targets,
1626    );
1627}
1628
1629fn add_composite_prim(
1630    blend_mode: BlendMode,
1631    prim_instance_index: PrimitiveInstanceIndex,
1632    rect: &DeviceRect,
1633    frame_state: &mut FrameBuildingState,
1634    targets: &[CommandBufferIndex],
1635    segments: &[QuadSegment],
1636) {
1637    assert!(!segments.is_empty());
1638
1639    // Note: At the primitive level we specify an invalid task ID here, which
1640    // may look suspicious since we are using the textured shader. However each
1641    // segment comes with its own render task id, and that's what the batching
1642    // code uses.
1643
1644    let composite_prim_address = write_device_prim_blocks(
1645        &mut frame_state.frame_gpu_data.f32,
1646        rect,
1647        rect,
1648        ColorF::WHITE,
1649        RenderTaskId::INVALID,
1650        segments,
1651        ScaleOffset::identity(),
1652    );
1653
1654    frame_state.set_segments(segments, targets);
1655
1656    let quad_flags = QuadFlags::APPLY_RENDER_TASK_CLIP;
1657
1658    frame_state.push_cmd(
1659        &PrimitiveCommand::quad(
1660            PatternKind::ColorOrTexture,
1661            PatternShaderInput(
1662                crate::pattern::TEXTURED_SHADER_MODE_TEXTURE,
1663                crate::pattern::TEXTURED_SHADER_MAP_TO_SEGMENT,
1664            ),
1665            RenderTaskId::INVALID,
1666            crate::prim_store::storage::Index::from_u32(prim_instance_index.0),
1667            composite_prim_address,
1668            GpuTransformId::IDENTITY,
1669            quad_flags,
1670            // TODO(gw): No AA on composite, unless we use it to apply 2d clips
1671            EdgeMask::empty(),
1672            blend_mode,
1673        ),
1674        targets,
1675    );
1676}
1677
1678pub fn prepare_clip_range(
1679    clips_range: ClipNodeRange,
1680    masked_prim_task_id: RenderTaskId,
1681    task_rect: &DeviceRect,
1682    prim_local_coverage_rect: &LayoutRect,
1683    prim_spatial_node_index: SpatialNodeIndex,
1684    raster_spatial_node_index: SpatialNodeIndex,
1685    device_pixel_scale: DevicePixelScale,
1686    interned_clips: &DataStore<ClipIntern>,
1687    clip_store: &ClipStore,
1688    spatial_tree: &SpatialTree,
1689    rg_builder: &mut RenderTaskGraphBuilder,
1690    gpu_buffer: &mut GpuBufferBuilderF,
1691    transforms: &mut TransformPalette,
1692) {
1693    let mut sub_tasks = rg_builder.begin_sub_tasks();
1694
1695    for i in 0 .. clips_range.count {
1696        let clip_instance = clip_store.get_instance_from_range(&clips_range, i);
1697        let clip_item = &interned_clips[clip_instance.handle].item;
1698
1699        prepare_clip_task(
1700            clip_instance,
1701            clip_item,
1702            task_rect,
1703            prim_local_coverage_rect,
1704            prim_spatial_node_index,
1705            raster_spatial_node_index,
1706            device_pixel_scale,
1707            clip_store,
1708            spatial_tree,
1709            gpu_buffer,
1710            transforms,
1711            rg_builder,
1712            &mut sub_tasks,
1713        );
1714    }
1715
1716    rg_builder
1717        .get_task_mut(masked_prim_task_id)
1718        .set_sub_tasks(sub_tasks);
1719}
1720
1721pub fn prepare_clip_task(
1722    clip_instance: &ClipNodeInstance,
1723    clip_item: &ClipItem,
1724    task_rect: &DeviceRect,
1725    prim_local_coverage_rect: &LayoutRect,
1726    prim_spatial_node_index: SpatialNodeIndex,
1727    raster_spatial_node_index: SpatialNodeIndex,
1728    device_pixel_scale: DevicePixelScale,
1729    clip_store: &ClipStore,
1730    spatial_tree: &SpatialTree,
1731    gpu_buffer: &mut GpuBufferBuilderF,
1732    transforms: &mut TransformPalette,
1733    rg_builder: &mut RenderTaskGraphBuilder,
1734    sub_tasks: &mut SubTaskRange,
1735) {
1736    let (clip_address, fast_path) = match clip_item.kind {
1737        ClipItemKind::RoundedRectangle { radius, mode } => {
1738            let radius = clamped_radius(&radius, clip_instance.clip_rect.size());
1739            let (fast_path, clip_address) = if radius.can_use_fast_path_in(&clip_instance.clip_rect) {
1740                let mut writer = gpu_buffer.write_blocks(3);
1741                writer.push_one(clip_instance.clip_rect);
1742                writer.push_one([
1743                    radius.bottom_right.width,
1744                    radius.top_right.width,
1745                    radius.bottom_left.width,
1746                    radius.top_left.width,
1747                ]);
1748                writer.push_one([mode as i32 as f32, 0.0, 0.0, 0.0]);
1749                let clip_address = writer.finish();
1750
1751                (true, clip_address)
1752            } else {
1753                let mut writer = gpu_buffer.write_blocks(4);
1754                writer.push_one(clip_instance.clip_rect);
1755                writer.push_one([
1756                    radius.top_left.width,
1757                    radius.top_left.height,
1758                    radius.top_right.width,
1759                    radius.top_right.height,
1760                ]);
1761                writer.push_one([
1762                    radius.bottom_left.width,
1763                    radius.bottom_left.height,
1764                    radius.bottom_right.width,
1765                    radius.bottom_right.height,
1766                ]);
1767                writer.push_one([mode as i32 as f32, 0.0, 0.0, 0.0]);
1768                let clip_address = writer.finish();
1769
1770                (false, clip_address)
1771            };
1772
1773            (clip_address, fast_path)
1774        }
1775        ClipItemKind::Rectangle { mode, .. } => {
1776            let mut writer = gpu_buffer.write_blocks(3);
1777            writer.push_one(clip_instance.clip_rect);
1778            writer.push_one([0.0, 0.0, 0.0, 0.0]);
1779            writer.push_one([mode as i32 as f32, 0.0, 0.0, 0.0]);
1780            let clip_address = writer.finish();
1781
1782            (clip_address, true)
1783        }
1784        ClipItemKind::Image { .. } => {
1785            let transform_id = transforms.gpu.get_id_with_post_scale(
1786                clip_instance.spatial_node_index,
1787                raster_spatial_node_index,
1788                device_pixel_scale.get(),
1789                spatial_tree,
1790            );
1791
1792            let is_scale_offset = transform_id.is_2d_scale_offset();
1793            let needs_scissor_rect = !is_scale_offset;
1794
1795            let pattern = Pattern::color(ColorF::WHITE);
1796            let mut quad_flags = QuadFlags::IS_MASK;
1797
1798            if is_scale_offset {
1799                quad_flags |= QuadFlags::APPLY_RENDER_TASK_CLIP;
1800            }
1801
1802            for tile in clip_store.visible_mask_tiles(&clip_instance) {
1803                let prim_address = write_layout_prim_blocks(
1804                    gpu_buffer,
1805                    &tile.tile_rect,
1806                    &tile.tile_rect,
1807                    pattern.base_color,
1808                    pattern.texture_input.task_id,
1809                    &[QuadSegment {
1810                        rect: tile.tile_rect.to_untyped(),
1811                        task_id: tile.task_id,
1812                    }],
1813                );
1814
1815                rg_builder.push_sub_task(
1816                    sub_tasks,
1817                    SubTask::ImageClip(ImageClipSubTask {
1818                        quad_address: prim_address,
1819                        quad_transform_id: transform_id,
1820                        src_task: tile.task_id,
1821                        quad_flags,
1822                        needs_scissor_rect,
1823                    }),
1824                );
1825            }
1826
1827            // TODO(gw): For now, we skip the main mask prim below for image masks. Perhaps
1828            //           we can better merge the logic together?
1829            // TODO(gw): How to efficiently handle if the image-mask rect doesn't cover local prim rect?
1830            return;
1831        }
1832    };
1833
1834    let clip_spatial_node = spatial_tree.get_spatial_node(clip_instance.spatial_node_index);
1835    let raster_spatial_node = spatial_tree.get_spatial_node(raster_spatial_node_index);
1836    let raster_clip = raster_spatial_node.coordinate_system_id == clip_spatial_node.coordinate_system_id;
1837
1838    // See the documentation of RectangleClipSubTask::clip_space.
1839    let (clip_space, clip_transform_id, quad_address, quad_transform_id, is_same_coord_system) = if raster_clip {
1840        let quad_transform_id = GpuTransformId::IDENTITY;
1841        let pattern = Pattern::color(ColorF::WHITE);
1842
1843        // TODO: This transform could be set to identity in favor of using the
1844        // pattern transform which serves the same purpose and is cheaper since
1845        // is a scale-offset. In this code path the raster-to-clip transform is
1846        // guaranteed to be representable by a scale and offset.
1847        let clip_transform_id = transforms.gpu.get_id_with_pre_scale(
1848            device_pixel_scale.inverse().get(),
1849            raster_spatial_node_index,
1850            clip_instance.spatial_node_index,
1851            spatial_tree,
1852        );
1853        let pattern_transform = ScaleOffset::identity();
1854
1855        let quad_address = write_device_prim_blocks(
1856            gpu_buffer,
1857            &task_rect,
1858            &task_rect,
1859            pattern.base_color,
1860            pattern.texture_input.task_id,
1861            &[],
1862            pattern_transform,
1863        );
1864
1865        (ClipSpace::Device, clip_transform_id, quad_address, quad_transform_id, true)
1866    } else {
1867        let prim_spatial_node = spatial_tree.get_spatial_node(prim_spatial_node_index);
1868
1869        let quad_transform_id = transforms.gpu.get_id_with_post_scale(
1870            prim_spatial_node_index,
1871            raster_spatial_node_index,
1872            device_pixel_scale.get(),
1873            spatial_tree,
1874        );
1875
1876        // Conservatively inflate the clip's primitive to ensure that it covers potential
1877        // anti-aliasing pixels of the original primitive. 2.0 matches AA_PIXEL_RADIUS in
1878        // quad.glsl.
1879        let rect = prim_local_coverage_rect.inflate(2.0, 2.0);
1880
1881        let quad_address = write_layout_prim_blocks(
1882            gpu_buffer,
1883            &rect,
1884            &rect,
1885            ColorF::WHITE,
1886            RenderTaskId::INVALID,
1887            &[],
1888        );
1889
1890        let clip_spatial_node = spatial_tree.get_spatial_node(clip_instance.spatial_node_index);
1891        let clip_transform_id = if prim_spatial_node.coordinate_system_id < clip_spatial_node.coordinate_system_id {
1892            transforms.gpu.get_id(
1893                clip_instance.spatial_node_index,
1894                prim_spatial_node_index,
1895                spatial_tree,
1896            )
1897        } else {
1898            transforms.gpu.get_id(
1899                prim_spatial_node_index,
1900                clip_instance.spatial_node_index,
1901                spatial_tree,
1902            )
1903        };
1904
1905        let is_same_coord_system = spatial_tree.is_matching_coord_system(
1906            prim_spatial_node_index,
1907            raster_spatial_node_index,
1908        );
1909
1910        (ClipSpace::Primitive, clip_transform_id, quad_address, quad_transform_id, is_same_coord_system)
1911    };
1912
1913    let needs_scissor_rect = !is_same_coord_system;
1914
1915    let quad_flags = if is_same_coord_system {
1916        QuadFlags::APPLY_RENDER_TASK_CLIP
1917    } else {
1918        QuadFlags::empty()
1919    };
1920
1921
1922    rg_builder.push_sub_task(
1923        sub_tasks,
1924        SubTask::RectangleClip(RectangleClipSubTask {
1925            quad_address,
1926            quad_transform_id,
1927            clip_address,
1928            clip_transform_id,
1929            quad_flags,
1930            clip_space,
1931            needs_scissor_rect,
1932            rounded_rect_fast_path: fast_path,
1933        }),
1934    );
1935}
1936
1937fn create_quad_primitive(
1938    local_rect: &LayoutRect,
1939    local_clip_rect: &LayoutRect,
1940    device_clip_rect: &DeviceRect,
1941    local_to_device: Option<&ScaleOffset>,
1942    round_edges: EdgeMask,
1943    pattern: &Pattern,
1944) -> QuadPrimitive {
1945    let mut prim_rect;
1946    let mut prim_clip_rect;
1947    let pattern_transform;
1948    if let Some(local_to_device) = local_to_device {
1949        prim_rect = local_to_device.map_rect(local_rect);
1950        prim_clip_rect = local_to_device
1951                .map_rect(&local_clip_rect)
1952                .intersection_unchecked(device_clip_rect)
1953                .to_untyped();
1954        prim_rect = round_edges.select(prim_rect.round(), prim_rect);
1955        prim_clip_rect = round_edges.select(prim_clip_rect.round(), prim_clip_rect);
1956
1957        pattern_transform = local_to_device.inverse();
1958    } else {
1959        prim_rect = local_rect.to_untyped();
1960        prim_clip_rect = local_clip_rect.to_untyped();
1961        pattern_transform = ScaleOffset::identity();
1962    };
1963
1964    QuadPrimitive {
1965        bounds: prim_rect,
1966        clip: prim_clip_rect,
1967        input_task: pattern.texture_input.task_id,
1968        pattern_scale_offset: pattern_transform,
1969        color: pattern.base_color.premultiplied(),
1970    }
1971}
1972
1973/// Write the GPU blocks, either in local or device space
1974///
1975/// If a local-to-device transform is provided, then the
1976/// primitive is written in device space, otherwise it is
1977/// written in layout space.
1978fn write_prim_blocks(
1979    builder: &mut GpuBufferBuilderF,
1980    local_rect: &LayoutRect,
1981    local_clip_rect: &LayoutRect,
1982    device_clip_rect: &DeviceRect,
1983    local_to_device: Option<&ScaleOffset>,
1984    round_edges: EdgeMask,
1985    pattern: &Pattern,
1986) -> GpuBufferAddress {
1987    let mut prim_rect;
1988    let mut prim_clip_rect;
1989    let pattern_transform;
1990    if let Some(local_to_device) = local_to_device {
1991        prim_rect = local_to_device.map_rect(&local_rect);
1992        prim_clip_rect = local_to_device
1993                .map_rect(&local_clip_rect)
1994                .intersection_unchecked(&device_clip_rect)
1995                .to_untyped();
1996        prim_rect = round_edges.select(prim_rect.round(), prim_rect);
1997        prim_clip_rect = round_edges.select(prim_rect.round(), prim_clip_rect);
1998        pattern_transform = local_to_device.inverse();
1999    } else {
2000        prim_rect = local_rect.to_untyped();
2001        prim_clip_rect = local_clip_rect.to_untyped();
2002        pattern_transform = ScaleOffset::identity();
2003    };
2004
2005    write_prim_blocks_impl(
2006        builder,
2007        prim_rect,
2008        prim_clip_rect,
2009        pattern.base_color,
2010        pattern.texture_input.task_id,
2011        &[],
2012        pattern_transform,
2013    )
2014}
2015
2016/// Write the gpu blocks for a primitive in device space.
2017pub fn write_device_prim_blocks(
2018    builder: &mut GpuBufferBuilderF,
2019    prim_rect: &DeviceRect,
2020    clip_rect: &DeviceRect,
2021    pattern_base_color: ColorF,
2022    pattern_texture_input: RenderTaskId,
2023    segments: &[QuadSegment],
2024    pattern_scale_offset: ScaleOffset,
2025) -> GpuBufferAddress {
2026    write_prim_blocks_impl(
2027        builder,
2028        prim_rect.to_untyped(),
2029        clip_rect.to_untyped(),
2030        pattern_base_color,
2031        pattern_texture_input,
2032        segments,
2033        pattern_scale_offset
2034    )
2035}
2036
2037/// Write the gpu blocks for a primitive in layout space.
2038pub fn write_layout_prim_blocks(
2039    builder: &mut GpuBufferBuilderF,
2040    prim_rect: &LayoutRect,
2041    clip_rect: &LayoutRect,
2042    pattern_base_color: ColorF,
2043    pattern_texture_input: RenderTaskId,
2044    segments: &[QuadSegment],
2045) -> GpuBufferAddress {
2046    write_prim_blocks_impl(
2047        builder,
2048        prim_rect.to_untyped(),
2049        clip_rect.to_untyped(),
2050        pattern_base_color,
2051        pattern_texture_input,
2052        segments,
2053        ScaleOffset::identity(),
2054    )
2055}
2056
2057fn write_prim_blocks_impl(
2058    builder: &mut GpuBufferBuilderF,
2059    prim_rect: LayoutOrDeviceRect,
2060    clip_rect: LayoutOrDeviceRect,
2061    pattern_base_color: ColorF,
2062    pattern_texture_input: RenderTaskId,
2063    segments: &[QuadSegment],
2064    pattern_scale_offset: ScaleOffset,
2065) -> GpuBufferAddress {
2066    let mut writer = builder.write_blocks(5 + segments.len() * 2);
2067
2068    writer.push(&QuadPrimitive {
2069        bounds: prim_rect,
2070        clip: clip_rect,
2071        input_task: pattern_texture_input,
2072        pattern_scale_offset,
2073        color: pattern_base_color.premultiplied(),
2074    });
2075
2076    for segment in segments {
2077        writer.push(segment);
2078    }
2079
2080    writer.finish()
2081}
2082
2083pub fn add_to_batch<F>(
2084    kind: PatternKind,
2085    pattern_input: PatternShaderInput,
2086    dst_task_address: RenderTaskAddress,
2087    transform_id: GpuTransformId,
2088    prim_address_f: GpuBufferAddress,
2089    quad_flags: QuadFlags,
2090    edge_flags: EdgeMask,
2091    segment_index: u8,
2092    src_task_id: RenderTaskId,
2093    z_id: ZBufferId,
2094    blend_mode: BlendMode,
2095    render_tasks: &RenderTaskGraph,
2096    gpu_buffer_builder: &mut GpuBufferBuilder,
2097    mut f: F,
2098) where F: FnMut(BatchKey, PrimitiveInstanceData) {
2099
2100    // See the corresponfing #defines in ps_quad.glsl
2101    #[repr(u8)]
2102    enum PartIndex {
2103        Center = 0,
2104        Left = 1,
2105        Top = 2,
2106        Right = 3,
2107        Bottom = 4,
2108        All = 5,
2109    }
2110
2111    let texture = match src_task_id {
2112        RenderTaskId::INVALID => TextureSource::Invalid,
2113        _ =>  match render_tasks.resolve_texture(src_task_id) {
2114            Some(texture) => texture,
2115            None => {
2116                // If a valid render task does not yield a texture source, render
2117                // nothing. This can happen, for example when a stacking context
2118                // could not be snapshotted.
2119                return;
2120            },
2121        }
2122    };
2123
2124
2125    // See QuadHeader in ps_quad.glsl
2126    let mut writer = gpu_buffer_builder.i32.write_blocks(QuadHeader::NUM_BLOCKS);
2127    writer.push(&QuadHeader {
2128        transform_id,
2129        z_id,
2130        pattern_input,
2131    });
2132    let prim_address_i = writer.finish();
2133
2134    let textures = BatchTextures::prim_textured(
2135        texture,
2136        TextureSource::Invalid,
2137    );
2138
2139    let prim_blend_mode = if quad_flags.contains(QuadFlags::IS_OPAQUE)
2140        && blend_mode == BlendMode::PremultipliedAlpha
2141    {
2142        BlendMode::None
2143    } else {
2144        blend_mode
2145    };
2146
2147    let edge_flags_bits = edge_flags.bits();
2148
2149    let prim_batch_key = BatchKey {
2150        blend_mode: prim_blend_mode,
2151        kind: BatchKind::Quad(kind),
2152        textures,
2153    };
2154
2155    let aa_batch_key = BatchKey {
2156        blend_mode,
2157        kind: BatchKind::Quad(kind),
2158        textures,
2159    };
2160
2161    let mut instance = QuadInstance {
2162        dst_task_address,
2163        prim_address_i: prim_address_i.as_int(),
2164        prim_address_f: prim_address_f.as_int(),
2165        edge_flags: edge_flags_bits,
2166        quad_flags: quad_flags.bits(),
2167        part_index: PartIndex::All as u8,
2168        segment_index,
2169    };
2170
2171    if edge_flags.is_empty() {
2172        // No antialisaing.
2173        f(prim_batch_key, instance.into());
2174    } else if quad_flags.contains(QuadFlags::USE_AA_SEGMENTS) {
2175        // Add instances for the antialisaing. This gives the center part
2176        // an opportunity to stay in the opaque pass.
2177        if edge_flags.contains(EdgeMask::LEFT) {
2178            let instance = QuadInstance {
2179                part_index: PartIndex::Left as u8,
2180                ..instance
2181            };
2182            f(aa_batch_key, instance.into());
2183        }
2184        if edge_flags.contains(EdgeMask::TOP) {
2185            let instance = QuadInstance {
2186                part_index: PartIndex::Top as u8,
2187                ..instance
2188            };
2189            f(aa_batch_key, instance.into());
2190        }
2191        if edge_flags.contains(EdgeMask::RIGHT) {
2192            let instance = QuadInstance {
2193                part_index: PartIndex::Right as u8,
2194                ..instance
2195            };
2196            f(aa_batch_key, instance.into());
2197        }
2198        if edge_flags.contains(EdgeMask::BOTTOM) {
2199            let instance = QuadInstance {
2200                part_index: PartIndex::Bottom as u8,
2201                ..instance
2202            };
2203            f(aa_batch_key, instance.into());
2204        }
2205
2206        instance = QuadInstance {
2207            part_index: PartIndex::Center as u8,
2208            ..instance
2209        };
2210
2211        f(prim_batch_key, instance.into());
2212    } else {
2213        // Render the anti-aliased quad with a single primitive.
2214        f(aa_batch_key, instance.into());
2215    }
2216}
2217
2218/// Classification result for a tile within a quad
2219#[allow(dead_code)]
2220#[cfg_attr(feature = "capture", derive(Serialize))]
2221#[derive(Debug, Copy, Clone, PartialEq)]
2222pub enum QuadTileKind {
2223    // Clipped out - can be skipped
2224    Clipped,
2225    // Requires the pattern only, can draw directly
2226    Pattern {
2227        has_mask: bool,
2228    },
2229}
2230
2231#[cfg_attr(feature = "capture", derive(Serialize))]
2232#[derive(Copy, Clone, Debug)]
2233pub struct QuadTileInfo {
2234    pub rect: DeviceRect,
2235    pub kind: QuadTileKind,
2236}
2237
2238impl Default for QuadTileInfo {
2239    fn default() -> Self {
2240        QuadTileInfo {
2241            rect: DeviceRect::zero(),
2242            kind: QuadTileKind::Pattern { has_mask: false },
2243        }
2244    }
2245}
2246
2247/// A helper struct for classifying a set of tiles within a quad depending on
2248/// what strategy they can be used to draw them.
2249#[cfg_attr(feature = "capture", derive(Serialize))]
2250pub struct QuadTileClassifier {
2251    buffer: [QuadTileInfo; MAX_TILES_PER_QUAD_X * MAX_TILES_PER_QUAD_Y],
2252    mask_regions: Vec<DeviceRect>,
2253    clip_in_regions: Vec<DeviceRect>,
2254    clip_out_regions: Vec<DeviceRect>,
2255    rect: DeviceRect,
2256    x_tiles: usize,
2257    y_tiles: usize,
2258    // Treat all tiles that have some coverage as masked.
2259    force_masks: bool,
2260}
2261
2262impl QuadTileClassifier {
2263    pub fn new() -> Self {
2264        QuadTileClassifier {
2265            buffer: [QuadTileInfo::default(); MAX_TILES_PER_QUAD_X * MAX_TILES_PER_QUAD_Y],
2266            mask_regions: Vec::new(),
2267            clip_in_regions: Vec::new(),
2268            clip_out_regions: Vec::new(),
2269            rect: DeviceRect::zero(),
2270            x_tiles: 0,
2271            y_tiles: 0,
2272            force_masks: false,
2273        }
2274    }
2275
2276    pub fn reset(
2277        &mut self,
2278        rect: DeviceRect,
2279        force_masks: bool,
2280    ) {
2281        let x_tiles = (rect.width() / MIN_QUAD_SPLIT_SIZE)
2282            .min(MAX_TILES_PER_QUAD_X as f32)
2283            .max(1.0)
2284            .ceil() as usize;
2285        let y_tiles = (rect.width() / MIN_QUAD_SPLIT_SIZE)
2286            .min(MAX_TILES_PER_QUAD_Y as f32)
2287            .max(1.0)
2288            .ceil() as usize;
2289
2290        self.x_tiles = x_tiles;
2291        self.y_tiles = y_tiles;
2292        self.rect = rect;
2293        self.force_masks = force_masks;
2294        self.mask_regions.clear();
2295        self.clip_in_regions.clear();
2296        self.clip_out_regions.clear();
2297
2298        // TODO(gw): Might be some f32 accuracy issues with how we construct these,
2299        //           should be more robust here...
2300
2301        let dx = rect.width() / x_tiles as f32;
2302        let dy = rect.height() / y_tiles as f32;
2303
2304        let mut y0 = rect.min.y;
2305
2306        for y in 0 .. y_tiles {
2307            let y1 = if y == y_tiles - 1 {
2308                rect.max.y
2309            } else {
2310                (rect.min.y + (y + 1) as f32 * dy).round()
2311            };
2312
2313            let mut x0 = rect.min.x;
2314            for x in 0 .. x_tiles {
2315                let info = &mut self.buffer[y * x_tiles + x];
2316
2317                let x1 = if x == x_tiles - 1 {
2318                    rect.max.x
2319                } else {
2320                    (rect.min.x + (x + 1) as f32 * dx).round()
2321                };
2322
2323                let p0 = DevicePoint::new(x0, y0);
2324                let p1 = DevicePoint::new(x1, y1);
2325                info.rect = DeviceRect::new(p0, p1);
2326                info.kind = QuadTileKind::Pattern { has_mask: force_masks };
2327
2328                x0 = x1;
2329            }
2330
2331            y0 = y1;
2332        }
2333    }
2334
2335    /// Add an area that needs a clip mask / indirect area
2336    pub fn add_mask_region(
2337        &mut self,
2338        mask_region: DeviceRect,
2339    ) {
2340        if !mask_region.is_empty() {
2341            self.mask_regions.push(mask_region);
2342        }
2343    }
2344
2345    // TODO(gw): Make use of this to skip tiles that are completely clipped out in a follow up!
2346    pub fn add_clip_rect(
2347        &mut self,
2348        clip_rect: DeviceRect,
2349        clip_mode: ClipMode,
2350    ) {
2351        match clip_mode {
2352            ClipMode::Clip => {
2353                self.clip_in_regions.push(clip_rect);
2354            }
2355            ClipMode::ClipOut => {
2356                self.clip_out_regions.push(clip_rect);
2357
2358                self.add_mask_region(self.rect);
2359            }
2360        }
2361    }
2362
2363    /// Classify all the tiles in to categories, based on the provided masks and clip regions
2364    pub fn classify(
2365        &mut self,
2366    ) -> QuadTileIterator {
2367        assert_ne!(self.x_tiles, 0);
2368        assert_ne!(self.y_tiles, 0);
2369
2370        let tile_count = self.x_tiles * self.y_tiles;
2371        let tiles = &mut self.buffer[0 .. tile_count];
2372
2373        for info in tiles.iter_mut() {
2374            // If a clip region contains the entire tile, it's clipped
2375            for clip_region in &self.clip_in_regions {
2376                match info.kind {
2377                    QuadTileKind::Clipped => {},
2378                    QuadTileKind::Pattern { .. } => {
2379                        if !clip_region.intersects(&info.rect) {
2380                            info.kind = QuadTileKind::Clipped;
2381                        }
2382                    }
2383                }
2384
2385            }
2386
2387            // If a tile doesn't intersect with a clip-out region, it's clipped
2388            for clip_region in &self.clip_out_regions {
2389                match info.kind {
2390                    QuadTileKind::Clipped => {},
2391                    QuadTileKind::Pattern { .. } => {
2392                        if clip_region.contains_box(&info.rect) {
2393                            info.kind = QuadTileKind::Clipped;
2394                        }
2395                    }
2396                }
2397            }
2398
2399            // If a tile intersects with a mask region, and isn't clipped, it needs a mask
2400            for mask_region in &self.mask_regions {
2401                match info.kind {
2402                    QuadTileKind::Clipped | QuadTileKind::Pattern { has_mask: true, .. } => {},
2403                    QuadTileKind::Pattern { ref mut has_mask, .. } => {
2404                        if mask_region.intersects(&info.rect) {
2405                            *has_mask = true;
2406                        }
2407                    }
2408                }
2409            }
2410        }
2411
2412        self.x_tiles = 0;
2413        self.y_tiles = 0;
2414
2415        QuadTileIterator { tiles }
2416    }
2417}
2418
2419pub struct QuadTileIterator<'l> {
2420    tiles: &'l[QuadTileInfo],
2421}
2422
2423impl<'l> Iterator for QuadTileIterator<'l> {
2424    type Item = QuadTileInfo;
2425    fn next(&mut self) -> Option<QuadTileInfo> {
2426        if self.tiles.is_empty() {
2427            return None;
2428        }
2429
2430        let mut tile = self.tiles[0];
2431        self.tiles = &self.tiles[1..];
2432
2433        // Skip over empty tiles
2434        while tile.kind == QuadTileKind::Clipped {
2435            tile = *self.tiles.first()?;
2436            self.tiles = &self.tiles[1..];
2437        }
2438
2439        // Merge consecutive compatible tiles.
2440        // This reduces some of the per-tile overhead both on CPU and GPU, especially
2441        // with SWGL which benefits enormously from working with long rows of pixels.
2442        while let Some(info) = self.tiles.first() {
2443            if tile.rect.min.y != info.rect.min.y || tile.kind != info.kind {
2444                // Different row or different kind, stop merging.
2445                break;
2446            }
2447
2448            let max = match info.kind {
2449                // If a tile must be rendered into an intermediate target, don't make
2450                // wider than 1024 pixels so that it plays well with the texture atlas.
2451                QuadTileKind::Pattern { has_mask: true } => 1024.0,
2452                // If the tile is rendered directly into the destination target let it
2453                // be as wide as possible.
2454                QuadTileKind::Pattern { has_mask: false } => f32::MAX,
2455                QuadTileKind::Clipped => { break; }
2456            };
2457
2458            if info.rect.max.x - tile.rect.min.x > max {
2459                break;
2460            }
2461
2462            // At this point we know that this tile on the same row, adjacent
2463            // and of the same kind as the previous one, so they can be merged.
2464            tile.rect.max.x = info.rect.max.x;
2465            self.tiles = &self.tiles[1..];
2466        }
2467
2468        Some(tile)
2469    }
2470}
2471
2472#[cfg(test)]
2473fn qc_new(x0: f32, y0: f32, w: f32, h: f32) -> QuadTileClassifier {
2474    let mut qc = QuadTileClassifier::new();
2475
2476    qc.reset(
2477        DeviceRect::new(DevicePoint::new(x0, y0), DevicePoint::new(x0 + w, y0 + h)),
2478        false,
2479    );
2480
2481    qc
2482}
2483
2484#[cfg(test)]
2485fn qc_verify(mut qc: QuadTileClassifier, expected: &[QuadTileKind]) {
2486    let tiles = qc.classify();
2487
2488    let mut n = 0;
2489    for (tile, ex) in tiles.zip(expected.iter()) {
2490        assert_eq!(tile.kind, *ex, "Failed for tile {:?}", tile.rect.to_rect());
2491        n += 1;
2492    }
2493
2494    assert_eq!(n, expected.len())
2495}
2496
2497#[cfg(test)]
2498const P: QuadTileKind = QuadTileKind::Pattern { has_mask: false };
2499
2500#[cfg(test)]
2501const M: QuadTileKind = QuadTileKind::Pattern { has_mask: true };
2502
2503#[test]
2504fn quad_classify_1() {
2505    let qc = qc_new(0.0, 0.0, 768.0, 768.0);
2506    qc_verify(qc, &[
2507        P,
2508        P,
2509        P,
2510    ]);
2511}
2512
2513#[test]
2514fn quad_classify_2() {
2515    let mut qc = qc_new(0.0, 0.0, 768.0, 768.0);
2516
2517    let rect = DeviceRect::new(DevicePoint::new(0.0, 0.0), DevicePoint::new(768.0, 768.0));
2518    qc.add_clip_rect(rect, ClipMode::Clip);
2519
2520    qc_verify(qc, &[
2521        P,
2522        P,
2523        P,
2524    ]);
2525}
2526
2527#[test]
2528fn quad_classify_3() {
2529    let mut qc = qc_new(0.0, 0.0, 768.0, 768.0);
2530
2531    let rect = DeviceRect::new(DevicePoint::new(230.0, 230.0), DevicePoint::new(460.0, 460.0));
2532    qc.add_clip_rect(rect, ClipMode::Clip);
2533
2534    qc_verify(qc, &[P]);
2535}
2536
2537#[test]
2538fn quad_classify_4() {
2539    let mut qc = qc_new(0.0, 0.0, 768.0, 768.0);
2540
2541    let rect = DeviceRect::new(DevicePoint::new(230.0, 230.0), DevicePoint::new(537.0, 537.0));
2542    qc.add_clip_rect(rect, ClipMode::Clip);
2543
2544    qc_verify(qc, &[
2545        P,
2546        P,
2547        P,
2548    ]);
2549}
2550
2551#[test]
2552fn quad_classify_5() {
2553    let mut qc = qc_new(0.0, 0.0, 768.0, 768.0);
2554
2555    let rect = DeviceRect::new(DevicePoint::new(230.0, 230.0), DevicePoint::new(537.0, 537.0));
2556    qc.add_clip_rect(rect, ClipMode::ClipOut);
2557
2558    qc_verify(qc, &[
2559        M,
2560        M, M,
2561        M,
2562    ]);
2563}
2564
2565#[test]
2566fn quad_classify_6() {
2567    let mut qc = qc_new(0.0, 0.0, 768.0, 768.0);
2568
2569    let rect = DeviceRect::new(DevicePoint::new(40.0, 40.0), DevicePoint::new(60.0, 60.0));
2570    qc.add_clip_rect(rect, ClipMode::ClipOut);
2571
2572    qc_verify(qc, &[
2573        M,
2574        M,
2575        M,
2576    ]);
2577}
2578
2579#[test]
2580fn quad_classify_7() {
2581    let mut qc = qc_new(0.0, 0.0, 768.0, 768.0);
2582
2583    let rect = DeviceRect::new(DevicePoint::new(154.0, 77.0), DevicePoint::new(691.0, 614.0));
2584    qc.add_mask_region(rect);
2585
2586    qc_verify(qc, &[
2587        M,
2588        M,
2589        M,
2590    ]);
2591}
2592
2593#[test]
2594fn quad_classify_8() {
2595    let mut qc = qc_new(0.0, 0.0, 768.0, 768.0);
2596
2597    let rect = DeviceRect::new(DevicePoint::new(307.0, 307.0), DevicePoint::new(460.0, 460.0));
2598    qc.add_mask_region(rect);
2599
2600    qc_verify(qc, &[
2601        P,
2602        P, M, P,
2603        P,
2604    ]);
2605}
2606
2607#[test]
2608fn quad_classify_9() {
2609    let mut qc = qc_new(100.0, 200.0, 1024.0, 1024.0);
2610
2611    let rect = DeviceRect::new(DevicePoint::new(90.0, 180.0), DevicePoint::new(250.0, 650.0));
2612    qc.add_mask_region(rect);
2613
2614    qc_verify(qc, &[
2615        M, P,
2616        M, P,
2617        P,
2618        P,
2619    ]);
2620}
2621
2622#[test]
2623fn quad_classify_10() {
2624    let mut qc = qc_new(100.0, 200.0, 1024.0, 1024.0);
2625
2626    let mask_rect = DeviceRect::new(DevicePoint::new(90.0, 180.0), DevicePoint::new(510.0, 710.0));
2627    qc.add_mask_region(mask_rect);
2628
2629    let clip_rect = DeviceRect::new(DevicePoint::new(120.0, 220.0), DevicePoint::new(714.0, 1015.0));
2630    qc.add_clip_rect(clip_rect, ClipMode::Clip);
2631
2632    qc_verify(qc, &[
2633        M, P,
2634        M, P,
2635        P,
2636        P,
2637    ]);
2638}
2639
2640#[test]
2641fn quad_classify_11() {
2642    let mut qc = qc_new(100.0, 200.0, 1024.0, 1024.0);
2643
2644    let mask_rect = DeviceRect::new(DevicePoint::new(90.0, 180.0), DevicePoint::new(510.0, 710.0));
2645    qc.add_mask_region(mask_rect);
2646
2647    let clip_rect = DeviceRect::new(DevicePoint::new(120.0, 220.0), DevicePoint::new(714.0, 1015.0));
2648    qc.add_clip_rect(clip_rect, ClipMode::Clip);
2649
2650    let clip_out_rect = DeviceRect::new(DevicePoint::new(130.0, 200.0), DevicePoint::new(714.0, 609.0));
2651    qc.add_clip_rect(clip_out_rect, ClipMode::ClipOut);
2652
2653    qc_verify(qc, &[
2654        M,
2655        M,
2656        M,
2657        M,
2658    ]);
2659}
2660
2661#[test]
2662fn quad_classify_12() {
2663    let mut qc = qc_new(100.0, 200.0, 1024.0, 1024.0);
2664
2665    let clip_out_rect = DeviceRect::new(DevicePoint::new(130.0, 200.0), DevicePoint::new(714.0, 609.0));
2666    qc.add_clip_rect(clip_out_rect, ClipMode::ClipOut);
2667
2668    let clip_rect = DeviceRect::new(DevicePoint::new(120.0, 220.0), DevicePoint::new(714.0, 1015.0));
2669    qc.add_clip_rect(clip_rect, ClipMode::Clip);
2670
2671    let mask_rect = DeviceRect::new(DevicePoint::new(90.0, 180.0), DevicePoint::new(510.0, 710.0));
2672    qc.add_mask_region(mask_rect);
2673
2674    qc_verify(qc, &[
2675        M,
2676        M,
2677        M,
2678        M,
2679    ]);
2680}