Skip to main content

webrender/
batch.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::{AlphaType, ClipMode, ImageBufferKind};
6use api::{FontInstanceFlags, YuvColorSpace, YuvFormat, ColorDepth, ColorRange, PremultipliedColorF};
7use api::units::*;
8use crate::clip::{clamped_radius, ClipNodeFlags, ClipNodeRange, ClipItemKind, ClipStore};
9use crate::command_buffer::PrimitiveCommand;
10use crate::composite::CompositorSurfaceKind;
11use crate::pattern::PatternKind;
12use crate::spatial_tree::{SpatialTree, SpatialNodeIndex, CoordinateSystemId};
13use glyph_rasterizer::{GlyphFormat, SubpixelDirection};
14use crate::gpu_types::{BrushFlags, BrushInstance, ImageSource, PrimitiveHeaders, UvRectKind, ZBufferId, ZBufferIdGenerator};
15use crate::gpu_types::SplitCompositeInstance;
16use crate::gpu_types::{PrimitiveInstanceData, RasterizationSpace, GlyphInstance};
17use crate::gpu_types::{PrimitiveHeader, PrimitiveHeaderIndex};
18use crate::gpu_types::{ImageBrushUserData, get_shader_opacity, MaskInstance};
19use crate::gpu_types::{ClipMaskInstanceCommon, ClipMaskInstanceRect};
20use crate::internal_types::{FastHashMap, Filter, FrameAllocator, FrameMemory, FrameVec, Swizzle, TextureSource};
21use crate::picture::{Picture3DContext, PictureCompositeMode, calculate_screen_uv};
22use crate::prim_store::{PrimitiveKind, ClipData};
23use crate::prim_store::{PrimitiveInstance, PrimitiveOpacity, SegmentInstanceIndex};
24use crate::prim_store::{BrushSegment, ClipMaskKind, ClipTaskIndex};
25use crate::prim_store::VECS_PER_SEGMENT;
26use crate::quad;
27use crate::render_target::RenderTargetContext;
28use crate::render_task_graph::{RenderTaskId, RenderTaskGraph};
29use crate::render_task::{RenderTaskAddress, RenderTaskKind};
30use crate::renderer::{BlendMode, GpuBufferAddress, GpuBufferBlockF, GpuBufferBuilder, ShaderColorMode};
31use crate::renderer::MAX_VERTEX_TEXTURE_WIDTH;
32use crate::resource_cache::{GlyphFetchResult, ImageProperties};
33use crate::space::SpaceMapper;
34use crate::transform::{GpuTransformId, TransformPalette, TransformMetadata};
35use crate::visibility::{PrimitiveVisibilityFlags, DrawState};
36use smallvec::SmallVec;
37use std::{f32, i32, usize};
38use crate::util::{project_rect, MaxRect, ScaleOffset};
39use crate::segment::EdgeMask;
40
41
42// Special sentinel value recognized by the shader. It is considered to be
43// a dummy task that doesn't mask out anything.
44const OPAQUE_TASK_ADDRESS: RenderTaskAddress = RenderTaskAddress(0x7fffffff);
45
46/// Used to signal there are no segments provided with this primitive.
47pub const INVALID_SEGMENT_INDEX: i32 = 0xffff;
48
49/// Size in device pixels for tiles that clip masks are drawn in.
50const CLIP_RECTANGLE_TILE_SIZE: i32 = 128;
51
52/// The minimum size of a clip mask before trying to draw in tiles.
53const CLIP_RECTANGLE_AREA_THRESHOLD: f32 = (CLIP_RECTANGLE_TILE_SIZE * CLIP_RECTANGLE_TILE_SIZE * 4) as f32;
54
55#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
56#[cfg_attr(feature = "capture", derive(Serialize))]
57#[cfg_attr(feature = "replay", derive(Deserialize))]
58pub enum BrushBatchKind {
59    Solid,
60    Image(ImageBufferKind),
61    Blend,
62    MixBlend {
63        task_id: RenderTaskId,
64        backdrop_id: RenderTaskId,
65    },
66    YuvImage(ImageBufferKind, YuvFormat, ColorDepth, YuvColorSpace, ColorRange),
67    Opacity,
68}
69
70#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
71#[cfg_attr(feature = "capture", derive(Serialize))]
72#[cfg_attr(feature = "replay", derive(Deserialize))]
73pub enum BatchKind {
74    SplitComposite,
75    TextRun(GlyphFormat),
76    Brush(BrushBatchKind),
77    Quad(PatternKind),
78}
79
80/// Input textures for a primitive, without consideration of clip mask
81#[derive(Copy, Clone, Debug)]
82#[cfg_attr(feature = "capture", derive(Serialize))]
83#[cfg_attr(feature = "replay", derive(Deserialize))]
84pub struct TextureSet {
85    pub colors: [TextureSource; 3],
86}
87
88impl TextureSet {
89    const UNTEXTURED: TextureSet = TextureSet {
90        colors: [
91            TextureSource::Invalid,
92            TextureSource::Invalid,
93            TextureSource::Invalid,
94        ],
95    };
96
97    /// A textured primitive
98    fn prim_textured(
99        color: TextureSource,
100    ) -> Self {
101        TextureSet {
102            colors: [
103                color,
104                TextureSource::Invalid,
105                TextureSource::Invalid,
106            ],
107        }
108    }
109
110    fn is_compatible_with(&self, other: &TextureSet) -> bool {
111        self.colors[0].is_compatible(&other.colors[0]) &&
112        self.colors[1].is_compatible(&other.colors[1]) &&
113        self.colors[2].is_compatible(&other.colors[2])
114    }
115}
116
117impl TextureSource {
118    fn combine(&self, other: TextureSource) -> TextureSource {
119        if other == TextureSource::Invalid {
120            *self
121        } else {
122            other
123        }
124    }
125}
126
127/// Optional textures that can be used as a source in the shaders.
128/// Textures that are not used by the batch are equal to TextureId::invalid().
129#[derive(Copy, Clone, Debug)]
130#[cfg_attr(feature = "capture", derive(Serialize))]
131#[cfg_attr(feature = "replay", derive(Deserialize))]
132pub struct BatchTextures {
133    pub input: TextureSet,
134    pub clip_mask: TextureSource,
135}
136
137impl BatchTextures {
138    /// An empty batch textures (no binding slots set)
139    pub fn empty() -> BatchTextures {
140        BatchTextures {
141            input: TextureSet::UNTEXTURED,
142            clip_mask: TextureSource::Invalid,
143        }
144    }
145
146    /// A textured primitive with optional clip mask
147    pub fn prim_textured(
148        color: TextureSource,
149        clip_mask: TextureSource,
150    ) -> BatchTextures {
151        BatchTextures {
152            input: TextureSet::prim_textured(color),
153            clip_mask,
154        }
155    }
156
157    /// An untextured primitive with optional clip mask
158    pub fn prim_untextured(
159        clip_mask: TextureSource,
160    ) -> BatchTextures {
161        BatchTextures {
162            input: TextureSet::UNTEXTURED,
163            clip_mask,
164        }
165    }
166
167    /// A composite style effect with single input texture
168    pub fn composite_rgb(
169        texture: TextureSource,
170    ) -> BatchTextures {
171        BatchTextures {
172            input: TextureSet {
173                colors: [
174                    texture,
175                    TextureSource::Invalid,
176                    TextureSource::Invalid,
177                ],
178            },
179            clip_mask: TextureSource::Invalid,
180        }
181    }
182
183    /// A composite style effect with up to 3 input textures
184    pub fn composite_yuv(
185        color0: TextureSource,
186        color1: TextureSource,
187        color2: TextureSource,
188    ) -> BatchTextures {
189        BatchTextures {
190            input: TextureSet {
191                colors: [color0, color1, color2],
192            },
193            clip_mask: TextureSource::Invalid,
194        }
195    }
196
197    pub fn is_compatible_with(&self, other: &BatchTextures) -> bool {
198        if !self.clip_mask.is_compatible(&other.clip_mask) {
199            return false;
200        }
201
202        self.input.is_compatible_with(&other.input)
203    }
204
205    pub fn combine_textures(&self, other: BatchTextures) -> Option<BatchTextures> {
206        if !self.is_compatible_with(&other) {
207            return None;
208        }
209
210        let mut new_textures = BatchTextures::empty();
211
212        new_textures.clip_mask = self.clip_mask.combine(other.clip_mask);
213
214        for i in 0 .. 3 {
215            new_textures.input.colors[i] = self.input.colors[i].combine(other.input.colors[i]);
216        }
217
218        Some(new_textures)
219    }
220
221    fn merge(&mut self, other: &BatchTextures) {
222        self.clip_mask = self.clip_mask.combine(other.clip_mask);
223
224        for (s, o) in self.input.colors.iter_mut().zip(other.input.colors.iter()) {
225            *s = s.combine(*o);
226        }
227    }
228}
229
230#[derive(Copy, Clone, Debug)]
231#[cfg_attr(feature = "capture", derive(Serialize))]
232#[cfg_attr(feature = "replay", derive(Deserialize))]
233pub struct BatchKey {
234    pub kind: BatchKind,
235    pub blend_mode: BlendMode,
236    pub textures: BatchTextures,
237}
238
239impl BatchKey {
240    pub fn new(kind: BatchKind, blend_mode: BlendMode, textures: BatchTextures) -> Self {
241        BatchKey {
242            kind,
243            blend_mode,
244            textures,
245        }
246    }
247
248    pub fn is_compatible_with(&self, other: &BatchKey) -> bool {
249        self.kind == other.kind && self.blend_mode == other.blend_mode && self.textures.is_compatible_with(&other.textures)
250    }
251}
252
253pub struct BatchRects {
254    /// Union of all of the batch's item rects.
255    ///
256    /// Very often we can skip iterating over item rects by testing against
257    /// this one first.
258    batch: PictureRect,
259    /// When the batch rectangle above isn't a good enough approximation, we
260    /// store per item rects.
261    items: Option<FrameVec<PictureRect>>,
262    // TODO: batch rects don't need to be part of the frame but they currently
263    // are. It may be cleaner to remove them from the frame's final data structure
264    // and not use the frame's allocator.
265    allocator: FrameAllocator,
266}
267
268impl BatchRects {
269    fn new(allocator: FrameAllocator) -> Self {
270        BatchRects {
271            batch: PictureRect::zero(),
272            items: None,
273            allocator,
274        }
275    }
276
277    #[inline]
278    fn add_rect(&mut self, rect: &PictureRect) {
279        let union = self.batch.union(rect);
280        // If we have already started storing per-item rects, continue doing so.
281        // Otherwise, check whether only storing the batch rect is a good enough
282        // approximation.
283        if let Some(items) = &mut self.items {
284            items.push(*rect);
285        } else if self.batch.area() + rect.area() < union.area() {
286            let mut items = self.allocator.clone().new_vec_with_capacity(16);
287            items.push(self.batch);
288            items.push(*rect);
289            self.items = Some(items);
290        }
291
292        self.batch = union;
293    }
294
295    #[inline]
296    fn intersects(&mut self, rect: &PictureRect) -> bool {
297        if !self.batch.intersects(rect) {
298            return false;
299        }
300
301        if let Some(items) = &self.items {
302            items.iter().any(|item| item.intersects(rect))
303        } else {
304            // If we don't have per-item rects it means the batch rect is a good
305            // enough approximation and we didn't bother storing per-rect items.
306            true
307        }
308    }
309}
310
311
312pub struct AlphaBatchList {
313    pub batches: FrameVec<PrimitiveBatch>,
314    pub batch_rects: FrameVec<BatchRects>,
315    current_batch_index: usize,
316    current_z_id: ZBufferId,
317    break_advanced_blend_batches: bool,
318}
319
320impl AlphaBatchList {
321    fn new(break_advanced_blend_batches: bool, preallocate: usize, memory: &FrameMemory) -> Self {
322        AlphaBatchList {
323            batches: memory.new_vec_with_capacity(preallocate),
324            batch_rects: memory.new_vec_with_capacity(preallocate),
325            current_z_id: ZBufferId::invalid(),
326            current_batch_index: usize::MAX,
327            break_advanced_blend_batches,
328        }
329    }
330
331    /// Clear all current batches in this list. This is typically used
332    /// when a primitive is encountered that occludes all previous
333    /// content in this batch list.
334    fn clear(&mut self) {
335        self.current_batch_index = usize::MAX;
336        self.current_z_id = ZBufferId::invalid();
337        self.batches.clear();
338        self.batch_rects.clear();
339    }
340
341    pub fn set_params_and_get_batch(
342        &mut self,
343        key: BatchKey,
344        features: BatchFeatures,
345        // The bounding box of everything at this Z plane. We expect potentially
346        // multiple primitive segments coming with the same `z_id`.
347        z_bounding_rect: &PictureRect,
348        z_id: ZBufferId,
349    ) -> &mut FrameVec<PrimitiveInstanceData> {
350        if z_id != self.current_z_id ||
351           self.current_batch_index == usize::MAX ||
352           !self.batches[self.current_batch_index].key.is_compatible_with(&key)
353        {
354            let mut selected_batch_index = None;
355
356            match key.blend_mode {
357                BlendMode::Advanced(_) if self.break_advanced_blend_batches => {
358                    // don't try to find a batch
359                }
360                _ => {
361                    for (batch_index, batch) in self.batches.iter().enumerate().rev() {
362                        // For normal batches, we only need to check for overlaps for batches
363                        // other than the first batch we consider. If the first batch
364                        // is compatible, then we know there isn't any potential overlap
365                        // issues to worry about.
366                        if batch.key.is_compatible_with(&key) {
367                            selected_batch_index = Some(batch_index);
368                            break;
369                        }
370
371                        // check for intersections
372                        if self.batch_rects[batch_index].intersects(z_bounding_rect) {
373                            break;
374                        }
375                    }
376                }
377            }
378
379            if selected_batch_index.is_none() {
380                // Text runs tend to have a lot of instances per batch, causing a lot of reallocation
381                // churn as items are added one by one, so we give it a head start. Ideally we'd start
382                // with a larger number, closer to 1k but in some bad cases with lots of batch break
383                // we would be wasting a lot of memory.
384                // Generally it is safe to preallocate small-ish values for other batch kinds because
385                // the items are small and there are no zero-sized batches so there will always be
386                // at least one allocation.
387                let prealloc = match key.kind {
388                    BatchKind::TextRun(..) => 128,
389                    _ => 16,
390                };
391                let mut new_batch = PrimitiveBatch::new(key, self.batches.allocator().clone());
392                new_batch.instances.reserve(prealloc);
393                selected_batch_index = Some(self.batches.len());
394                self.batches.push(new_batch);
395                self.batch_rects.push(BatchRects::new(self.batches.allocator().clone()));
396            }
397
398            self.current_batch_index = selected_batch_index.unwrap();
399            self.batch_rects[self.current_batch_index].add_rect(z_bounding_rect);
400            self.current_z_id = z_id;
401        }
402
403        let batch = &mut self.batches[self.current_batch_index];
404        batch.features |= features;
405        batch.key.textures.merge(&key.textures);
406
407        &mut batch.instances
408    }
409}
410
411pub struct OpaqueBatchList {
412    pub pixel_area_threshold_for_new_batch: f32,
413    pub batches: FrameVec<PrimitiveBatch>,
414    pub current_batch_index: usize,
415    lookback_count: usize,
416}
417
418impl OpaqueBatchList {
419    fn new(pixel_area_threshold_for_new_batch: f32, lookback_count: usize, memory: &FrameMemory) -> Self {
420        OpaqueBatchList {
421            batches: memory.new_vec(),
422            pixel_area_threshold_for_new_batch,
423            current_batch_index: usize::MAX,
424            lookback_count,
425        }
426    }
427
428    /// Clear all current batches in this list. This is typically used
429    /// when a primitive is encountered that occludes all previous
430    /// content in this batch list.
431    fn clear(&mut self) {
432        self.current_batch_index = usize::MAX;
433        self.batches.clear();
434    }
435
436    pub fn set_params_and_get_batch(
437        &mut self,
438        key: BatchKey,
439        features: BatchFeatures,
440        // The bounding box of everything at the current Z, whatever it is. We expect potentially
441        // multiple primitive segments produced by a primitive, which we allow to check
442        // `current_batch_index` instead of iterating the batches.
443        z_bounding_rect: &PictureRect,
444    ) -> &mut FrameVec<PrimitiveInstanceData> {
445        // If the area of this primitive is larger than the given threshold,
446        // then it is large enough to warrant breaking a batch for. In this
447        // case we just see if it can be added to the existing batch or
448        // create a new one.
449        let is_large_occluder = z_bounding_rect.area() > self.pixel_area_threshold_for_new_batch;
450        // Since primitives of the same kind tend to come in succession, we keep track
451        // of the current batch index to skip the search in some cases. We ignore the
452        // current batch index in the case of large occluders to make sure they get added
453        // at the top of the bach list.
454        if is_large_occluder || self.current_batch_index == usize::MAX ||
455           !self.batches[self.current_batch_index].key.is_compatible_with(&key) {
456            let mut selected_batch_index = None;
457            if is_large_occluder {
458                if let Some(batch) = self.batches.last() {
459                    if batch.key.is_compatible_with(&key) {
460                        selected_batch_index = Some(self.batches.len() - 1);
461                    }
462                }
463            } else {
464                // Otherwise, look back through a reasonable number of batches.
465                for (batch_index, batch) in self.batches.iter().enumerate().rev().take(self.lookback_count) {
466                    if batch.key.is_compatible_with(&key) {
467                        selected_batch_index = Some(batch_index);
468                        break;
469                    }
470                }
471            }
472
473            if selected_batch_index.is_none() {
474                let new_batch = PrimitiveBatch::new(key, self.batches.allocator().clone());
475                selected_batch_index = Some(self.batches.len());
476                self.batches.push(new_batch);
477            }
478
479            self.current_batch_index = selected_batch_index.unwrap();
480        }
481
482        let batch = &mut self.batches[self.current_batch_index];
483        batch.features |= features;
484        batch.key.textures.merge(&key.textures);
485
486        &mut batch.instances
487    }
488
489    fn finalize(&mut self) {
490        // Reverse the instance arrays in the opaque batches
491        // to get maximum z-buffer efficiency by drawing
492        // front-to-back.
493        // TODO(gw): Maybe we can change the batch code to
494        //           build these in reverse and avoid having
495        //           to reverse the instance array here.
496        for batch in &mut self.batches {
497            batch.instances.reverse();
498        }
499    }
500}
501
502#[cfg_attr(feature = "capture", derive(Serialize))]
503#[cfg_attr(feature = "replay", derive(Deserialize))]
504pub struct PrimitiveBatch {
505    pub key: BatchKey,
506    pub instances: FrameVec<PrimitiveInstanceData>,
507    pub features: BatchFeatures,
508}
509
510bitflags! {
511    /// Features of the batch that, if not requested, may allow a fast-path.
512    ///
513    /// Rather than breaking batches when primitives request different features,
514    /// we always request the minimum amount of features to satisfy all items in
515    /// the batch.
516    /// The goal is to let the renderer be optionally select more specialized
517    /// versions of a shader if the batch doesn't require code certain code paths.
518    /// Not all shaders necessarily implement all of these features.
519    #[cfg_attr(feature = "capture", derive(Serialize))]
520    #[cfg_attr(feature = "replay", derive(Deserialize))]
521    #[derive(Debug, Copy, PartialEq, Eq, Clone, PartialOrd, Ord, Hash)]
522    pub struct BatchFeatures: u8 {
523        const ALPHA_PASS = 1 << 0;
524        const ANTIALIASING = 1 << 1;
525        const REPETITION = 1 << 2;
526        /// Indicates a primitive in this batch may use a clip mask.
527        const CLIP_MASK = 1 << 3;
528    }
529}
530
531impl PrimitiveBatch {
532    fn new(key: BatchKey, allocator: FrameAllocator) -> PrimitiveBatch {
533        PrimitiveBatch {
534            key,
535            instances: FrameVec::new_in(allocator),
536            features: BatchFeatures::empty(),
537        }
538    }
539
540    fn merge(&mut self, other: PrimitiveBatch) {
541        self.instances.extend(other.instances);
542        self.features |= other.features;
543        self.key.textures.merge(&other.key.textures);
544    }
545}
546
547#[cfg_attr(feature = "capture", derive(Serialize))]
548#[cfg_attr(feature = "replay", derive(Deserialize))]
549pub struct AlphaBatchContainer {
550    pub opaque_batches: FrameVec<PrimitiveBatch>,
551    pub alpha_batches: FrameVec<PrimitiveBatch>,
552    /// The overall scissor rect for this render task, if one
553    /// is required.
554    pub task_scissor_rect: Option<DeviceIntRect>,
555    /// The rectangle of the owning render target that this
556    /// set of batches affects.
557    pub task_rect: DeviceIntRect,
558}
559
560impl AlphaBatchContainer {
561    pub fn new(
562        task_scissor_rect: Option<DeviceIntRect>,
563        memory: &FrameMemory,
564    ) -> AlphaBatchContainer {
565        AlphaBatchContainer {
566            opaque_batches: memory.new_vec(),
567            alpha_batches: memory.new_vec(),
568            task_scissor_rect,
569            task_rect: DeviceIntRect::zero(),
570        }
571    }
572
573    pub fn is_empty(&self) -> bool {
574        self.opaque_batches.is_empty() &&
575        self.alpha_batches.is_empty()
576    }
577
578    fn merge(&mut self, builder: AlphaBatchBuilder, task_rect: &DeviceIntRect) {
579        self.task_rect = self.task_rect.union(task_rect);
580
581        for other_batch in builder.opaque_batch_list.batches {
582            let batch_index = self.opaque_batches.iter().position(|batch| {
583                batch.key.is_compatible_with(&other_batch.key)
584            });
585
586            match batch_index {
587                Some(batch_index) => {
588                    self.opaque_batches[batch_index].merge(other_batch);
589                }
590                None => {
591                    self.opaque_batches.push(other_batch);
592                }
593            }
594        }
595
596        let mut min_batch_index = 0;
597
598        for other_batch in builder.alpha_batch_list.batches {
599            let batch_index = self.alpha_batches.iter().skip(min_batch_index).position(|batch| {
600                batch.key.is_compatible_with(&other_batch.key)
601            });
602
603            match batch_index {
604                Some(batch_index) => {
605                    let index = batch_index + min_batch_index;
606                    self.alpha_batches[index].merge(other_batch);
607                    min_batch_index = index;
608                }
609                None => {
610                    self.alpha_batches.push(other_batch);
611                    min_batch_index = self.alpha_batches.len();
612                }
613            }
614        }
615    }
616}
617
618/// Each segment can optionally specify a per-segment
619/// texture set and one user data field.
620#[derive(Debug, Copy, Clone)]
621struct SegmentInstanceData {
622    textures: TextureSet,
623    specific_resource_address: i32,
624}
625
626/// Encapsulates the logic of building batches for items that are blended.
627pub struct AlphaBatchBuilder {
628    pub alpha_batch_list: AlphaBatchList,
629    pub opaque_batch_list: OpaqueBatchList,
630    pub render_task_id: RenderTaskId,
631    render_task_address: RenderTaskAddress,
632}
633
634impl AlphaBatchBuilder {
635    pub fn new(
636        screen_size: DeviceIntSize,
637        break_advanced_blend_batches: bool,
638        lookback_count: usize,
639        render_task_id: RenderTaskId,
640        render_task_address: RenderTaskAddress,
641        memory: &FrameMemory,
642    ) -> Self {
643        // The threshold for creating a new batch is
644        // one quarter the screen size.
645        let batch_area_threshold = (screen_size.width * screen_size.height) as f32 / 4.0;
646
647        AlphaBatchBuilder {
648            alpha_batch_list: AlphaBatchList::new(break_advanced_blend_batches, 128, memory),
649            opaque_batch_list: OpaqueBatchList::new(batch_area_threshold, lookback_count, memory),
650            render_task_id,
651            render_task_address,
652        }
653    }
654
655    /// Clear all current batches in this builder. This is typically used
656    /// when a primitive is encountered that occludes all previous
657    /// content in this batch list.
658    fn clear(&mut self) {
659        self.alpha_batch_list.clear();
660        self.opaque_batch_list.clear();
661    }
662
663    pub fn build(
664        mut self,
665        batch_containers: &mut FrameVec<AlphaBatchContainer>,
666        merged_batches: &mut AlphaBatchContainer,
667        task_rect: DeviceIntRect,
668        task_scissor_rect: Option<DeviceIntRect>,
669    ) {
670        self.opaque_batch_list.finalize();
671
672        if task_scissor_rect.is_none() {
673            merged_batches.merge(self, &task_rect);
674        } else {
675            batch_containers.push(AlphaBatchContainer {
676                alpha_batches: self.alpha_batch_list.batches,
677                opaque_batches: self.opaque_batch_list.batches,
678                task_scissor_rect,
679                task_rect,
680            });
681        }
682    }
683
684    pub fn push_single_instance(
685        &mut self,
686        key: BatchKey,
687        features: BatchFeatures,
688        bounding_rect: &PictureRect,
689        z_id: ZBufferId,
690        instance: PrimitiveInstanceData,
691    ) {
692        self.set_params_and_get_batch(key, features, bounding_rect, z_id)
693            .push(instance);
694    }
695
696    pub fn set_params_and_get_batch(
697        &mut self,
698        key: BatchKey,
699        features: BatchFeatures,
700        bounding_rect: &PictureRect,
701        z_id: ZBufferId,
702    ) -> &mut FrameVec<PrimitiveInstanceData> {
703        match key.blend_mode {
704            BlendMode::None => {
705                self.opaque_batch_list
706                    .set_params_and_get_batch(key, features, bounding_rect)
707            }
708            BlendMode::Alpha |
709            BlendMode::PremultipliedAlpha |
710            BlendMode::PremultipliedDestOut |
711            BlendMode::SubpixelDualSource |
712            BlendMode::Advanced(_) |
713            BlendMode::MultiplyDualSource |
714            BlendMode::Screen |
715            BlendMode::Exclusion |
716            BlendMode::PlusLighter => {
717                self.alpha_batch_list
718                    .set_params_and_get_batch(key, features, bounding_rect, z_id)
719            }
720        }
721    }
722}
723
724/// Supports (recursively) adding a list of primitives and pictures to an alpha batch
725/// builder. In future, it will support multiple dirty regions / slices, allowing the
726/// contents of a picture to be spliced into multiple batch builders.
727pub struct BatchBuilder {
728    /// A temporary buffer that is used during glyph fetching, stored here
729    /// to reduce memory allocations.
730    glyph_fetch_buffer: Vec<GlyphFetchResult>,
731
732    batcher: AlphaBatchBuilder,
733}
734
735impl BatchBuilder {
736    pub fn new(batcher: AlphaBatchBuilder) -> Self {
737        BatchBuilder {
738            glyph_fetch_buffer: Vec::new(),
739            batcher,
740        }
741    }
742
743    pub fn finalize(self) -> AlphaBatchBuilder {
744        self.batcher
745    }
746
747    fn add_brush_instance_to_batches(
748        &mut self,
749        batch_key: BatchKey,
750        features: BatchFeatures,
751        bounding_rect: &PictureRect,
752        z_id: ZBufferId,
753        segment_index: i32,
754        edge_flags: EdgeMask,
755        clip_task_address: RenderTaskAddress,
756        brush_flags: BrushFlags,
757        prim_header_index: PrimitiveHeaderIndex,
758        resource_address: i32,
759    ) {
760        assert!(
761            !(brush_flags.contains(BrushFlags::NORMALIZED_UVS)
762                && features.contains(BatchFeatures::REPETITION)),
763            "Normalized UVs are not supported with repetition."
764        );
765        let instance = BrushInstance {
766            segment_index,
767            edge_flags,
768            clip_task_address,
769            brush_flags,
770            prim_header_index,
771            resource_address,
772        };
773
774        self.batcher.push_single_instance(
775            batch_key,
776            features,
777            bounding_rect,
778            z_id,
779            PrimitiveInstanceData::from(instance),
780        );
781    }
782
783    fn add_split_composite_instance_to_batches(
784        &mut self,
785        batch_key: BatchKey,
786        features: BatchFeatures,
787        bounding_rect: &PictureRect,
788        z_id: ZBufferId,
789        prim_header_index: PrimitiveHeaderIndex,
790        polygons_address: i32,
791    ) {
792        let render_task_address = self.batcher.render_task_address;
793
794        self.batcher.push_single_instance(
795            batch_key,
796            features,
797            bounding_rect,
798            z_id,
799            PrimitiveInstanceData::from(SplitCompositeInstance {
800                prim_header_index,
801                render_task_address,
802                polygons_address,
803                z: z_id,
804            }),
805        );
806    }
807
808    /// Clear all current batchers. This is typically used when a primitive
809    /// is encountered that occludes all previous content in this batch list.
810    fn clear_batches(&mut self) {
811        self.batcher.clear();
812    }
813
814    // Adds a primitive to a batch.
815    // It can recursively call itself in some situations, for
816    // example if it encounters a picture where the items
817    // in that picture are being drawn into the same target.
818    pub fn add_prim_to_batch(
819        &mut self,
820        cmd: &PrimitiveCommand,
821        prim_spatial_node_index: SpatialNodeIndex,
822        ctx: &RenderTargetContext,
823        render_tasks: &RenderTaskGraph,
824        prim_headers: &mut PrimitiveHeaders,
825        transforms: &mut TransformPalette,
826        root_spatial_node_index: SpatialNodeIndex,
827        surface_spatial_node_index: SpatialNodeIndex,
828        z_generator: &mut ZBufferIdGenerator,
829        prim_instances: &[PrimitiveInstance],
830        gpu_buffer_builder: &mut GpuBufferBuilder,
831        segments: &[RenderTaskId],
832    ) {
833        let (draw_index, extra_prim_gpu_address) = match cmd {
834            PrimitiveCommand::Simple { draw_index } => {
835                (draw_index, None)
836            }
837            PrimitiveCommand::Complex { draw_index, gpu_address } => {
838                (draw_index, Some(gpu_address.as_int()))
839            }
840            PrimitiveCommand::Instance { draw_index, gpu_buffer_address } => {
841                (draw_index, Some(gpu_buffer_address.as_int()))
842            }
843            PrimitiveCommand::Quad { pattern, pattern_input, draw_index, gpu_buffer_address, quad_flags, edge_flags, transform_id, src_color_task_id, blend_mode } => {
844                let prim_info = &ctx.scratch.frame.draws[draw_index.0 as usize];
845                let bounding_rect = &prim_info.clip_chain.pic_coverage_rect;
846                let render_task_address = self.batcher.render_task_address;
847
848                if segments.is_empty() {
849                    let z_id = z_generator.next();
850
851                    quad::add_to_batch(
852                        *pattern,
853                        *pattern_input,
854                        render_task_address,
855                        *transform_id,
856                        *gpu_buffer_address,
857                        *quad_flags,
858                        *edge_flags,
859                        INVALID_SEGMENT_INDEX as u8,
860                        *src_color_task_id,
861                        z_id,
862                        *blend_mode,
863                        render_tasks,
864                        gpu_buffer_builder,
865                        |key, instance| {
866                            let batch = self.batcher.set_params_and_get_batch(
867                                key,
868                                BatchFeatures::empty(),
869                                bounding_rect,
870                                z_id,
871                            );
872                            batch.push(instance);
873                        },
874                    );
875                } else {
876                    for (i, task_id) in segments.iter().enumerate() {
877                        // TODO(gw): edge_flags should be per-segment, when used for more than composites
878                        debug_assert!(edge_flags.is_empty());
879
880                        let z_id = z_generator.next();
881
882                        quad::add_to_batch(
883                            *pattern,
884                            *pattern_input,
885                            render_task_address,
886                            *transform_id,
887                            *gpu_buffer_address,
888                            *quad_flags,
889                            *edge_flags,
890                            i as u8,
891                            *task_id,
892                            z_id,
893                            *blend_mode,
894                            render_tasks,
895                            gpu_buffer_builder,
896                            |key, instance| {
897                                let batch = self.batcher.set_params_and_get_batch(
898                                    key,
899                                    BatchFeatures::empty(),
900                                    bounding_rect,
901                                    z_id,
902                                );
903                                batch.push(instance);
904                            },
905                        );
906                    }
907                }
908
909                return;
910            }
911        };
912
913        let prim_instance = &prim_instances[draw_index.0 as usize];
914        let is_anti_aliased = ctx.data_stores.prim_has_anti_aliasing(prim_instance);
915
916        let brush_flags = if is_anti_aliased {
917            BrushFlags::FORCE_AA
918        } else {
919            BrushFlags::empty()
920        };
921
922        let vis_flags = match ctx.scratch.frame.draws[draw_index.0 as usize].state {
923            DrawState::Culled => {
924                return;
925            }
926            DrawState::PassThrough |
927            DrawState::Unset => {
928                panic!("bug: invalid visibility state");
929            }
930            DrawState::Visible { vis_flags, .. } => {
931                vis_flags
932            }
933        };
934
935        // If this primitive is a backdrop, that means that it is known to cover
936        // the entire picture cache background. In that case, the renderer will
937        // use the backdrop color as a clear color, and so we can drop this
938        // primitive and any prior primitives from the batch lists for this
939        // picture cache slice.
940        if vis_flags.contains(PrimitiveVisibilityFlags::IS_BACKDROP) {
941            self.clear_batches();
942            return;
943        }
944
945        let transform_id = transforms.gpu.get_id(
946            prim_spatial_node_index,
947            root_spatial_node_index,
948            ctx.spatial_tree,
949        );
950
951        // TODO(gw): Calculating this for every primitive is a bit
952        //           wasteful. We should probably cache this in
953        //           the scroll node...
954        let transform_metadata = transform_id.metadata();
955        let prim_info = &ctx.scratch.frame.draws[draw_index.0 as usize];
956        let bounding_rect = &prim_info.clip_chain.pic_coverage_rect;
957
958        let mut z_id = z_generator.next();
959
960        let prim_rect = ctx.data_stores.get_local_prim_rect(
961            prim_instance,
962            prim_info.snapped_local_rect,
963            &ctx.prim_store.pictures,
964            ctx.surfaces,
965        );
966
967        let mut batch_features = BatchFeatures::empty();
968        let may_need_repetition = match prim_instance.kind {
969            PrimitiveKind::Image { .. } => {
970                let idx = prim_info.kind_scratch.unwrap_image();
971                ctx.scratch.frame.images[idx].may_need_repetition
972            }
973            PrimitiveKind::NormalBorder { .. } => {
974                let idx = prim_info.kind_scratch.unwrap_normal_border();
975                ctx.scratch.frame.normal_border[idx].may_need_repetition
976            }
977            // Image borders always go through brush_image and may tile
978            // their mid sections, so request the repetition-capable
979            // shader.
980            PrimitiveKind::ImageBorder { .. } => true,
981            // Patterned line decorations (Dashed / Dotted / Wavy) batch
982            // as `BrushBatchKind::Image` over a cached pattern tile and
983            // rely on shader-level repetition to span the segment.
984            // Solid lines batch as `BrushBatchKind::Solid`, where the
985            // REPETITION flag is harmless.
986            PrimitiveKind::LineDecoration { .. } => true,
987            // Other prim kinds don't reach the brush_image consumer of
988            // BatchFeatures::REPETITION; the flag is dead state for
989            // them.
990            _ => false,
991        };
992        if may_need_repetition {
993            batch_features |= BatchFeatures::REPETITION;
994        }
995
996        if !transform_id.is_2d_axis_aligned() || is_anti_aliased {
997            batch_features |= BatchFeatures::ANTIALIASING;
998        }
999
1000        // Check if the primitive might require a clip mask.
1001        if prim_info.clip_task_index != ClipTaskIndex::INVALID {
1002            batch_features |= BatchFeatures::CLIP_MASK;
1003        }
1004
1005        if !bounding_rect.is_empty() {
1006            debug_assert_eq!(prim_info.clip_chain.pic_spatial_node_index, surface_spatial_node_index,
1007                "The primitive's bounding box is specified in a different coordinate system from the current batch!");
1008        }
1009
1010        if let PrimitiveKind::Picture { pic_index, .. } = prim_instance.kind {
1011            let pic_scratch_handle = ctx.scratch.frame.draws[draw_index.0 as usize].kind_scratch.unwrap_picture();
1012            let picture = &ctx.prim_store.pictures[pic_index.0];
1013            let picture_scratch = &ctx.scratch.frame.pictures[pic_scratch_handle];
1014            if let Some(snapshot) = picture.snapshot {
1015                if snapshot.detached {
1016                    return;
1017                }
1018            }
1019
1020            let blend_mode = BlendMode::PremultipliedAlpha;
1021            let prim_cache_address = ctx.globals.default_image_data;
1022
1023            match picture.raster_config {
1024                Some(ref raster_config) => {
1025                    // If the child picture was rendered in local space, we can safely
1026                    // interpolate the UV coordinates with perspective correction.
1027                    let brush_flags = brush_flags | BrushFlags::PERSPECTIVE_INTERPOLATION;
1028
1029                    let surface = &ctx.surfaces[raster_config.surface_index.0];
1030                    let mut local_clip_rect = prim_info.clip_chain.local_clip_rect;
1031
1032                    // If we are drawing with snapping enabled, form a simple transform that just applies
1033                    // the scale / translation from the raster transform. Otherwise, in edge cases where the
1034                    // intermediate surface has a non-identity but axis-aligned transform (e.g. a 180 degree
1035                    // rotation) it can be applied twice.
1036                    let transform_id = if surface.surface_spatial_node_index == surface.raster_spatial_node_index {
1037                        transform_id
1038                    } else {
1039                        let map_local_to_raster = SpaceMapper::new_with_target(
1040                            root_spatial_node_index,
1041                            surface.surface_spatial_node_index,
1042                            LayoutRect::max_rect(),
1043                            ctx.spatial_tree,
1044                        );
1045
1046                        let raster_rect = map_local_to_raster
1047                            .map(&prim_rect)
1048                            .unwrap();
1049
1050                        let sx = (raster_rect.max.x - raster_rect.min.x) / (prim_rect.max.x - prim_rect.min.x);
1051                        let sy = (raster_rect.max.y - raster_rect.min.y) / (prim_rect.max.y - prim_rect.min.y);
1052
1053                        let tx = raster_rect.min.x - sx * prim_rect.min.x;
1054                        let ty = raster_rect.min.y - sy * prim_rect.min.y;
1055
1056                        let transform = ScaleOffset::new(sx, sy, tx, ty);
1057
1058                        let raster_clip_rect = map_local_to_raster
1059                            .map(&prim_info.clip_chain.local_clip_rect)
1060                            .unwrap();
1061                        local_clip_rect = transform.unmap_rect(&raster_clip_rect);
1062
1063                        transforms.gpu.get_custom(transform.to_transform())
1064                    };
1065
1066                    let picture_prim_header = PrimitiveHeader {
1067                        local_rect: prim_rect,
1068                        local_clip_rect,
1069                        specific_prim_address: prim_cache_address.as_int(),
1070                        transform_id,
1071                        z: z_id,
1072                        render_task_address: self.batcher.render_task_address,
1073                        user_data: [0; 4], // Will be overridden by most uses
1074                    };
1075
1076                    let mut is_opaque = prim_info.clip_task_index == ClipTaskIndex::INVALID
1077                        && surface.is_opaque
1078                        && transform_id.is_2d_axis_aligned()
1079                        && !is_anti_aliased
1080                        && !prim_info.clip_chain.needs_mask;
1081
1082                    match raster_config.composite_mode {
1083                        PictureCompositeMode::TileCache { .. } => {
1084                            // TODO(gw): For now, TileCache is still a composite mode, even though
1085                            //           it will only exist as a top level primitive and never
1086                            //           be encountered during batching. Consider making TileCache
1087                            //           a standalone type, not a picture.
1088                            return;
1089                        }
1090                        PictureCompositeMode::IntermediateSurface { .. } => {
1091                            // TODO(gw): As an optimization, support making this a pass-through
1092                            //           and/or drawing directly from here when possible
1093                            //           (e.g. if not wrapped by filters / different spatial node).
1094                            return;
1095                        }
1096                        _=>{}
1097                    }
1098
1099                    let (clip_task_address, clip_mask_texture_id) = ctx.get_prim_clip_task_and_texture(
1100                        prim_info.clip_task_index,
1101                        render_tasks,
1102                    ).unwrap();
1103
1104                    let pic_task_id = picture_scratch.primary_render_task_id.unwrap();
1105
1106                    let (uv_rect_address, texture) = render_tasks.resolve_location(
1107                        pic_task_id,
1108
1109                    ).unwrap();
1110
1111                    // The set of input textures that most composite modes use,
1112                    // howevr some override it.
1113                    let textures = BatchTextures::prim_textured(
1114                        texture,
1115                        clip_mask_texture_id,
1116                    );
1117
1118                    let (key, prim_user_data, resource_address) = match raster_config.composite_mode {
1119                        PictureCompositeMode::TileCache { .. }
1120                        | PictureCompositeMode::IntermediateSurface { .. }
1121                        => return,
1122                        PictureCompositeMode::Filter(ref filter) => {
1123                            assert!(filter.is_visible());
1124                            match filter {
1125                                Filter::Blur { .. } => {
1126                                    let kind = BatchKind::Brush(
1127                                        BrushBatchKind::Image(ImageBufferKind::Texture2D)
1128                                    );
1129
1130                                    let key = BatchKey::new(
1131                                        kind,
1132                                        blend_mode,
1133                                        textures,
1134                                    );
1135
1136                                    let prim_user_data = ImageBrushUserData {
1137                                        color_mode: ShaderColorMode::Image,
1138                                        alpha_type: AlphaType::PremultipliedAlpha,
1139                                        raster_space: RasterizationSpace::Screen,
1140                                        opacity: 1.0,
1141                                    }.encode();
1142
1143                                    (key, prim_user_data, uv_rect_address.as_int())
1144                                }
1145                                Filter::DropShadows(shadows) => {
1146                                    // Draw an instance per shadow first, following by the content.
1147
1148                                    // The shadows and the content get drawn as a brush image.
1149                                    let kind = BatchKind::Brush(
1150                                        BrushBatchKind::Image(ImageBufferKind::Texture2D),
1151                                    );
1152
1153                                    // Gets the saved render task ID of the content, which is
1154                                    // deeper in the render task graph than the direct child.
1155                                    let secondary_id = picture_scratch.secondary_render_task_id.expect("no secondary!?");
1156                                    let content_source = {
1157                                        let secondary_task = &render_tasks[secondary_id];
1158                                        let texture_id = secondary_task.get_target_texture();
1159                                        TextureSource::TextureCache(
1160                                            texture_id,
1161                                            Swizzle::default(),
1162                                        )
1163                                    };
1164
1165                                    // Retrieve the UV rect addresses for shadow/content.
1166                                    let shadow_uv_rect_address = uv_rect_address;
1167                                    let shadow_textures = textures;
1168
1169                                    let content_uv_rect_address = render_tasks[secondary_id]
1170                                        .get_texture_address()
1171                                        .as_int();
1172
1173                                    // Build BatchTextures for shadow/content
1174                                    let content_textures = BatchTextures::prim_textured(
1175                                        content_source,
1176                                        clip_mask_texture_id,
1177                                    );
1178
1179                                    // Build batch keys for shadow/content
1180                                    let shadow_key = BatchKey::new(kind, blend_mode, shadow_textures);
1181                                    let content_key = BatchKey::new(kind, blend_mode, content_textures);
1182
1183                                    for (shadow, shadow_prim_address) in shadows.iter().zip(picture_scratch.extra_gpu_data.iter()) {
1184                                        let shadow_rect = picture_prim_header.local_rect.translate(shadow.offset);
1185
1186                                        let shadow_prim_header = PrimitiveHeader {
1187                                            local_rect: shadow_rect,
1188                                            specific_prim_address: shadow_prim_address.as_int(),
1189                                            z: z_id,
1190                                            user_data: ImageBrushUserData {
1191                                                color_mode: ShaderColorMode::Alpha,
1192                                                alpha_type: AlphaType::PremultipliedAlpha,
1193                                                raster_space: RasterizationSpace::Screen,
1194                                                opacity: 1.0,
1195                                            }.encode(),
1196                                            ..picture_prim_header
1197                                        };
1198                                        let shadow_prim_header_index = prim_headers.push(&shadow_prim_header);
1199
1200                                        self.add_brush_instance_to_batches(
1201                                            shadow_key,
1202                                            batch_features,
1203                                            bounding_rect,
1204                                            z_id,
1205                                            INVALID_SEGMENT_INDEX,
1206                                            EdgeMask::all(),
1207                                            clip_task_address,
1208                                            brush_flags,
1209                                            shadow_prim_header_index,
1210                                            shadow_uv_rect_address.as_int(),
1211                                        );
1212                                    }
1213
1214                                    // Update z_id for the content
1215                                    z_id = z_generator.next();
1216
1217                                    let prim_user_data = ImageBrushUserData {
1218                                        color_mode: ShaderColorMode::Image,
1219                                        alpha_type: AlphaType::PremultipliedAlpha,
1220                                        raster_space: RasterizationSpace::Screen,
1221                                        opacity: 1.0,
1222                                    }.encode();
1223
1224                                    (content_key, prim_user_data, content_uv_rect_address)
1225                                }
1226                                Filter::Opacity(_, amount) => {
1227                                    let amount = (amount * 65536.0) as i32;
1228
1229                                    let key = BatchKey::new(
1230                                        BatchKind::Brush(BrushBatchKind::Opacity),
1231                                        BlendMode::PremultipliedAlpha,
1232                                        textures,
1233                                    );
1234
1235                                    let prim_user_data = [
1236                                        uv_rect_address.as_int(),
1237                                        amount,
1238                                        0,
1239                                        0,
1240                                    ];
1241
1242                                    (key, prim_user_data, 0)
1243                                }
1244                                _ => {
1245                                    // Must be kept in sync with brush_blend.glsl
1246                                    let filter_mode = filter.as_int();
1247
1248                                    let user_data = match filter {
1249                                        Filter::Identity => 0x10000i32, // matches `Contrast(1)`
1250                                        Filter::Contrast(amount) |
1251                                        Filter::Grayscale(amount) |
1252                                        Filter::Invert(amount) |
1253                                        Filter::Saturate(amount) |
1254                                        Filter::Sepia(amount) |
1255                                        Filter::Brightness(amount) => {
1256                                            (amount * 65536.0) as i32
1257                                        }
1258                                        Filter::SrgbToLinear | Filter::LinearToSrgb => 0,
1259                                        Filter::HueRotate(angle) => {
1260                                            (0.01745329251 * angle * 65536.0) as i32
1261                                        }
1262                                        Filter::ColorMatrix(_) => {
1263                                            picture_scratch.extra_gpu_data[0].as_int()
1264                                        }
1265                                        Filter::Flood(_) => {
1266                                            picture_scratch.extra_gpu_data[0].as_int()
1267                                        }
1268
1269                                        // These filters are handled via different paths.
1270                                        Filter::ComponentTransfer |
1271                                        Filter::Blur { .. } |
1272                                        Filter::DropShadows(..) |
1273                                        Filter::Opacity(..) |
1274                                        Filter::SVGGraphNode(..) => unreachable!(),
1275                                    };
1276
1277                                    // Other filters that may introduce opacity are handled via different
1278                                    // paths.
1279                                    if let Filter::ColorMatrix(..) = filter {
1280                                        is_opaque = false;
1281                                    }
1282
1283                                    let blend_mode = if is_opaque {
1284                                        BlendMode::None
1285                                    } else {
1286                                        BlendMode::PremultipliedAlpha
1287                                    };
1288
1289                                    let key = BatchKey::new(
1290                                        BatchKind::Brush(BrushBatchKind::Blend),
1291                                        blend_mode,
1292                                        textures,
1293                                    );
1294
1295                                    let prim_user_data = [
1296                                        uv_rect_address.as_int(),
1297                                        filter_mode,
1298                                        user_data,
1299                                        0,
1300                                    ];
1301
1302                                    (key, prim_user_data, 0)
1303                                }
1304                            }
1305                        }
1306                        PictureCompositeMode::ComponentTransferFilter(handle) => {
1307                            // This is basically the same as the general filter case above
1308                            // except we store a little more data in the filter mode and
1309                            // a gpu cache handle in the user data.
1310                            let filter_data = &ctx.data_stores.filter_data[handle];
1311                            let filter_mode : i32 = Filter::ComponentTransfer.as_int() |
1312                                ((filter_data.data.r_func.to_int() << 28 |
1313                                  filter_data.data.g_func.to_int() << 24 |
1314                                  filter_data.data.b_func.to_int() << 20 |
1315                                  filter_data.data.a_func.to_int() << 16) as i32);
1316
1317                            let user_data = filter_data.gpu_buffer_address.as_int();
1318
1319                            let key = BatchKey::new(
1320                                BatchKind::Brush(BrushBatchKind::Blend),
1321                                BlendMode::PremultipliedAlpha,
1322                                textures,
1323                            );
1324
1325                            let prim_user_data = [
1326                                uv_rect_address.as_int(),
1327                                filter_mode,
1328                                user_data,
1329                                0,
1330                            ];
1331
1332                            (key, prim_user_data, 0)
1333                        }
1334                        PictureCompositeMode::MixBlend(mode) if BlendMode::from_mix_blend_mode(
1335                            mode,
1336                            ctx.use_advanced_blending,
1337                            !ctx.break_advanced_blend_batches,
1338                            ctx.use_dual_source_blending,
1339                        ).is_some() => {
1340                            let key = BatchKey::new(
1341                                BatchKind::Brush(
1342                                    BrushBatchKind::Image(ImageBufferKind::Texture2D),
1343                                ),
1344                                BlendMode::from_mix_blend_mode(
1345                                    mode,
1346                                    ctx.use_advanced_blending,
1347                                    !ctx.break_advanced_blend_batches,
1348                                    ctx.use_dual_source_blending,
1349                                ).unwrap(),
1350                                textures,
1351                            );
1352
1353                            let prim_user_data = ImageBrushUserData {
1354                                color_mode: match key.blend_mode {
1355                                    BlendMode::MultiplyDualSource => ShaderColorMode::MultiplyDualSource,
1356                                    _ => ShaderColorMode::Image,
1357                                },
1358                                alpha_type: AlphaType::PremultipliedAlpha,
1359                                raster_space: RasterizationSpace::Screen,
1360                                opacity: 1.0,
1361                            }.encode();
1362
1363                            (key, prim_user_data, uv_rect_address.as_int())
1364                        }
1365                        PictureCompositeMode::MixBlend(mode) => {
1366                            let backdrop_id = picture_scratch.secondary_render_task_id.expect("no backdrop!?");
1367
1368                            let color0 = render_tasks[backdrop_id].get_target_texture();
1369                            let color1 = render_tasks[pic_task_id].get_target_texture();
1370
1371                            // Create a separate brush instance for each batcher. For most cases,
1372                            // there is only one batcher. However, in the case of drawing onto
1373                            // a picture cache, there is one batcher per tile. Although not
1374                            // currently used, the implementation of mix-blend-mode now supports
1375                            // doing partial readbacks per-tile. In future, this will be enabled
1376                            // and allow mix-blends to operate on picture cache surfaces without
1377                            // a separate isolated intermediate surface.
1378
1379                            let batch_key = BatchKey::new(
1380                                BatchKind::Brush(
1381                                    BrushBatchKind::MixBlend {
1382                                        task_id: self.batcher.render_task_id,
1383                                        backdrop_id,
1384                                    },
1385                                ),
1386                                BlendMode::PremultipliedAlpha,
1387                                BatchTextures {
1388                                    input: TextureSet {
1389                                        colors: [
1390                                            TextureSource::TextureCache(
1391                                                color0,
1392                                                Swizzle::default(),
1393                                            ),
1394                                            TextureSource::TextureCache(
1395                                                color1,
1396                                                Swizzle::default(),
1397                                            ),
1398                                            TextureSource::Invalid,
1399                                        ],
1400                                    },
1401                                    clip_mask: clip_mask_texture_id,
1402                                },
1403                            );
1404                            let src_uv_address = render_tasks[pic_task_id].get_texture_address();
1405                            let readback_uv_address = render_tasks[backdrop_id].get_texture_address();
1406                            let prim_header = PrimitiveHeader {
1407                                user_data: [
1408                                    mode as u32 as i32,
1409                                    readback_uv_address.as_int(),
1410                                    src_uv_address.as_int(),
1411                                    0,
1412                                ],
1413                                ..picture_prim_header
1414                            };
1415                            let prim_header_index = prim_headers.push(&prim_header);
1416
1417                            let instance = BrushInstance {
1418                                segment_index: INVALID_SEGMENT_INDEX,
1419                                edge_flags: EdgeMask::all(),
1420                                clip_task_address,
1421                                brush_flags,
1422                                prim_header_index,
1423                                resource_address: 0,
1424                            };
1425
1426                            self.batcher.push_single_instance(
1427                                batch_key,
1428                                batch_features,
1429                                bounding_rect,
1430                                z_id,
1431                                PrimitiveInstanceData::from(instance),
1432                            );
1433
1434                            return;
1435                        }
1436                        PictureCompositeMode::Blit(_) => {
1437                            match picture.context_3d {
1438                                Picture3DContext::In { root_data: Some(_), .. } => {
1439                                    unreachable!("bug: should not have a raster_config");
1440                                }
1441                                Picture3DContext::In { root_data: None, .. } => {
1442                                    // TODO(gw): Store this inside the split picture so that we
1443                                    //           don't need to pass in extra_prim_gpu_address for
1444                                    //           every prim instance.
1445                                    // TODO(gw): Ideally we'd skip adding 3d child prims to batches
1446                                    //           without gpu cache address but it's currently
1447                                    //           used by the prepare pass. Refactor this!
1448                                    let extra_prim_gpu_address = match extra_prim_gpu_address {
1449                                        Some(prim_address) => prim_address,
1450                                        None => return,
1451                                    };
1452
1453                                    // Need a new z-id for each child preserve-3d context added
1454                                    // by this inner loop.
1455                                    let z_id = z_generator.next();
1456
1457                                    let prim_header = PrimitiveHeader {
1458                                        z: z_id,
1459                                        transform_id: transforms.gpu.get_id(
1460                                            prim_spatial_node_index,
1461                                            root_spatial_node_index,
1462                                            ctx.spatial_tree,
1463                                        ),
1464                                        user_data: [
1465                                            uv_rect_address.as_int(),
1466                                            BrushFlags::PERSPECTIVE_INTERPOLATION.bits() as i32,
1467                                            0,
1468                                            clip_task_address.0 as i32,
1469                                        ],
1470                                        ..picture_prim_header
1471                                    };
1472                                    let prim_header_index = prim_headers.push(&prim_header);
1473
1474                                    let key = BatchKey::new(
1475                                        BatchKind::SplitComposite,
1476                                        BlendMode::PremultipliedAlpha,
1477                                        textures,
1478                                    );
1479
1480                                    self.add_split_composite_instance_to_batches(
1481                                        key,
1482                                        BatchFeatures::CLIP_MASK,
1483                                        &prim_info.clip_chain.pic_coverage_rect,
1484                                        z_id,
1485                                        prim_header_index,
1486                                        extra_prim_gpu_address,
1487                                    );
1488
1489                                    return;
1490                                }
1491                                Picture3DContext::Out { .. } => {
1492                                    let textures = TextureSet {
1493                                        colors: [
1494                                            texture,
1495                                            TextureSource::Invalid,
1496                                            TextureSource::Invalid,
1497                                        ],
1498                                    };
1499                                    let batch_params = BrushBatchParameters::shared(
1500                                        BrushBatchKind::Image(ImageBufferKind::Texture2D),
1501                                        textures,
1502                                        ImageBrushUserData {
1503                                            color_mode: ShaderColorMode::Image,
1504                                            alpha_type: AlphaType::PremultipliedAlpha,
1505                                            raster_space: RasterizationSpace::Screen,
1506                                            opacity: 1.0,
1507                                        }.encode(),
1508                                        uv_rect_address.as_int(),
1509                                    );
1510
1511                                    let prim_header = PrimitiveHeader {
1512                                        specific_prim_address: prim_cache_address.as_int(),
1513                                        user_data: batch_params.prim_user_data,
1514                                        ..picture_prim_header
1515                                    };
1516                                    let prim_header_index = prim_headers.push(&prim_header);
1517
1518                                    let (opacity, blend_mode) = if is_opaque {
1519                                        (PrimitiveOpacity::opaque(), BlendMode::None)
1520                                    } else {
1521                                        (PrimitiveOpacity::translucent(), BlendMode::PremultipliedAlpha)
1522                                    };
1523
1524                                    self.add_segmented_prim_to_batch(
1525                                        None,
1526                                        opacity,
1527                                        &batch_params,
1528                                        blend_mode,
1529                                        batch_features,
1530                                        brush_flags,
1531                                        EdgeMask::all(),
1532                                        prim_header_index,
1533                                        bounding_rect,
1534                                        transform_metadata,
1535                                        z_id,
1536                                        prim_info.clip_task_index,
1537                                        ctx,
1538                                        render_tasks,
1539                                    );
1540
1541                                    return;
1542                                }
1543                            }
1544                        }
1545                        PictureCompositeMode::SVGFEGraph(..) => {
1546                            let kind = BatchKind::Brush(
1547                                BrushBatchKind::Image(ImageBufferKind::Texture2D)
1548                            );
1549                            let key = BatchKey::new(
1550                                kind,
1551                                blend_mode,
1552                                textures,
1553                            );
1554
1555                            let prim_user_data = ImageBrushUserData {
1556                                color_mode: ShaderColorMode::Image,
1557                                alpha_type: AlphaType::PremultipliedAlpha,
1558                                raster_space: RasterizationSpace::Screen,
1559                                opacity: 1.0,
1560                            }.encode();
1561
1562                            (key, prim_user_data, uv_rect_address.as_int())
1563                        }
1564                    };
1565
1566                    let prim_header = PrimitiveHeader {
1567                        z: z_id,
1568                        user_data: prim_user_data,
1569                        ..picture_prim_header
1570                    };
1571                    let prim_header_index = prim_headers.push(&prim_header);
1572
1573                    self.add_brush_instance_to_batches(
1574                        key,
1575                        batch_features,
1576                        bounding_rect,
1577                        z_id,
1578                        INVALID_SEGMENT_INDEX,
1579                        EdgeMask::all(),
1580                        clip_task_address,
1581                        brush_flags,
1582                        prim_header_index,
1583                        resource_address,
1584                    );
1585                }
1586                None => {
1587                    unreachable!();
1588                }
1589            }
1590
1591            return;
1592        }
1593
1594        let base_prim_header = PrimitiveHeader {
1595            local_rect: prim_rect,
1596            local_clip_rect: prim_info.clip_chain.local_clip_rect,
1597            transform_id,
1598            z: z_id,
1599            render_task_address: self.batcher.render_task_address,
1600            specific_prim_address: GpuBufferAddress::INVALID.as_int(), // Will be overridden by most uses
1601            user_data: [0; 4], // Will be overridden by most uses
1602        };
1603
1604        let common_data = ctx.data_stores.as_common_data(prim_instance);
1605
1606        let needs_blending = !common_data.opacity.is_opaque ||
1607            prim_info.clip_task_index != ClipTaskIndex::INVALID ||
1608            !transform_metadata.is_2d_axis_aligned ||
1609            is_anti_aliased;
1610
1611        let blend_mode = if needs_blending {
1612            BlendMode::PremultipliedAlpha
1613        } else {
1614            BlendMode::None
1615        };
1616
1617        let segment_instance_index = match prim_instance.kind {
1618            PrimitiveKind::Rectangle { .. }
1619            | PrimitiveKind::YuvImage { .. } => prim_info.segment_instance_index,
1620            _ => SegmentInstanceIndex::UNUSED,
1621        };
1622
1623        let (prim_cache_address, segments) = if segment_instance_index == SegmentInstanceIndex::UNUSED {
1624            (common_data.gpu_buffer_address, None)
1625        } else {
1626            let segment_instance = &ctx.scratch.frame.segment_instances[segment_instance_index];
1627            let segments = Some(&ctx.scratch.frame.segments[segment_instance.segments_range]);
1628            (segment_instance.gpu_data, segments)
1629        };
1630
1631        // The following primitives lower to the image brush shader in the same way.
1632        // For ImageBorder, the GPU block lives on per-instance scratch
1633        // (`ImageBorderScratch.gpu_address`), so override
1634        // `prim_cache_address` here.
1635        let mut prim_cache_address = prim_cache_address;
1636        let img_brush_data = match prim_instance.kind {
1637            PrimitiveKind::RadialGradient { .. } => {
1638                unreachable!("BUG: radial gradients should always use quad path");
1639            }
1640            PrimitiveKind::ConicGradient { .. } => {
1641                unreachable!("BUG: conic gradients should always use quad path");
1642            }
1643            PrimitiveKind::ImageBorder { data_handle, .. } => {
1644                let prim_data = &ctx.data_stores.image_border[data_handle];
1645                let ib_handle = prim_info.kind_scratch.unwrap_image_border();
1646                let ib_scratch = ctx.scratch.frame.image_border[ib_handle];
1647                prim_cache_address = ib_scratch.gpu_address;
1648                let brush_segments = &ctx.scratch.frame.segments[ib_scratch.brush_segments_range];
1649                Some((prim_data.kind.src_color, brush_segments))
1650            }
1651            _ => None,
1652        };
1653
1654        if let Some((src_color, brush_segments)) = img_brush_data {
1655            let src_color = render_tasks.resolve_location(src_color);
1656
1657            let (uv_rect_address, texture_source) = match src_color {
1658                Some(src) => src,
1659                None => {
1660                    return;
1661                }
1662            };
1663
1664            let textures = TextureSet::prim_textured(texture_source);
1665
1666            let prim_user_data = ImageBrushUserData {
1667                color_mode: ShaderColorMode::Image,
1668                alpha_type: AlphaType::PremultipliedAlpha,
1669                raster_space: RasterizationSpace::Local,
1670                opacity: 1.0,
1671            }.encode();
1672
1673            let prim_header = PrimitiveHeader {
1674                specific_prim_address: prim_cache_address.as_int(),
1675                user_data: prim_user_data,
1676                ..base_prim_header
1677            };
1678
1679            let batch_kind = BrushBatchKind::Image(texture_source.image_buffer_kind());
1680
1681            let batch_params = BrushBatchParameters::shared(
1682                batch_kind,
1683                textures,
1684                prim_user_data,
1685                uv_rect_address.as_int(),
1686            );
1687
1688            let segments = if brush_segments.is_empty() {
1689                None
1690            } else {
1691                Some(&brush_segments[..])
1692            };
1693
1694            let prim_header_index = prim_headers.push(&prim_header);
1695
1696            self.add_segmented_prim_to_batch(
1697                segments,
1698                common_data.opacity,
1699                &batch_params,
1700                blend_mode,
1701                batch_features,
1702                brush_flags,
1703                common_data.transformed_aa_edges,
1704                prim_header_index,
1705                bounding_rect,
1706                transform_metadata,
1707                z_id,
1708                prim_info.clip_task_index,
1709                ctx,
1710                render_tasks,
1711            );
1712
1713            return;
1714        }
1715
1716        match prim_instance.kind {
1717            // Handled above.
1718            PrimitiveKind::Picture { .. } => {}
1719            PrimitiveKind::RadialGradient { .. } => { }
1720            PrimitiveKind::ConicGradient { .. } => { }
1721            PrimitiveKind::ImageBorder { .. } => {}
1722            PrimitiveKind::BoxShadow { .. } => {
1723                unreachable!("BUG: Should not hit box-shadow here as they are handled by quad infra");
1724            }
1725            PrimitiveKind::NormalBorder { .. } => {
1726                let scratch_handle = prim_info.kind_scratch.unwrap_normal_border();
1727                let nb_scratch = ctx.scratch.frame.normal_border[scratch_handle];
1728                let prim_cache_address = nb_scratch.gpu_address;
1729                let task_ids = &ctx.scratch.frame.border_task_ids[nb_scratch.task_ids];
1730                let mut segment_data: SmallVec<[SegmentInstanceData; 8]> = SmallVec::new();
1731
1732                // Collect the segment instance data from each render
1733                // task for each valid edge / corner of the border.
1734
1735                for task_id in task_ids {
1736                    if let Some((uv_rect_address, texture)) = render_tasks.resolve_location(*task_id) {
1737                        segment_data.push(
1738                            SegmentInstanceData {
1739                                textures: TextureSet::prim_textured(texture),
1740                                specific_resource_address: uv_rect_address.as_int(),
1741                            }
1742                        );
1743                    }
1744                }
1745
1746                // TODO: it would be less error-prone to get this info from the texture cache.
1747                let image_buffer_kind = ImageBufferKind::Texture2D;
1748
1749                let batch_params = BrushBatchParameters::instanced(
1750                    BrushBatchKind::Image(image_buffer_kind),
1751                    ImageBrushUserData {
1752                        color_mode: ShaderColorMode::Image,
1753                        alpha_type: AlphaType::PremultipliedAlpha,
1754                        raster_space: RasterizationSpace::Local,
1755                        opacity: 1.0,
1756                    }.encode(),
1757                    segment_data,
1758                );
1759
1760                let prim_header = PrimitiveHeader {
1761                    specific_prim_address: prim_cache_address.as_int(),
1762                    user_data: batch_params.prim_user_data,
1763                    ..base_prim_header
1764                };
1765                let prim_header_index = prim_headers.push(&prim_header);
1766
1767                let brush_segments = &ctx.scratch.frame.segments[nb_scratch.brush_segments_range];
1768                self.add_segmented_prim_to_batch(
1769                    Some(brush_segments),
1770                    common_data.opacity,
1771                    &batch_params,
1772                    blend_mode,
1773                    batch_features,
1774                    brush_flags,
1775                    common_data.transformed_aa_edges,
1776                    prim_header_index,
1777                    bounding_rect,
1778                    transform_metadata,
1779                    z_id,
1780                    prim_info.clip_task_index,
1781                    ctx,
1782                    render_tasks,
1783                );
1784            }
1785            PrimitiveKind::TextRun { data_handle, .. } => {
1786                let text_run_scratch_handle = prim_info.kind_scratch.unwrap_text_run();
1787                let run_scratch = &ctx.scratch.frame.text_runs[text_run_scratch_handle];
1788                let subpx_dir = run_scratch.used_font.get_subpx_dir();
1789                let prim_data = &ctx.data_stores.text_run[data_handle];
1790
1791                let glyph_keys = &ctx.scratch.frame.glyph_keys[run_scratch.glyph_keys_range];
1792
1793                // `local_rect.p0` is the run anchor (the normalized prim rect
1794                // origin). In device mode the shader transforms it to device
1795                // space and adds the per-glyph device offsets stored at
1796                // `gpu_address`. `user_data` carries the raster scale (for
1797                // local-raster mode's raster -> local mapping) and the mode flag
1798                // (0 = device, 1 = local raster).
1799                let prim_header = PrimitiveHeader {
1800                    local_rect: run_scratch.local_rect,
1801                    specific_prim_address: run_scratch.gpu_address.as_int(),
1802                    user_data: [
1803                        (run_scratch.raster_scale * 65535.0).round() as i32,
1804                        run_scratch.local_raster as i32,
1805                        0,
1806                        0,
1807                    ],
1808                    ..base_prim_header
1809                };
1810                let prim_header_index = prim_headers.push(&prim_header);
1811                let base_instance = GlyphInstance::new(
1812                    prim_header_index,
1813                );
1814                let batcher = &mut self.batcher;
1815
1816                let (clip_task_address, clip_mask_texture_id) = ctx.get_prim_clip_task_and_texture(
1817                    prim_info.clip_task_index,
1818                    render_tasks,
1819                ).unwrap();
1820
1821                // The run_scratch.used_font.clone() is here instead of inline in the `fetch_glyph`
1822                // function call to work around a miscompilation.
1823                // https://github.com/rust-lang/rust/issues/80111
1824                let font = run_scratch.used_font.clone();
1825                ctx.resource_cache.fetch_glyphs(
1826                    font,
1827                    &glyph_keys,
1828                    &gpu_buffer_builder.f32,
1829                    &mut self.glyph_fetch_buffer,
1830                    |texture_id, glyph_format, glyphs| {
1831                        debug_assert_ne!(texture_id, TextureSource::Invalid);
1832
1833                        let subpx_dir = subpx_dir.limit_by(glyph_format);
1834
1835                        let textures = BatchTextures::prim_textured(
1836                            texture_id,
1837                            clip_mask_texture_id,
1838                        );
1839
1840                        let kind = BatchKind::TextRun(glyph_format);
1841
1842                        let (blend_mode, color_mode) = match glyph_format {
1843                            GlyphFormat::Subpixel |
1844                            GlyphFormat::TransformedSubpixel => {
1845                                debug_assert!(ctx.use_dual_source_blending);
1846                                (
1847                                    BlendMode::SubpixelDualSource,
1848                                    ShaderColorMode::SubpixelDualSource,
1849                                )
1850                            }
1851                            GlyphFormat::Alpha |
1852                            GlyphFormat::TransformedAlpha |
1853                            GlyphFormat::Bitmap => {
1854                                (
1855                                    BlendMode::PremultipliedAlpha,
1856                                    ShaderColorMode::Alpha,
1857                                )
1858                            }
1859                            GlyphFormat::ColorBitmap => {
1860                                (
1861                                    BlendMode::PremultipliedAlpha,
1862                                    if prim_data.shadow {
1863                                        // Ignore color and only sample alpha when shadowing.
1864                                        ShaderColorMode::BitmapShadow
1865                                    } else {
1866                                        ShaderColorMode::ColorBitmap
1867                                    },
1868                                )
1869                            }
1870                        };
1871
1872                        // Calculate a tighter bounding rect of just the glyphs passed to this
1873                        // callback from request_glyphs(), rather than using the bounds of the
1874                        // entire text run. This improves batching when glyphs are fragmented
1875                        // over multiple textures in the texture cache.
1876                        // This mirrors the glyph positioning in the ps_text_run shader. The
1877                        // TRANSFORM_GLYPHS branch covers device mode for 2D rotated/skewed
1878                        // glyphs; the other branch covers device-mode axis-aligned and
1879                        // local-raster mode (distinguished by `run_scratch.raster_scale`).
1880                        // `text_offset` is zero because glyph positions are stored absolutely
1881                        // (relative to the prim origin via `local_rect.min`), not relative to
1882                        // a separate snapped reference-frame offset; the TRANSFORM_GLYPHS
1883                        // branch's `raster_text_offset` then reduces to the reference-frame
1884                        // device snap that `request_resources` applies.
1885                        let tight_bounding_rect = {
1886                            let snap_bias = match subpx_dir {
1887                                SubpixelDirection::None => DeviceVector2D::new(0.5, 0.5),
1888                                SubpixelDirection::Horizontal => DeviceVector2D::new(0.125, 0.5),
1889                                SubpixelDirection::Vertical => DeviceVector2D::new(0.5, 0.125),
1890                            };
1891                            let text_offset = LayoutVector2D::zero();
1892
1893                            let pic_bounding_rect = if run_scratch.used_font.flags.contains(FontInstanceFlags::TRANSFORM_GLYPHS) {
1894                                let mut device_bounding_rect = DeviceRect::default();
1895
1896                                let glyph_transform = ctx.spatial_tree.get_relative_transform(
1897                                    prim_spatial_node_index,
1898                                    root_spatial_node_index,
1899                                ).into_transform()
1900                                    .with_destination::<WorldPixel>()
1901                                    .then(&euclid::Transform3D::from_scale(ctx.global_device_pixel_scale));
1902
1903                                let glyph_translation = DeviceVector2D::new(glyph_transform.m41, glyph_transform.m42);
1904
1905                                let mut use_tight_bounding_rect = true;
1906                                for glyph in glyphs {
1907                                    let glyph_offset = prim_data.glyphs[glyph.index_in_text_run as usize].point + prim_header.local_rect.min.to_vector();
1908
1909                                    let transformed_offset = match glyph_transform.transform_point2d(glyph_offset) {
1910                                        Some(transformed_offset) => transformed_offset,
1911                                        None => {
1912                                            use_tight_bounding_rect = false;
1913                                            break;
1914                                        }
1915                                    };
1916                                    let raster_glyph_offset = (transformed_offset + snap_bias).floor();
1917                                    let raster_text_offset = (
1918                                        glyph_transform.transform_vector2d(text_offset) +
1919                                        glyph_translation +
1920                                        DeviceVector2D::new(0.5, 0.5)
1921                                    ).floor() - glyph_translation;
1922
1923                                    let device_glyph_rect = DeviceRect::from_origin_and_size(
1924                                        glyph.offset + raster_glyph_offset.to_vector() + raster_text_offset,
1925                                        glyph.size.to_f32(),
1926                                    );
1927
1928                                    device_bounding_rect = device_bounding_rect.union(&device_glyph_rect);
1929                                }
1930
1931                                if use_tight_bounding_rect {
1932                                    let map_device_to_surface: SpaceMapper<PicturePixel, DevicePixel> = SpaceMapper::new_with_target(
1933                                        root_spatial_node_index,
1934                                        surface_spatial_node_index,
1935                                        device_bounding_rect,
1936                                        ctx.spatial_tree,
1937                                    );
1938
1939                                    match map_device_to_surface.unmap(&device_bounding_rect) {
1940                                        Some(r) => r.intersection(bounding_rect),
1941                                        None => Some(*bounding_rect),
1942                                    }
1943                                } else {
1944                                    Some(*bounding_rect)
1945                                }
1946                            } else {
1947                                let mut local_bounding_rect = LayoutRect::default();
1948
1949                                let glyph_raster_scale = run_scratch.raster_scale * ctx.global_device_pixel_scale.get();
1950
1951                                for glyph in glyphs {
1952                                    let glyph_offset = prim_data.glyphs[glyph.index_in_text_run as usize].point + prim_header.local_rect.min.to_vector();
1953                                    let glyph_scale = LayoutToDeviceScale::new(glyph_raster_scale / glyph.scale);
1954                                    let raster_glyph_offset = (glyph_offset * LayoutToDeviceScale::new(glyph_raster_scale) + snap_bias).floor() / glyph.scale;
1955                                    let local_glyph_rect = LayoutRect::from_origin_and_size(
1956                                        (glyph.offset + raster_glyph_offset.to_vector()) / glyph_scale + text_offset,
1957                                        glyph.size.to_f32() / glyph_scale,
1958                                    );
1959
1960                                    local_bounding_rect = local_bounding_rect.union(&local_glyph_rect);
1961                                }
1962
1963                                let map_prim_to_surface: SpaceMapper<LayoutPixel, PicturePixel> = SpaceMapper::new_with_target(
1964                                    surface_spatial_node_index,
1965                                    prim_spatial_node_index,
1966                                    *bounding_rect,
1967                                    ctx.spatial_tree,
1968                                );
1969                                map_prim_to_surface.map(&local_bounding_rect)
1970                            };
1971
1972                            let intersected = match pic_bounding_rect {
1973                                // The text run may have been clipped, for example if part of it is offscreen.
1974                                // So intersect our result with the original bounding rect.
1975                                Some(rect) => rect.intersection(bounding_rect).unwrap_or_else(PictureRect::zero),
1976                                // If space mapping went off the rails, fall back to the old behavior.
1977                                //TODO: consider skipping the glyph run completely in this case.
1978                                None => *bounding_rect,
1979                            };
1980
1981                            intersected
1982                        };
1983
1984                        let key = BatchKey::new(kind, blend_mode, textures);
1985
1986                        let batch = batcher.alpha_batch_list.set_params_and_get_batch(
1987                            key,
1988                            batch_features,
1989                            &tight_bounding_rect,
1990                            z_id,
1991                        );
1992
1993                        batch.reserve(glyphs.len());
1994                        for glyph in glyphs {
1995                            batch.push(base_instance.build(
1996                                clip_task_address,
1997                                subpx_dir,
1998                                glyph.index_in_text_run,
1999                                glyph.uv_rect_address,
2000                                color_mode,
2001                                glyph.subpx_offset_x,
2002                                glyph.subpx_offset_y,
2003                                glyph.is_packed_glyph,
2004                            ));
2005                        }
2006                    },
2007                );
2008            }
2009            PrimitiveKind::LineDecoration { .. } => {
2010                let scratch_handle = prim_info.kind_scratch.unwrap_line_decoration();
2011                let line_dec_scratch = ctx.scratch.frame.line_decoration[scratch_handle];
2012                let render_task_id = line_dec_scratch.task_id;
2013                let prim_cache_address = line_dec_scratch.gpu_address;
2014
2015                let (clip_task_address, clip_mask_texture_id) = ctx.get_prim_clip_task_and_texture(
2016                    prim_info.clip_task_index,
2017                    render_tasks,
2018                ).unwrap();
2019
2020                let (batch_kind, textures, prim_user_data, specific_resource_address) = if render_task_id != RenderTaskId::INVALID {
2021                    let (uv_rect_address, texture) = render_tasks.resolve_location(render_task_id).unwrap();
2022                    let textures = BatchTextures::prim_textured(
2023                        texture,
2024                        clip_mask_texture_id,
2025                    );
2026                    (
2027                        BrushBatchKind::Image(texture.image_buffer_kind()),
2028                        textures,
2029                        ImageBrushUserData {
2030                            color_mode: ShaderColorMode::Image,
2031                            alpha_type: AlphaType::PremultipliedAlpha,
2032                            raster_space: RasterizationSpace::Local,
2033                            opacity: 1.0,
2034                        }.encode(),
2035                        uv_rect_address.as_int(),
2036                    )
2037                } else {
2038                    (
2039                        BrushBatchKind::Solid,
2040                        BatchTextures::prim_untextured(clip_mask_texture_id),
2041                        [get_shader_opacity(1.0), 0, 0, 0],
2042                        0,
2043                    )
2044                };
2045
2046                let prim_header = PrimitiveHeader {
2047                    specific_prim_address: prim_cache_address.as_int(),
2048                    user_data: prim_user_data,
2049                    ..base_prim_header
2050                };
2051                let prim_header_index = prim_headers.push(&prim_header);
2052
2053                let batch_key = BatchKey {
2054                    blend_mode,
2055                    kind: BatchKind::Brush(batch_kind),
2056                    textures,
2057                };
2058
2059                self.add_brush_instance_to_batches(
2060                    batch_key,
2061                    batch_features,
2062                    bounding_rect,
2063                    z_id,
2064                    INVALID_SEGMENT_INDEX,
2065                    common_data.transformed_aa_edges,
2066                    clip_task_address,
2067                    brush_flags | BrushFlags::PERSPECTIVE_INTERPOLATION,
2068                    prim_header_index,
2069                    specific_resource_address,
2070                );
2071            }
2072            PrimitiveKind::Rectangle { .. } => {
2073                let batch_params = BrushBatchParameters::shared(
2074                    BrushBatchKind::Solid,
2075                    TextureSet::UNTEXTURED,
2076                    [get_shader_opacity(1.0), 0, 0, 0],
2077                    0,
2078                );
2079
2080                let prim_header = PrimitiveHeader {
2081                    specific_prim_address: prim_cache_address.as_int(),
2082                    user_data: batch_params.prim_user_data,
2083                    ..base_prim_header
2084                };
2085                let prim_header_index = prim_headers.push(&prim_header);
2086
2087                self.add_segmented_prim_to_batch(
2088                    segments,
2089                    common_data.opacity,
2090                    &batch_params,
2091                    blend_mode,
2092                    batch_features,
2093                    brush_flags,
2094                    common_data.transformed_aa_edges,
2095                    prim_header_index,
2096                    bounding_rect,
2097                    transform_metadata,
2098                    z_id,
2099                    prim_info.clip_task_index,
2100                    ctx,
2101                    render_tasks,
2102                );
2103            }
2104            PrimitiveKind::YuvImage { data_handle, .. } => {
2105                let segment_instance_index = prim_info.segment_instance_index;
2106                if prim_info.compositor_surface_kind.needs_cutout() {
2107                    self.add_compositor_surface_cutout(
2108                        prim_rect,
2109                        prim_info.clip_chain.local_clip_rect,
2110                        prim_info.clip_task_index,
2111                        transform_id,
2112                        z_id,
2113                        bounding_rect,
2114                        ctx,
2115                        render_tasks,
2116                        prim_headers,
2117                    );
2118
2119                    return;
2120                }
2121
2122                let yuv_image_data = &ctx.data_stores.yuv_image[data_handle].kind;
2123                let mut textures = TextureSet::UNTEXTURED;
2124                let mut uv_rect_addresses = [0; 3];
2125
2126                //yuv channel
2127                let channel_count = yuv_image_data.format.get_plane_num();
2128                debug_assert!(channel_count <= 3);
2129                for channel in 0 .. channel_count {
2130
2131                    let src_channel = render_tasks.resolve_location(yuv_image_data.src_yuv[channel]);
2132
2133                    let (uv_rect_address, texture_source) = match src_channel {
2134                        Some(src) => src,
2135                        None => {
2136                            warn!("Warnings: skip a PrimitiveKind::YuvImage");
2137                            return;
2138                        }
2139                    };
2140
2141                    textures.colors[channel] = texture_source;
2142                    uv_rect_addresses[channel] = uv_rect_address.as_int();
2143                }
2144
2145                // All yuv textures should be the same type.
2146                let buffer_kind = textures.colors[0].image_buffer_kind();
2147                assert!(
2148                    textures.colors[1 .. yuv_image_data.format.get_plane_num()]
2149                        .iter()
2150                        .all(|&tid| buffer_kind == tid.image_buffer_kind())
2151                );
2152
2153                let kind = BrushBatchKind::YuvImage(
2154                    buffer_kind,
2155                    yuv_image_data.format,
2156                    yuv_image_data.color_depth,
2157                    yuv_image_data.color_space,
2158                    yuv_image_data.color_range,
2159                );
2160
2161                let batch_params = BrushBatchParameters::shared(
2162                    kind,
2163                    textures,
2164                    [
2165                        uv_rect_addresses[0],
2166                        uv_rect_addresses[1],
2167                        uv_rect_addresses[2],
2168                        0,
2169                    ],
2170                    0,
2171                );
2172
2173                debug_assert_ne!(segment_instance_index, SegmentInstanceIndex::INVALID);
2174
2175                let prim_header = PrimitiveHeader {
2176                    specific_prim_address: prim_cache_address.as_int(),
2177                    user_data: batch_params.prim_user_data,
2178                    ..base_prim_header
2179                };
2180                let prim_header_index = prim_headers.push(&prim_header);
2181
2182                self.add_segmented_prim_to_batch(
2183                    segments,
2184                    common_data.opacity,
2185                    &batch_params,
2186                    blend_mode,
2187                    batch_features,
2188                    brush_flags,
2189                    common_data.transformed_aa_edges,
2190                    prim_header_index,
2191                    bounding_rect,
2192                    transform_metadata,
2193                    z_id,
2194                    prim_info.clip_task_index,
2195                    ctx,
2196                    render_tasks,
2197                );
2198            }
2199            PrimitiveKind::Image { data_handle, .. } => {
2200                let img_scratch_handle = prim_info.kind_scratch.unwrap_image();
2201                if prim_info.compositor_surface_kind.needs_cutout() {
2202                    self.add_compositor_surface_cutout(
2203                        prim_rect,
2204                        prim_info.clip_chain.local_clip_rect,
2205                        prim_info.clip_task_index,
2206                        transform_id,
2207                        z_id,
2208                        bounding_rect,
2209                        ctx,
2210                        render_tasks,
2211                        prim_headers,
2212                    );
2213
2214                    return;
2215                }
2216
2217                let image_data = &ctx.data_stores.image[data_handle].kind;
2218                let image_scratch = &ctx.scratch.frame.images[img_scratch_handle];
2219                let visible_tiles = &ctx.scratch.frame.visible_image_tiles[image_scratch.visible_tiles];
2220                let prim_user_data = ImageBrushUserData {
2221                    color_mode: ShaderColorMode::Image,
2222                    alpha_type: image_data.alpha_type,
2223                    raster_space: RasterizationSpace::Local,
2224                    opacity: 1.0,
2225                }.encode();
2226
2227                let blend_mode = if needs_blending {
2228                    match image_data.alpha_type {
2229                        AlphaType::PremultipliedAlpha => BlendMode::PremultipliedAlpha,
2230                        AlphaType::Alpha => BlendMode::Alpha,
2231                    }
2232                } else {
2233                    BlendMode::None
2234                };
2235
2236                if visible_tiles.is_empty() {
2237                    if cfg!(debug_assertions) {
2238                        match ctx.resource_cache.get_image_properties(image_data.key) {
2239                            Some(ImageProperties { tiling: None, .. }) | None => (),
2240                            other => panic!("Non-tiled image with no visible images detected! Properties {:?}", other),
2241                        }
2242                    }
2243
2244                    let src_color = render_tasks.resolve_location(image_scratch.src_color);
2245
2246                    let (uv_rect_address, texture_source) = match src_color {
2247                        Some(src) => src,
2248                        None => {
2249                            return;
2250                        }
2251                    };
2252
2253                    let batch_params = BrushBatchParameters::shared(
2254                        BrushBatchKind::Image(texture_source.image_buffer_kind()),
2255                        TextureSet::prim_textured(texture_source),
2256                        prim_user_data,
2257                        uv_rect_address.as_int(),
2258                    );
2259
2260                    let (prim_cache_address, segments) = if prim_info.segment_instance_index == SegmentInstanceIndex::UNUSED {
2261                        (image_scratch.gpu_address, None)
2262                    } else {
2263                        let segment_instance = &ctx.scratch.frame.segment_instances[prim_info.segment_instance_index];
2264                        let segments = Some(&ctx.scratch.frame.segments[segment_instance.segments_range]);
2265                        (segment_instance.gpu_data, segments)
2266                    };
2267
2268                    let local_rect = image_scratch.adjustment.map_local_rect(&prim_rect);
2269                    let local_clip_rect = image_scratch.tight_local_clip_rect
2270                        .intersection_unchecked(&local_rect);
2271
2272                    let prim_header = PrimitiveHeader {
2273                        local_rect,
2274                        local_clip_rect,
2275                        specific_prim_address: prim_cache_address.as_int(),
2276                        user_data: batch_params.prim_user_data,
2277                        ..base_prim_header
2278                    };
2279
2280                    let prim_header_index = prim_headers.push(&prim_header);
2281
2282                    let brush_flags = match image_scratch.normalized_uvs {
2283                        true => brush_flags | BrushFlags::NORMALIZED_UVS,
2284                        false => brush_flags,
2285                    };
2286
2287                    self.add_segmented_prim_to_batch(
2288                        segments,
2289                        common_data.opacity,
2290                        &batch_params,
2291                        blend_mode,
2292                        batch_features,
2293                        brush_flags,
2294                        common_data.transformed_aa_edges,
2295                        prim_header_index,
2296                        bounding_rect,
2297                        transform_metadata,
2298                        z_id,
2299                        prim_info.clip_task_index,
2300                        ctx,
2301                        render_tasks,
2302                    );
2303                } else {
2304                    const VECS_PER_SPECIFIC_BRUSH: usize = 3;
2305                    let max_tiles_per_header = (MAX_VERTEX_TEXTURE_WIDTH - VECS_PER_SPECIFIC_BRUSH) / VECS_PER_SEGMENT;
2306
2307                    let (clip_task_address, clip_mask_texture_id) = ctx.get_prim_clip_task_and_texture(
2308                        prim_info.clip_task_index,
2309                        render_tasks,
2310                    ).unwrap();
2311
2312                    // use temporary block storage since we don't know the number of visible tiles beforehand
2313                    let mut gpu_blocks = Vec::<GpuBufferBlockF>::with_capacity(3 + max_tiles_per_header * 2);
2314                    for chunk in visible_tiles.chunks(max_tiles_per_header) {
2315                        gpu_blocks.clear();
2316                        gpu_blocks.push(image_data.color.premultiplied().into()); //color
2317                        gpu_blocks.push(PremultipliedColorF::WHITE.into()); //bg color
2318                        gpu_blocks.push([-1.0, 0.0, 0.0, 0.0].into()); //stretch size
2319                        // negative first value makes the shader code ignore it and use the local size instead
2320                        for tile in chunk {
2321                            let tile_rect = tile.local_rect.translate(-prim_rect.min.to_vector());
2322                            gpu_blocks.push(tile_rect.into());
2323                            gpu_blocks.push([0.0; 4].into());
2324                        }
2325
2326                        let mut writer = gpu_buffer_builder.f32.write_blocks(gpu_blocks.len());
2327                        for block in &gpu_blocks {
2328                            writer.push_one(*block);
2329                        }
2330                        let specific_prim_address = writer.finish();
2331
2332                        let prim_header = PrimitiveHeader {
2333                            local_clip_rect: image_scratch.tight_local_clip_rect,
2334                            specific_prim_address: specific_prim_address.as_int(),
2335                            user_data: prim_user_data,
2336                            ..base_prim_header
2337                        };
2338                        let prim_header_index = prim_headers.push(&prim_header);
2339
2340                        for (i, tile) in chunk.iter().enumerate() {
2341                            let (uv_rect_address, texture) = match render_tasks.resolve_location(tile.src_color) {
2342                                Some(result) => result,
2343                                None => {
2344                                    return;
2345                                }
2346                            };
2347
2348                            let textures = BatchTextures::prim_textured(
2349                                texture,
2350                                clip_mask_texture_id,
2351                            );
2352
2353                            let batch_key = BatchKey {
2354                                blend_mode,
2355                                kind: BatchKind::Brush(BrushBatchKind::Image(texture.image_buffer_kind())),
2356                                textures,
2357                            };
2358
2359                            self.add_brush_instance_to_batches(
2360                                batch_key,
2361                                batch_features,
2362                                bounding_rect,
2363                                z_id,
2364                                i as i32,
2365                                tile.edge_flags,
2366                                clip_task_address,
2367                                brush_flags | BrushFlags::SEGMENT_RELATIVE | BrushFlags::PERSPECTIVE_INTERPOLATION,
2368                                prim_header_index,
2369                                uv_rect_address.as_int(),
2370                            );
2371                        }
2372                    }
2373                }
2374            }
2375            PrimitiveKind::LinearGradient { .. } => {
2376                unreachable!("BUG: linear gradients should always use quad path");
2377            }
2378            PrimitiveKind::BackdropCapture { .. } => {}
2379            PrimitiveKind::BackdropRender { .. } => {
2380                let scratch_handle = prim_info.kind_scratch.unwrap_backdrop_render();
2381                let blend_mode = BlendMode::PremultipliedAlpha;
2382                let pic_task_id = Some(ctx.scratch.frame.backdrop_render[scratch_handle].src_task_id);
2383
2384                let (clip_task_address, clip_mask_texture_id) = ctx.get_prim_clip_task_and_texture(
2385                    prim_info.clip_task_index,
2386                    render_tasks,
2387                ).unwrap();
2388
2389                let kind = BatchKind::Brush(
2390                    BrushBatchKind::Image(ImageBufferKind::Texture2D)
2391                );
2392                let (_, texture) = render_tasks.resolve_location(pic_task_id).unwrap();
2393                let textures = BatchTextures::prim_textured(
2394                    texture,
2395                    clip_mask_texture_id,
2396                );
2397                let key = BatchKey::new(
2398                    kind,
2399                    blend_mode,
2400                    textures,
2401                );
2402
2403                let prim_header = PrimitiveHeader {
2404                    specific_prim_address: ctx.globals.default_image_data.as_int(),
2405                    user_data: ImageBrushUserData {
2406                        color_mode: ShaderColorMode::Image,
2407                        alpha_type: AlphaType::PremultipliedAlpha,
2408                        raster_space: RasterizationSpace::Screen,
2409                        opacity: 1.0,
2410                    }.encode(),
2411                    ..base_prim_header
2412                };
2413                let prim_header_index = prim_headers.push(&prim_header);
2414
2415                let pic_task = &render_tasks[pic_task_id.unwrap()];
2416                let pic_info = match pic_task.kind {
2417                    RenderTaskKind::Picture(ref info) => info,
2418                    _ => panic!("bug: not a picture"),
2419                };
2420                let target_rect = pic_task.get_target_rect();
2421
2422                let backdrop_rect = DeviceRect::from_origin_and_size(
2423                    pic_info.content_origin,
2424                    target_rect.size().to_f32(),
2425                );
2426
2427                let map_prim_to_backdrop = SpaceMapper::new_with_target(
2428                    pic_info.surface_spatial_node_index,
2429                    prim_spatial_node_index,
2430                    WorldRect::max_rect(),
2431                    ctx.spatial_tree,
2432                );
2433
2434                let points = [
2435                    map_prim_to_backdrop.map_point(prim_rect.top_left()),
2436                    map_prim_to_backdrop.map_point(prim_rect.top_right()),
2437                    map_prim_to_backdrop.map_point(prim_rect.bottom_left()),
2438                    map_prim_to_backdrop.map_point(prim_rect.bottom_right()),
2439                ];
2440
2441                if points.iter().any(|p| p.is_none()) {
2442                    return;
2443                }
2444
2445                let uvs = [
2446                    calculate_screen_uv(points[0].unwrap() * pic_info.device_pixel_scale, backdrop_rect),
2447                    calculate_screen_uv(points[1].unwrap() * pic_info.device_pixel_scale, backdrop_rect),
2448                    calculate_screen_uv(points[2].unwrap() * pic_info.device_pixel_scale, backdrop_rect),
2449                    calculate_screen_uv(points[3].unwrap() * pic_info.device_pixel_scale, backdrop_rect),
2450                ];
2451
2452                let source = ImageSource {
2453                    p0: target_rect.min.to_f32(),
2454                    p1: target_rect.max.to_f32(),
2455                    user_data: [0.0; 4],
2456                    uv_rect_kind: UvRectKind::Quad {
2457                        top_left: uvs[0],
2458                        top_right: uvs[1],
2459                        bottom_left: uvs[2],
2460                        bottom_right: uvs[3],
2461                    },
2462                };
2463
2464                let uv_rect_handle = source.write_gpu_blocks(&mut gpu_buffer_builder.f32);
2465                let uv_rect_address = gpu_buffer_builder.f32.resolve_handle(uv_rect_handle);
2466
2467                self.add_brush_instance_to_batches(
2468                    key,
2469                    batch_features,
2470                    bounding_rect,
2471                    z_id,
2472                    INVALID_SEGMENT_INDEX,
2473                    EdgeMask::all(),
2474                    clip_task_address,
2475                    brush_flags,
2476                    prim_header_index,
2477                    uv_rect_address.as_int(),
2478                );
2479            }
2480        }
2481    }
2482
2483    /// Draw a (potentially masked) alpha cutout so that a video underlay will be blended
2484    /// through by the compositor
2485    fn add_compositor_surface_cutout(
2486        &mut self,
2487        prim_rect: LayoutRect,
2488        local_clip_rect: LayoutRect,
2489        clip_task_index: ClipTaskIndex,
2490        transform_id: GpuTransformId,
2491        z_id: ZBufferId,
2492        bounding_rect: &PictureRect,
2493        ctx: &RenderTargetContext,
2494        render_tasks: &RenderTaskGraph,
2495        prim_headers: &mut PrimitiveHeaders,
2496    ) {
2497        let (clip_task_address, clip_mask_texture_id) = ctx.get_prim_clip_task_and_texture(
2498            clip_task_index,
2499            render_tasks,
2500        ).unwrap();
2501
2502        let prim_header = PrimitiveHeader {
2503            local_rect: prim_rect,
2504            local_clip_rect,
2505            specific_prim_address: ctx.globals.default_black_rect_address.as_int(),
2506            transform_id,
2507            z: z_id,
2508            render_task_address: self.batcher.render_task_address,
2509            user_data: [get_shader_opacity(1.0), 0, 0, 0],
2510        };
2511        let prim_header_index = prim_headers.push(&prim_header);
2512
2513        let batch_key = BatchKey {
2514            blend_mode: BlendMode::PremultipliedDestOut,
2515            kind: BatchKind::Brush(BrushBatchKind::Solid),
2516            textures: BatchTextures::prim_untextured(clip_mask_texture_id),
2517        };
2518
2519        self.add_brush_instance_to_batches(
2520            batch_key,
2521            BatchFeatures::ALPHA_PASS | BatchFeatures::CLIP_MASK,
2522            bounding_rect,
2523            z_id,
2524            INVALID_SEGMENT_INDEX,
2525            EdgeMask::empty(),
2526            clip_task_address,
2527            BrushFlags::empty(),
2528            prim_header_index,
2529            0,
2530        );
2531    }
2532
2533    /// Add a single segment instance to a batch.
2534    ///
2535    /// `edge_aa_mask` Specifies the edges that are *allowed* to have anti-aliasing, if and only
2536    /// if the segments enable it.
2537    /// In other words passing EdgeAaSegmentFlags::all() does not necessarily mean all edges will
2538    /// be anti-aliased, only that they could be.
2539    fn add_segment_to_batch(
2540        &mut self,
2541        segment: &BrushSegment,
2542        segment_data: &SegmentInstanceData,
2543        segment_index: i32,
2544        batch_kind: BrushBatchKind,
2545        prim_header_index: PrimitiveHeaderIndex,
2546        alpha_blend_mode: BlendMode,
2547        features: BatchFeatures,
2548        brush_flags: BrushFlags,
2549        edge_aa_mask: EdgeMask,
2550        bounding_rect: &PictureRect,
2551        transform_metadata: TransformMetadata,
2552        z_id: ZBufferId,
2553        prim_opacity: PrimitiveOpacity,
2554        clip_task_index: ClipTaskIndex,
2555        ctx: &RenderTargetContext,
2556        render_tasks: &RenderTaskGraph,
2557    ) {
2558        debug_assert!(clip_task_index != ClipTaskIndex::INVALID);
2559
2560        // Get GPU address of clip task for this segment, or None if
2561        // the entire segment is clipped out.
2562        if let Some((clip_task_address, clip_mask)) = ctx.get_clip_task_and_texture(
2563            clip_task_index,
2564            segment_index,
2565            render_tasks,
2566        ) {
2567            // If a got a valid (or OPAQUE) clip task address, add the segment.
2568            let is_inner = segment.edge_flags.is_empty();
2569            let needs_blending = !prim_opacity.is_opaque ||
2570                                 clip_task_address != OPAQUE_TASK_ADDRESS ||
2571                                 (!is_inner && !transform_metadata.is_2d_axis_aligned) ||
2572                                 brush_flags.contains(BrushFlags::FORCE_AA);
2573
2574            let textures = BatchTextures {
2575                input: segment_data.textures,
2576                clip_mask,
2577            };
2578
2579            let batch_key = BatchKey {
2580                blend_mode: if needs_blending { alpha_blend_mode } else { BlendMode::None },
2581                kind: BatchKind::Brush(batch_kind),
2582                textures,
2583            };
2584
2585            self.add_brush_instance_to_batches(
2586                batch_key,
2587                features,
2588                bounding_rect,
2589                z_id,
2590                segment_index,
2591                segment.edge_flags & edge_aa_mask,
2592                clip_task_address,
2593                brush_flags | BrushFlags::PERSPECTIVE_INTERPOLATION | segment.brush_flags,
2594                prim_header_index,
2595                segment_data.specific_resource_address,
2596            );
2597        }
2598    }
2599
2600    /// Add any segment(s) from a brush to batches.
2601    ///
2602    /// `edge_aa_mask` Specifies the edges that are *allowed* to have anti-aliasing, if and only
2603    /// if the segments enable it.
2604    /// In other words passing EdgeAaSegmentFlags::all() does not necessarily mean all edges will
2605    /// be anti-aliased, only that they could be.
2606    fn add_segmented_prim_to_batch(
2607        &mut self,
2608        brush_segments: Option<&[BrushSegment]>,
2609        prim_opacity: PrimitiveOpacity,
2610        params: &BrushBatchParameters,
2611        blend_mode: BlendMode,
2612        features: BatchFeatures,
2613        brush_flags: BrushFlags,
2614        edge_aa_mask: EdgeMask,
2615        prim_header_index: PrimitiveHeaderIndex,
2616        bounding_rect: &PictureRect,
2617        transform_metadata: TransformMetadata,
2618        z_id: ZBufferId,
2619        clip_task_index: ClipTaskIndex,
2620        ctx: &RenderTargetContext,
2621        render_tasks: &RenderTaskGraph,
2622    ) {
2623        match (brush_segments, &params.segment_data) {
2624            (Some(ref brush_segments), SegmentDataKind::Instanced(ref segment_data)) => {
2625                // In this case, we have both a list of segments, and a list of
2626                // per-segment instance data. Zip them together to build batches.
2627                debug_assert_eq!(brush_segments.len(), segment_data.len());
2628                for (segment_index, (segment, segment_data)) in brush_segments
2629                    .iter()
2630                    .zip(segment_data.iter())
2631                    .enumerate()
2632                {
2633                    self.add_segment_to_batch(
2634                        segment,
2635                        segment_data,
2636                        segment_index as i32,
2637                        params.batch_kind,
2638                        prim_header_index,
2639                        blend_mode,
2640                        features,
2641                        brush_flags,
2642                        edge_aa_mask,
2643                        bounding_rect,
2644                        transform_metadata,
2645                        z_id,
2646                        prim_opacity,
2647                        clip_task_index,
2648                        ctx,
2649                        render_tasks,
2650                    );
2651                }
2652            }
2653            (Some(ref brush_segments), SegmentDataKind::Shared(ref segment_data)) => {
2654                // A list of segments, but the per-segment data is common
2655                // between all segments.
2656                for (segment_index, segment) in brush_segments
2657                    .iter()
2658                    .enumerate()
2659                {
2660                    self.add_segment_to_batch(
2661                        segment,
2662                        segment_data,
2663                        segment_index as i32,
2664                        params.batch_kind,
2665                        prim_header_index,
2666                        blend_mode,
2667                        features,
2668                        brush_flags,
2669                        edge_aa_mask,
2670                        bounding_rect,
2671                        transform_metadata,
2672                        z_id,
2673                        prim_opacity,
2674                        clip_task_index,
2675                        ctx,
2676                        render_tasks,
2677                    );
2678                }
2679            }
2680            (None, SegmentDataKind::Shared(ref segment_data)) => {
2681                // No segments, and thus no per-segment instance data.
2682                // Note: the blend mode already takes opacity into account
2683
2684                let (clip_task_address, clip_mask) = ctx.get_prim_clip_task_and_texture(
2685                    clip_task_index,
2686                    render_tasks,
2687                ).unwrap();
2688
2689                let textures = BatchTextures {
2690                    input: segment_data.textures,
2691                    clip_mask,
2692                };
2693
2694                let batch_key = BatchKey {
2695                    blend_mode,
2696                    kind: BatchKind::Brush(params.batch_kind),
2697                    textures,
2698                };
2699
2700                self.add_brush_instance_to_batches(
2701                    batch_key,
2702                    features,
2703                    bounding_rect,
2704                    z_id,
2705                    INVALID_SEGMENT_INDEX,
2706                    edge_aa_mask,
2707                    clip_task_address,
2708                    brush_flags | BrushFlags::PERSPECTIVE_INTERPOLATION,
2709                    prim_header_index,
2710                    segment_data.specific_resource_address,
2711                );
2712            }
2713            (None, SegmentDataKind::Instanced(..)) => {
2714                // We should never hit the case where there are no segments,
2715                // but a list of segment instance data.
2716                unreachable!();
2717            }
2718        }
2719    }
2720}
2721
2722/// Either a single texture / user data for all segments,
2723/// or a list of one per segment.
2724enum SegmentDataKind {
2725    Shared(SegmentInstanceData),
2726    Instanced(SmallVec<[SegmentInstanceData; 8]>),
2727}
2728
2729/// The parameters that are specific to a kind of brush,
2730/// used by the common method to add a brush to batches.
2731struct BrushBatchParameters {
2732    batch_kind: BrushBatchKind,
2733    prim_user_data: [i32; 4],
2734    segment_data: SegmentDataKind,
2735}
2736
2737impl BrushBatchParameters {
2738    /// This brush instance has a list of per-segment
2739    /// instance data.
2740    fn instanced(
2741        batch_kind: BrushBatchKind,
2742        prim_user_data: [i32; 4],
2743        segment_data: SmallVec<[SegmentInstanceData; 8]>,
2744    ) -> Self {
2745        BrushBatchParameters {
2746            batch_kind,
2747            prim_user_data,
2748            segment_data: SegmentDataKind::Instanced(segment_data),
2749        }
2750    }
2751
2752    /// This brush instance shares the per-segment data
2753    /// across all segments.
2754    fn shared(
2755        batch_kind: BrushBatchKind,
2756        textures: TextureSet,
2757        prim_user_data: [i32; 4],
2758        specific_resource_address: i32,
2759    ) -> Self {
2760        BrushBatchParameters {
2761            batch_kind,
2762            prim_user_data,
2763            segment_data: SegmentDataKind::Shared(
2764                SegmentInstanceData {
2765                    textures,
2766                    specific_resource_address,
2767                }
2768            ),
2769        }
2770    }
2771}
2772
2773/// A list of clip instances to be drawn into a target.
2774#[cfg_attr(feature = "capture", derive(Serialize))]
2775#[cfg_attr(feature = "replay", derive(Deserialize))]
2776pub struct ClipMaskInstanceList {
2777    pub mask_instances_fast: FrameVec<MaskInstance>,
2778    pub mask_instances_slow: FrameVec<MaskInstance>,
2779
2780    pub mask_instances_fast_with_scissor: FastHashMap<DeviceIntRect, FrameVec<MaskInstance>>,
2781    pub mask_instances_slow_with_scissor: FastHashMap<DeviceIntRect, FrameVec<MaskInstance>>,
2782
2783    pub image_mask_instances: FastHashMap<TextureSource, FrameVec<PrimitiveInstanceData>>,
2784    pub image_mask_instances_with_scissor: FastHashMap<(DeviceIntRect, TextureSource), FrameVec<PrimitiveInstanceData>>,
2785}
2786
2787impl ClipMaskInstanceList {
2788    pub fn new(memory: &FrameMemory) -> Self {
2789        ClipMaskInstanceList {
2790            mask_instances_fast: memory.new_vec(),
2791            mask_instances_slow: memory.new_vec(),
2792            mask_instances_fast_with_scissor: FastHashMap::default(),
2793            mask_instances_slow_with_scissor: FastHashMap::default(),
2794            image_mask_instances: FastHashMap::default(),
2795            image_mask_instances_with_scissor: FastHashMap::default(),
2796        }
2797    }
2798
2799    pub fn is_empty(&self) -> bool {
2800        // Destructure self to make sure we don't forget to update this method if
2801        // a new member is added.
2802        let ClipMaskInstanceList {
2803            mask_instances_fast,
2804            mask_instances_slow,
2805            mask_instances_fast_with_scissor,
2806            mask_instances_slow_with_scissor,
2807            image_mask_instances,
2808            image_mask_instances_with_scissor,
2809        } = self;
2810
2811        mask_instances_fast.is_empty()
2812            && mask_instances_slow.is_empty()
2813            && mask_instances_fast_with_scissor.is_empty()
2814            && mask_instances_slow_with_scissor.is_empty()
2815            && image_mask_instances.is_empty()
2816            && image_mask_instances_with_scissor.is_empty()
2817    }
2818}
2819
2820/// A list of clip instances to be drawn into a target.
2821#[derive(Debug)]
2822#[cfg_attr(feature = "capture", derive(Serialize))]
2823#[cfg_attr(feature = "replay", derive(Deserialize))]
2824pub struct ClipBatchList {
2825    /// Rectangle draws fill up the rectangles with rounded corners.
2826    pub slow_rectangles: FrameVec<ClipMaskInstanceRect>,
2827    pub fast_rectangles: FrameVec<ClipMaskInstanceRect>,
2828}
2829
2830impl ClipBatchList {
2831    fn new(memory: &FrameMemory) -> Self {
2832        ClipBatchList {
2833            slow_rectangles: memory.new_vec(),
2834            fast_rectangles: memory.new_vec(),
2835        }
2836    }
2837
2838    pub fn is_empty(&self) -> bool {
2839        self.slow_rectangles.is_empty()
2840          && self.fast_rectangles.is_empty()
2841    }
2842}
2843
2844/// Batcher managing draw calls into the clip mask (in the RT cache).
2845#[derive(Debug)]
2846#[cfg_attr(feature = "capture", derive(Serialize))]
2847#[cfg_attr(feature = "replay", derive(Deserialize))]
2848pub struct ClipBatcher {
2849    /// The first clip in each clip task. This will overwrite all pixels
2850    /// in the clip region, so we can skip doing a clear and write with
2851    /// blending disabled, which is a big performance win on Intel GPUs.
2852    pub primary_clips: ClipBatchList,
2853    /// Any subsequent clip masks (rare) for a clip task get drawn in
2854    /// a second pass with multiplicative blending enabled.
2855    pub secondary_clips: ClipBatchList,
2856
2857    gpu_supports_fast_clears: bool,
2858}
2859
2860impl ClipBatcher {
2861    pub fn new(
2862        gpu_supports_fast_clears: bool,
2863        memory: &FrameMemory,
2864    ) -> Self {
2865        ClipBatcher {
2866            primary_clips: ClipBatchList::new(memory),
2867            secondary_clips: ClipBatchList::new(memory),
2868            gpu_supports_fast_clears,
2869        }
2870    }
2871
2872    pub fn add_clip_region(
2873        &mut self,
2874        local_pos: LayoutPoint,
2875        sub_rect: DeviceRect,
2876        clip_data: ClipData,
2877        task_origin: DevicePoint,
2878        screen_origin: DevicePoint,
2879        device_pixel_scale: f32,
2880    ) {
2881        let instance = ClipMaskInstanceRect {
2882            common: ClipMaskInstanceCommon {
2883                clip_transform_id: GpuTransformId::IDENTITY,
2884                prim_transform_id: GpuTransformId::IDENTITY,
2885                sub_rect,
2886                task_origin,
2887                screen_origin,
2888                device_pixel_scale,
2889            },
2890            local_pos,
2891            clip_data,
2892        };
2893
2894        self.primary_clips.slow_rectangles.push(instance);
2895    }
2896
2897    /// Where appropriate, draw a clip rectangle as a small series of tiles,
2898    /// instead of one large rectangle.
2899    fn add_tiled_clip_mask(
2900        &mut self,
2901        mask_screen_rect: DeviceRect,
2902        local_clip_rect: LayoutRect,
2903        clip_spatial_node_index: SpatialNodeIndex,
2904        spatial_tree: &SpatialTree,
2905        world_rect: &WorldRect,
2906        global_device_pixel_scale: DevicePixelScale,
2907        common: &ClipMaskInstanceCommon,
2908        is_first_clip: bool,
2909    ) -> bool {
2910        // Only try to draw in tiles if the clip mark is big enough.
2911        if mask_screen_rect.area() < CLIP_RECTANGLE_AREA_THRESHOLD {
2912            return false;
2913        }
2914
2915        let mask_screen_rect_size = mask_screen_rect.size().to_i32();
2916        let clip_spatial_node = spatial_tree.get_spatial_node(clip_spatial_node_index);
2917
2918        // Only support clips that are axis-aligned to the root coordinate space,
2919        // for now, to simplify the logic below. This handles the vast majority
2920        // of real world cases, but could be expanded in future if needed.
2921        if clip_spatial_node.coordinate_system_id != CoordinateSystemId::root() {
2922            return false;
2923        }
2924
2925        // Get the world rect of the clip rectangle. If we can't transform it due
2926        // to the matrix, just fall back to drawing the entire clip mask.
2927        let transform = spatial_tree.get_world_transform(
2928            clip_spatial_node_index,
2929        );
2930        let world_clip_rect = match project_rect(
2931            &transform.into_transform(),
2932            &local_clip_rect,
2933            &world_rect,
2934        ) {
2935            Some(rect) => rect,
2936            None => return false,
2937        };
2938
2939        // Work out how many tiles to draw this clip mask in, stretched across the
2940        // device rect of the primitive clip mask.
2941        let world_device_rect = world_clip_rect * global_device_pixel_scale;
2942        let x_tiles = (mask_screen_rect_size.width + CLIP_RECTANGLE_TILE_SIZE-1) / CLIP_RECTANGLE_TILE_SIZE;
2943        let y_tiles = (mask_screen_rect_size.height + CLIP_RECTANGLE_TILE_SIZE-1) / CLIP_RECTANGLE_TILE_SIZE;
2944
2945        // Because we only run this code path for axis-aligned rects (the root coord system check above),
2946        // and only for rectangles (not rounded etc), the world_device_rect is not conservative - we know
2947        // that there is no inner_rect, and the world_device_rect should be the real, axis-aligned clip rect.
2948        let mask_origin = mask_screen_rect.min.to_vector();
2949        let clip_list = self.get_batch_list(is_first_clip);
2950
2951        for y in 0 .. y_tiles {
2952            for x in 0 .. x_tiles {
2953                let p0 = DeviceIntPoint::new(
2954                    x * CLIP_RECTANGLE_TILE_SIZE,
2955                    y * CLIP_RECTANGLE_TILE_SIZE,
2956                );
2957                let p1 = DeviceIntPoint::new(
2958                    (p0.x + CLIP_RECTANGLE_TILE_SIZE).min(mask_screen_rect_size.width),
2959                    (p0.y + CLIP_RECTANGLE_TILE_SIZE).min(mask_screen_rect_size.height),
2960                );
2961                let normalized_sub_rect = DeviceIntRect {
2962                    min: p0,
2963                    max: p1,
2964                }.to_f32();
2965                let world_sub_rect = normalized_sub_rect.translate(mask_origin);
2966
2967                // If the clip rect completely contains this tile rect, then drawing
2968                // these pixels would be redundant - since this clip can't possibly
2969                // affect the pixels in this tile, skip them!
2970                if !world_device_rect.contains_box(&world_sub_rect) {
2971                    clip_list.slow_rectangles.push(ClipMaskInstanceRect {
2972                        common: ClipMaskInstanceCommon {
2973                            sub_rect: normalized_sub_rect,
2974                            ..*common
2975                        },
2976                        local_pos: local_clip_rect.min,
2977                        clip_data: ClipData::uniform(local_clip_rect.size(), 0.0, ClipMode::Clip),
2978                    });
2979                }
2980            }
2981        }
2982
2983        true
2984    }
2985
2986    /// Retrieve the correct clip batch list to append to, depending
2987    /// on whether this is the first clip mask for a clip task.
2988    fn get_batch_list(
2989        &mut self,
2990        is_first_clip: bool,
2991    ) -> &mut ClipBatchList {
2992        if is_first_clip && !self.gpu_supports_fast_clears {
2993            &mut self.primary_clips
2994        } else {
2995            &mut self.secondary_clips
2996        }
2997    }
2998
2999    pub fn add(
3000        &mut self,
3001        clip_node_range: ClipNodeRange,
3002        root_spatial_node_index: SpatialNodeIndex,
3003        clip_store: &ClipStore,
3004        transforms: &mut TransformPalette,
3005        actual_rect: DeviceRect,
3006        surface_device_pixel_scale: DevicePixelScale,
3007        task_origin: DevicePoint,
3008        screen_origin: DevicePoint,
3009        ctx: &RenderTargetContext,
3010    ) -> bool {
3011        let mut is_first_clip = true;
3012        let mut clear_to_one = false;
3013
3014        for i in 0 .. clip_node_range.count {
3015            let clip_instance = clip_store.get_instance_from_range(&clip_node_range, i);
3016            let clip_node = &ctx.data_stores.clip[clip_instance.handle];
3017
3018            let clip_transform_id = transforms.gpu.get_id(
3019                clip_instance.spatial_node_index,
3020                ctx.root_spatial_node_index,
3021                ctx.spatial_tree,
3022            );
3023
3024            let prim_transform_id = transforms.gpu.get_id(
3025                root_spatial_node_index,
3026                ctx.root_spatial_node_index,
3027                ctx.spatial_tree,
3028            );
3029
3030            let common = ClipMaskInstanceCommon {
3031                sub_rect: DeviceRect::from_size(actual_rect.size()),
3032                task_origin,
3033                screen_origin,
3034                device_pixel_scale: surface_device_pixel_scale.0,
3035                clip_transform_id,
3036                prim_transform_id,
3037            };
3038
3039            let added_clip = match clip_node.item.kind {
3040                ClipItemKind::Image { .. } => {
3041                    unreachable!();
3042                }
3043                ClipItemKind::Rectangle { mode: ClipMode::ClipOut } => {
3044                    self.get_batch_list(is_first_clip)
3045                        .slow_rectangles
3046                        .push(ClipMaskInstanceRect {
3047                            common,
3048                            local_pos: clip_instance.clip_rect.min,
3049                            clip_data: ClipData::uniform(clip_instance.clip_rect.size(), 0.0, ClipMode::ClipOut),
3050                        });
3051
3052                    true
3053                }
3054                ClipItemKind::Rectangle { mode: ClipMode::Clip } => {
3055                    if clip_instance.flags.contains(ClipNodeFlags::SAME_COORD_SYSTEM) {
3056                        false
3057                    } else {
3058                        if self.add_tiled_clip_mask(
3059                            actual_rect,
3060                            clip_instance.clip_rect,
3061                            clip_instance.spatial_node_index,
3062                            ctx.spatial_tree,
3063                            &ctx.screen_world_rect,
3064                            ctx.global_device_pixel_scale,
3065                            &common,
3066                            is_first_clip,
3067                        ) {
3068                            clear_to_one |= is_first_clip;
3069                        } else {
3070                            self.get_batch_list(is_first_clip)
3071                                .slow_rectangles
3072                                .push(ClipMaskInstanceRect {
3073                                    common,
3074                                    local_pos: clip_instance.clip_rect.min,
3075                                    clip_data: ClipData::uniform(clip_instance.clip_rect.size(), 0.0, ClipMode::Clip),
3076                                });
3077                        }
3078
3079                        true
3080                    }
3081                }
3082                ClipItemKind::RoundedRectangle { ref radius, mode, .. } => {
3083                    let size = clip_instance.clip_rect.size();
3084                    let radius = clamped_radius(radius, size);
3085                    let batch_list = self.get_batch_list(is_first_clip);
3086                    let instance = ClipMaskInstanceRect {
3087                        common,
3088                        local_pos: clip_instance.clip_rect.min,
3089                        clip_data: ClipData::rounded_rect(size, &radius, mode),
3090                    };
3091                    if clip_instance.flags.contains(ClipNodeFlags::USE_FAST_PATH) {
3092                        batch_list.fast_rectangles.push(instance);
3093                    } else {
3094                        batch_list.slow_rectangles.push(instance);
3095                    }
3096
3097                    true
3098                }
3099            };
3100
3101            is_first_clip &= !added_clip;
3102        }
3103
3104        clear_to_one
3105    }
3106}
3107
3108impl<'a, 'rc> RenderTargetContext<'a, 'rc> {
3109    /// Retrieve the GPU task address for a given clip task instance.
3110    /// Returns None if the segment was completely clipped out.
3111    /// Returns Some(OPAQUE_TASK_ADDRESS) if no clip mask is needed.
3112    /// Returns Some(task_address) if there was a valid clip mask.
3113    fn get_clip_task_and_texture(
3114        &self,
3115        clip_task_index: ClipTaskIndex,
3116        offset: i32,
3117        render_tasks: &RenderTaskGraph,
3118    ) -> Option<(RenderTaskAddress, TextureSource)> {
3119        match self.scratch.frame.clip_mask_instances[clip_task_index.0 as usize + offset as usize] {
3120            ClipMaskKind::Mask(task_id) => {
3121                Some((
3122                    task_id.into(),
3123                    TextureSource::TextureCache(
3124                        render_tasks[task_id].get_target_texture(),
3125                        Swizzle::default(),
3126                    )
3127                ))
3128            }
3129            ClipMaskKind::None => {
3130                Some((OPAQUE_TASK_ADDRESS, TextureSource::Invalid))
3131            }
3132            ClipMaskKind::Clipped => {
3133                None
3134            }
3135        }
3136    }
3137
3138    /// Helper function to get the clip task address for a
3139    /// non-segmented primitive.
3140    fn get_prim_clip_task_and_texture(
3141        &self,
3142        clip_task_index: ClipTaskIndex,
3143        render_tasks: &RenderTaskGraph,
3144    ) -> Option<(RenderTaskAddress, TextureSource)> {
3145        self.get_clip_task_and_texture(
3146            clip_task_index,
3147            0,
3148            render_tasks,
3149        )
3150    }
3151}
3152
3153impl CompositorSurfaceKind {
3154    /// Returns true if the type of compositor surface needs an alpha cutout rendered
3155    fn needs_cutout(&self) -> bool {
3156        match self {
3157            CompositorSurfaceKind::Underlay => true,
3158            CompositorSurfaceKind::Overlay | CompositorSurfaceKind::Blit => false,
3159        }
3160    }
3161}