webrender/prim_store/gradient/
linear.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5//! Linear gradients
6//!
7//! Specification: https://drafts.csswg.org/css-images-4/#linear-gradients
8//!
9//! Linear gradients are rendered via cached render tasks and composited with the image brush.
10
11use euclid::approxeq::ApproxEq;
12use euclid::{point2, vec2, size2};
13use api::{ExtendMode, GradientStop, LineOrientation, PremultipliedColorF, ColorF, ColorU};
14use api::units::*;
15use crate::scene_building::IsVisible;
16use crate::frame_builder::FrameBuildingState;
17use crate::intern::{Internable, InternDebug, Handle as InternHandle};
18use crate::internal_types::LayoutPrimitiveInfo;
19use crate::image_tiling::simplify_repeated_primitive;
20use crate::prim_store::{BrushSegment, GradientTileRange};
21use crate::prim_store::{PrimitiveInstanceKind, PrimitiveOpacity};
22use crate::prim_store::{PrimKeyCommonData, PrimTemplateCommonData, PrimitiveStore};
23use crate::prim_store::{NinePatchDescriptor, PointKey, SizeKey, InternablePrimitive};
24use crate::render_task::{RenderTask, RenderTaskKind};
25use crate::render_task_graph::RenderTaskId;
26use crate::render_task_cache::{RenderTaskCacheKeyKind, RenderTaskCacheKey, RenderTaskParent};
27use crate::renderer::GpuBufferAddress;
28use crate::segment::EdgeAaSegmentMask;
29use crate::util::pack_as_float;
30use super::{stops_and_min_alpha, GradientStopKey, GradientGpuBlockBuilder, apply_gradient_local_clip};
31use std::ops::{Deref, DerefMut};
32use std::mem::swap;
33
34pub const MAX_CACHED_SIZE: f32 = 1024.0;
35
36/// Identifying key for a linear gradient.
37#[cfg_attr(feature = "capture", derive(Serialize))]
38#[cfg_attr(feature = "replay", derive(Deserialize))]
39#[derive(Debug, Clone, Eq, PartialEq, Hash, MallocSizeOf)]
40pub struct LinearGradientKey {
41    pub common: PrimKeyCommonData,
42    pub extend_mode: ExtendMode,
43    pub start_point: PointKey,
44    pub end_point: PointKey,
45    pub stretch_size: SizeKey,
46    pub tile_spacing: SizeKey,
47    pub stops: Vec<GradientStopKey>,
48    pub reverse_stops: bool,
49    pub cached: bool,
50    pub nine_patch: Option<Box<NinePatchDescriptor>>,
51    pub edge_aa_mask: EdgeAaSegmentMask,
52}
53
54impl LinearGradientKey {
55    pub fn new(
56        info: &LayoutPrimitiveInfo,
57        linear_grad: LinearGradient,
58    ) -> Self {
59        LinearGradientKey {
60            common: info.into(),
61            extend_mode: linear_grad.extend_mode,
62            start_point: linear_grad.start_point,
63            end_point: linear_grad.end_point,
64            stretch_size: linear_grad.stretch_size,
65            tile_spacing: linear_grad.tile_spacing,
66            stops: linear_grad.stops,
67            reverse_stops: linear_grad.reverse_stops,
68            cached: linear_grad.cached,
69            nine_patch: linear_grad.nine_patch,
70            edge_aa_mask: linear_grad.edge_aa_mask,
71        }
72    }
73}
74
75impl InternDebug for LinearGradientKey {}
76
77#[cfg_attr(feature = "capture", derive(Serialize))]
78#[cfg_attr(feature = "replay", derive(Deserialize))]
79#[derive(Debug, MallocSizeOf)]
80pub struct LinearGradientTemplate {
81    pub common: PrimTemplateCommonData,
82    pub extend_mode: ExtendMode,
83    pub start_point: DevicePoint,
84    pub end_point: DevicePoint,
85    pub task_size: DeviceIntSize,
86    pub scale: DeviceVector2D,
87    pub stretch_size: LayoutSize,
88    pub tile_spacing: LayoutSize,
89    pub stops_opacity: PrimitiveOpacity,
90    pub stops: Vec<GradientStop>,
91    pub brush_segments: Vec<BrushSegment>,
92    pub reverse_stops: bool,
93    pub is_fast_path: bool,
94    pub cached: bool,
95    pub src_color: Option<RenderTaskId>,
96}
97
98impl Deref for LinearGradientTemplate {
99    type Target = PrimTemplateCommonData;
100    fn deref(&self) -> &Self::Target {
101        &self.common
102    }
103}
104
105impl DerefMut for LinearGradientTemplate {
106    fn deref_mut(&mut self) -> &mut Self::Target {
107        &mut self.common
108    }
109}
110
111/// Perform a few optimizations to the gradient that are relevant to scene building.
112///
113/// Returns true if the gradient was decomposed into fast-path primitives, indicating
114/// that we shouldn't emit a regular gradient primitive after this returns.
115pub fn optimize_linear_gradient(
116    prim_rect: &mut LayoutRect,
117    tile_size: &mut LayoutSize,
118    mut tile_spacing: LayoutSize,
119    clip_rect: &LayoutRect,
120    start: &mut LayoutPoint,
121    end: &mut LayoutPoint,
122    extend_mode: ExtendMode,
123    stops: &mut [GradientStopKey],
124    // Callback called for each fast-path segment (rect, start end, stops).
125    callback: &mut dyn FnMut(&LayoutRect, LayoutPoint, LayoutPoint, &[GradientStopKey], EdgeAaSegmentMask)
126) -> bool {
127    // First sanitize the gradient parameters. See if we can remove repetitions,
128    // tighten the primitive bounds, etc.
129
130    simplify_repeated_primitive(&tile_size, &mut tile_spacing, prim_rect);
131
132    let vertical = start.x.approx_eq(&end.x);
133    let horizontal = start.y.approx_eq(&end.y);
134
135    let mut horizontally_tiled = prim_rect.width() > tile_size.width;
136    let mut vertically_tiled = prim_rect.height() > tile_size.height;
137
138    // Check whether the tiling is equivalent to stretching on either axis.
139    // Stretching the gradient is more efficient than repeating it.
140    if vertically_tiled && horizontal && tile_spacing.height == 0.0 {
141        tile_size.height = prim_rect.height();
142        vertically_tiled = false;
143    }
144
145    if horizontally_tiled && vertical && tile_spacing.width == 0.0 {
146        tile_size.width = prim_rect.width();
147        horizontally_tiled = false;
148    }
149
150    let offset = apply_gradient_local_clip(
151        prim_rect,
152        &tile_size,
153        &tile_spacing,
154        &clip_rect
155    );
156
157    // The size of gradient render tasks depends on the tile_size. No need to generate
158    // large stretch sizes that will be clipped to the bounds of the primitive.
159    tile_size.width = tile_size.width.min(prim_rect.width());
160    tile_size.height = tile_size.height.min(prim_rect.height());
161
162    *start += offset;
163    *end += offset;
164
165    // Next, in the case of axis-aligned gradients, see if it is worth
166    // decomposing the gradient into multiple gradients with only two
167    // gradient stops per segment to get a faster shader.
168
169    if extend_mode != ExtendMode::Clamp || stops.is_empty() {
170        return false;
171    }
172
173    if !vertical && !horizontal {
174        return false;
175    }
176
177    if vertical && horizontal {
178        return false;
179    }
180
181    if !tile_spacing.is_empty() || vertically_tiled || horizontally_tiled {
182        return false;
183    }
184
185    // If the gradient is small, no need to bother with decomposing it.
186    if (horizontal && tile_size.width < 256.0)
187        || (vertical && tile_size.height < 256.0) {
188
189        return false;
190    }
191
192    // Flip x and y if need be so that we only deal with the horizontal case.
193
194    // From now on don't return false. We are going modifying the caller's
195    // variables and not bother to restore them. If the control flow changes,
196    // Make sure to to restore &mut parameters to sensible values before
197    // returning false.
198
199    let adjust_rect = &mut |rect: &mut LayoutRect| {
200        if vertical {
201            swap(&mut rect.min.x, &mut rect.min.y);
202            swap(&mut rect.max.x, &mut rect.max.y);
203        }
204    };
205
206    let adjust_size = &mut |size: &mut LayoutSize| {
207        if vertical { swap(&mut size.width, &mut size.height); }
208    };
209
210    let adjust_point = &mut |p: &mut LayoutPoint| {
211        if vertical { swap(&mut p.x, &mut p.y); }
212    };
213
214    let clip_rect = match clip_rect.intersection(prim_rect) {
215        Some(clip) => clip,
216        None => {
217            return false;
218        }
219    };
220
221    adjust_rect(prim_rect);
222    adjust_point(start);
223    adjust_point(end);
224    adjust_size(tile_size);
225
226    let length = (end.x - start.x).abs();
227
228    // Decompose the gradient into simple segments. This lets us:
229    // - separate opaque from semi-transparent segments,
230    // - compress long segments into small render tasks,
231    // - make sure hard stops stay so even if the primitive is large.
232
233    let reverse_stops = start.x > end.x;
234
235    // Handle reverse stops so we can assume stops are arranged in increasing x.
236    if reverse_stops {
237        stops.reverse();
238        swap(start, end);
239    }
240
241    // Use fake gradient stop to emulate the potential constant color sections
242    // before and after the gradient endpoints.
243    let mut prev = *stops.first().unwrap();
244    let mut last = *stops.last().unwrap();
245
246    // Set the offsets of the fake stops to position them at the edges of the primitive.
247    prev.offset = -start.x / length;
248    last.offset = (tile_size.width - start.x) / length;
249    if reverse_stops {
250        prev.offset = 1.0 - prev.offset;
251        last.offset = 1.0 - last.offset;
252    }
253
254    let (side_edges, first_edge, last_edge) = if vertical {
255        (
256            EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::RIGHT,
257            EdgeAaSegmentMask::TOP,
258            EdgeAaSegmentMask::BOTTOM
259        )
260    } else {
261        (
262            EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::BOTTOM,
263            EdgeAaSegmentMask::LEFT,
264            EdgeAaSegmentMask::RIGHT
265        )
266    };
267
268    let mut is_first = true;
269    let last_offset = last.offset;
270    for stop in stops.iter().chain((&[last]).iter()) {
271        let prev_stop = prev;
272        prev = *stop;
273
274        if prev_stop.color.a == 0 && stop.color.a == 0 {
275            continue;
276        }
277
278
279        let prev_offset = if reverse_stops { 1.0 - prev_stop.offset } else { prev_stop.offset };
280        let offset = if reverse_stops { 1.0 - stop.offset } else { stop.offset };
281
282        // In layout space, relative to the primitive.
283        let segment_start = start.x + prev_offset * length;
284        let segment_end = start.x + offset * length;
285        let segment_length = segment_end - segment_start;
286
287        if segment_length <= 0.0 {
288            continue;
289        }
290
291        let mut segment_rect = *prim_rect;
292        segment_rect.min.x += segment_start;
293        segment_rect.max.x = segment_rect.min.x + segment_length;
294
295        let mut start = point2(0.0, 0.0);
296        let mut end = point2(segment_length, 0.0);
297
298        adjust_point(&mut start);
299        adjust_point(&mut end);
300        adjust_rect(&mut segment_rect);
301
302        let origin_before_clip = segment_rect.min;
303        segment_rect = match segment_rect.intersection(&clip_rect) {
304            Some(rect) => rect,
305            None => {
306                continue;
307            }
308        };
309        let offset = segment_rect.min - origin_before_clip;
310
311        // Account for the clipping since start and end are relative to the origin.
312        start -= offset;
313        end -= offset;
314
315        let mut edge_flags = side_edges;
316        if is_first {
317            edge_flags |= first_edge;
318            is_first = false;
319        }
320        if stop.offset == last_offset {
321            edge_flags |= last_edge;
322        }
323
324        callback(
325            &segment_rect,
326            start,
327            end,
328            &[
329                GradientStopKey { offset: 0.0, .. prev_stop },
330                GradientStopKey { offset: 1.0, .. *stop },
331            ],
332            edge_flags,
333        );
334    }
335
336    true
337}
338
339impl From<LinearGradientKey> for LinearGradientTemplate {
340    fn from(item: LinearGradientKey) -> Self {
341
342        let mut common = PrimTemplateCommonData::with_key_common(item.common);
343        common.edge_aa_mask = item.edge_aa_mask;
344
345        let (mut stops, min_alpha) = stops_and_min_alpha(&item.stops);
346
347        let mut brush_segments = Vec::new();
348
349        if let Some(ref nine_patch) = item.nine_patch {
350            brush_segments = nine_patch.create_segments(common.prim_rect.size());
351        }
352
353        // Save opacity of the stops for use in
354        // selecting which pass this gradient
355        // should be drawn in.
356        let stops_opacity = PrimitiveOpacity::from_alpha(min_alpha);
357
358        let start_point = DevicePoint::new(item.start_point.x, item.start_point.y);
359        let end_point = DevicePoint::new(item.end_point.x, item.end_point.y);
360        let tile_spacing: LayoutSize = item.tile_spacing.into();
361        let stretch_size: LayoutSize = item.stretch_size.into();
362        let mut task_size: DeviceSize = stretch_size.cast_unit();
363
364        let horizontal = start_point.y.approx_eq(&end_point.y);
365        let vertical = start_point.x.approx_eq(&end_point.x);
366
367        if horizontal {
368            // Completely horizontal, we can stretch the gradient vertically.
369            task_size.height = 1.0;
370        }
371
372        if vertical {
373            // Completely vertical, we can stretch the gradient horizontally.
374            task_size.width = 1.0;
375        }
376
377        // See if we can render the gradient using a special fast-path shader.
378        // The fast path path only works with two gradient stops.
379        let mut is_fast_path = false;
380        if item.cached && stops.len() == 2 && brush_segments.is_empty() {
381            if horizontal
382                && stretch_size.width >= common.prim_rect.width()
383                && start_point.x.approx_eq(&0.0)
384                && end_point.x.approx_eq(&stretch_size.width) {
385                is_fast_path = true;
386                task_size.width = task_size.width.min(256.0);
387            }
388            if vertical
389                && stretch_size.height >= common.prim_rect.height()
390                && start_point.y.approx_eq(&0.0)
391                && end_point.y.approx_eq(&stretch_size.height) {
392                is_fast_path = true;
393                task_size.height = task_size.height.min(256.0);
394            }
395
396            if stops[0].color == stops[1].color {
397                is_fast_path = true;
398                task_size = size2(1.0, 1.0);
399            }
400
401            if is_fast_path && item.reverse_stops {
402                // The fast path doesn't use the gradient gpu blocks builder so handle
403                // reversed stops here.
404                stops.swap(0, 1);
405            }
406        }
407
408        // Avoid rendering enormous gradients. Linear gradients are mostly made of soft transitions,
409        // so it is unlikely that rendering at a higher resolution than 1024 would produce noticeable
410        // differences, especially with 8 bits per channel.
411
412        let mut scale = vec2(1.0, 1.0);
413
414        if task_size.width > MAX_CACHED_SIZE {
415            scale.x = task_size.width / MAX_CACHED_SIZE;
416            task_size.width = MAX_CACHED_SIZE;
417        }
418
419        if task_size.height > MAX_CACHED_SIZE {
420            scale.y = task_size.height / MAX_CACHED_SIZE;
421            task_size.height = MAX_CACHED_SIZE;
422        }
423
424        LinearGradientTemplate {
425            common,
426            extend_mode: item.extend_mode,
427            start_point,
428            end_point,
429            task_size: task_size.ceil().to_i32(),
430            scale,
431            stretch_size,
432            tile_spacing,
433            stops_opacity,
434            stops,
435            brush_segments,
436            reverse_stops: item.reverse_stops,
437            is_fast_path,
438            cached: item.cached,
439            src_color: None,
440        }
441    }
442}
443
444impl LinearGradientTemplate {
445    /// Update the GPU cache for a given primitive template. This may be called multiple
446    /// times per frame, by each primitive reference that refers to this interned
447    /// template. The initial request call to the GPU cache ensures that work is only
448    /// done if the cache entry is invalid (due to first use or eviction).
449    pub fn update(
450        &mut self,
451        frame_state: &mut FrameBuildingState,
452    ) {
453        if let Some(mut request) = frame_state.gpu_cache.request(
454            &mut self.common.gpu_cache_handle
455        ) {
456
457            // Write_prim_gpu_blocks
458            if self.cached {
459                // We are using the image brush.
460                request.push(PremultipliedColorF::WHITE);
461                request.push(PremultipliedColorF::WHITE);
462                request.push([
463                    self.stretch_size.width,
464                    self.stretch_size.height,
465                    0.0,
466                    0.0,
467                ]);
468            } else {
469                // We are using the gradient brush.
470                request.push([
471                    self.start_point.x,
472                    self.start_point.y,
473                    self.end_point.x,
474                    self.end_point.y,
475                ]);
476                request.push([
477                    pack_as_float(self.extend_mode as u32),
478                    self.stretch_size.width,
479                    self.stretch_size.height,
480                    0.0,
481                ]);
482            }
483
484            // write_segment_gpu_blocks
485            for segment in &self.brush_segments {
486                // has to match VECS_PER_SEGMENT
487                request.write_segment(
488                    segment.local_rect,
489                    segment.extra_data,
490                );
491            }
492        }
493
494        // Tile spacing is always handled by decomposing into separate draw calls so the
495        // primitive opacity is equivalent to stops opacity. This might change to being
496        // set to non-opaque in the presence of tile spacing if/when tile spacing is handled
497        // in the same way as with the image primitive.
498        self.opacity = self.stops_opacity;
499
500        if !self.cached {
501            return;
502        }
503
504        let task_id = if self.is_fast_path {
505            let orientation = if self.task_size.width > self.task_size.height {
506                LineOrientation::Horizontal
507            } else {
508                LineOrientation::Vertical
509            };
510
511            let gradient = FastLinearGradientTask {
512                color0: self.stops[0].color.into(),
513                color1: self.stops[1].color.into(),
514                orientation,
515            };
516
517            frame_state.resource_cache.request_render_task(
518                Some(RenderTaskCacheKey {
519                    size: self.task_size,
520                    kind: RenderTaskCacheKeyKind::FastLinearGradient(gradient),
521                }),
522                false,
523                RenderTaskParent::Surface,
524                frame_state.gpu_cache,
525                &mut frame_state.frame_gpu_data.f32,
526                frame_state.rg_builder,
527                &mut frame_state.surface_builder,
528                &mut |rg_builder, _, _| {
529                    rg_builder.add().init(RenderTask::new_dynamic(
530                        self.task_size,
531                        RenderTaskKind::FastLinearGradient(gradient),
532                    ))
533                }
534            )
535        } else {
536            let cache_key = LinearGradientCacheKey {
537                size: self.task_size,
538                start: PointKey { x: self.start_point.x, y: self.start_point.y },
539                end: PointKey { x: self.end_point.x, y: self.end_point.y },
540                scale: PointKey { x: self.scale.x, y: self.scale.y },
541                extend_mode: self.extend_mode,
542                stops: self.stops.iter().map(|stop| (*stop).into()).collect(),
543                reversed_stops: self.reverse_stops,
544            };
545
546            frame_state.resource_cache.request_render_task(
547                Some(RenderTaskCacheKey {
548                    size: self.task_size,
549                    kind: RenderTaskCacheKeyKind::LinearGradient(cache_key),
550                }),
551                false,
552                RenderTaskParent::Surface,
553                frame_state.gpu_cache,
554                &mut frame_state.frame_gpu_data.f32,
555                frame_state.rg_builder,
556                &mut frame_state.surface_builder,
557                &mut |rg_builder, gpu_buffer_builder, _| {
558                    let stops = Some(GradientGpuBlockBuilder::build(
559                        self.reverse_stops,
560                        gpu_buffer_builder,
561                        &self.stops,
562                    ));
563
564                    rg_builder.add().init(RenderTask::new_dynamic(
565                        self.task_size,
566                        RenderTaskKind::LinearGradient(LinearGradientTask {
567                            start: self.start_point,
568                            end: self.end_point,
569                            scale: self.scale,
570                            extend_mode: self.extend_mode,
571                            stops: stops.unwrap(),
572                        }),
573                    ))
574                }
575            )
576        };
577
578        self.src_color = Some(task_id);
579    }
580}
581
582pub type LinearGradientDataHandle = InternHandle<LinearGradient>;
583
584#[derive(Debug, MallocSizeOf)]
585#[cfg_attr(feature = "capture", derive(Serialize))]
586#[cfg_attr(feature = "replay", derive(Deserialize))]
587pub struct LinearGradient {
588    pub extend_mode: ExtendMode,
589    pub start_point: PointKey,
590    pub end_point: PointKey,
591    pub stretch_size: SizeKey,
592    pub tile_spacing: SizeKey,
593    pub stops: Vec<GradientStopKey>,
594    pub reverse_stops: bool,
595    pub nine_patch: Option<Box<NinePatchDescriptor>>,
596    pub cached: bool,
597    pub edge_aa_mask: EdgeAaSegmentMask,
598}
599
600impl Internable for LinearGradient {
601    type Key = LinearGradientKey;
602    type StoreData = LinearGradientTemplate;
603    type InternData = ();
604    const PROFILE_COUNTER: usize = crate::profiler::INTERNED_LINEAR_GRADIENTS;
605}
606
607impl InternablePrimitive for LinearGradient {
608    fn into_key(
609        self,
610        info: &LayoutPrimitiveInfo,
611    ) -> LinearGradientKey {
612        LinearGradientKey::new(info, self)
613    }
614
615    fn make_instance_kind(
616        key: LinearGradientKey,
617        data_handle: LinearGradientDataHandle,
618        _prim_store: &mut PrimitiveStore,
619    ) -> PrimitiveInstanceKind {
620        if key.cached {
621            PrimitiveInstanceKind::CachedLinearGradient {
622                data_handle,
623                visible_tiles_range: GradientTileRange::empty(),
624            }
625        } else {
626            PrimitiveInstanceKind::LinearGradient {
627                data_handle,
628                visible_tiles_range: GradientTileRange::empty(),
629            }
630        }
631    }
632}
633
634impl IsVisible for LinearGradient {
635    fn is_visible(&self) -> bool {
636        true
637    }
638}
639
640#[derive(Debug)]
641#[cfg_attr(feature = "capture", derive(Serialize))]
642pub struct LinearGradientPrimitive {
643    pub cache_segments: Vec<CachedGradientSegment>,
644    pub visible_tiles_range: GradientTileRange,
645}
646
647#[derive(Debug)]
648#[cfg_attr(feature = "capture", derive(Serialize))]
649pub struct CachedGradientSegment {
650    pub render_task: RenderTaskId,
651    pub local_rect: LayoutRect,
652}
653
654
655#[derive(Copy, Clone, Debug, Hash, MallocSizeOf, PartialEq, Eq)]
656#[cfg_attr(feature = "capture", derive(Serialize))]
657#[cfg_attr(feature = "replay", derive(Deserialize))]
658pub struct FastLinearGradientTask {
659    pub color0: ColorU,
660    pub color1: ColorU,
661    pub orientation: LineOrientation,
662}
663
664impl FastLinearGradientTask {
665    pub fn to_instance(&self, target_rect: &DeviceIntRect) -> FastLinearGradientInstance {
666        FastLinearGradientInstance {
667            task_rect: target_rect.to_f32(),
668            color0: ColorF::from(self.color0).premultiplied(),
669            color1: ColorF::from(self.color1).premultiplied(),
670            axis_select: match self.orientation {
671                LineOrientation::Horizontal => 0.0,
672                LineOrientation::Vertical => 1.0,
673            },
674        }
675    }
676}
677
678pub type FastLinearGradientCacheKey = FastLinearGradientTask;
679
680/// The per-instance shader input of a fast-path linear gradient render task.
681///
682/// Must match the FAST_LINEAR_GRADIENT instance description in renderer/vertex.rs.
683#[cfg_attr(feature = "capture", derive(Serialize))]
684#[cfg_attr(feature = "replay", derive(Deserialize))]
685#[repr(C)]
686#[derive(Clone, Debug)]
687pub struct FastLinearGradientInstance {
688    pub task_rect: DeviceRect,
689    pub color0: PremultipliedColorF,
690    pub color1: PremultipliedColorF,
691    pub axis_select: f32,
692}
693
694#[derive(Debug)]
695#[cfg_attr(feature = "capture", derive(Serialize))]
696#[cfg_attr(feature = "replay", derive(Deserialize))]
697pub struct LinearGradientTask {
698    pub start: DevicePoint,
699    pub end: DevicePoint,
700    pub scale: DeviceVector2D,
701    pub extend_mode: ExtendMode,
702    pub stops: GpuBufferAddress,
703}
704
705impl LinearGradientTask {
706    pub fn to_instance(&self, target_rect: &DeviceIntRect) -> LinearGradientInstance {
707        LinearGradientInstance {
708            task_rect: target_rect.to_f32(),
709            start: self.start,
710            end: self.end,
711            scale: self.scale,
712            extend_mode: self.extend_mode as i32,
713            gradient_stops_address: self.stops.as_int(),
714        }
715    }
716}
717
718/// The per-instance shader input of a linear gradient render task.
719///
720/// Must match the LINEAR_GRADIENT instance description in renderer/vertex.rs.
721#[cfg_attr(feature = "capture", derive(Serialize))]
722#[cfg_attr(feature = "replay", derive(Deserialize))]
723#[repr(C)]
724#[derive(Clone, Debug)]
725pub struct LinearGradientInstance {
726    pub task_rect: DeviceRect,
727    pub start: DevicePoint,
728    pub end: DevicePoint,
729    pub scale: DeviceVector2D,
730    pub extend_mode: i32,
731    pub gradient_stops_address: i32,
732}
733
734#[derive(Clone, Debug, Hash, PartialEq, Eq)]
735#[cfg_attr(feature = "capture", derive(Serialize))]
736#[cfg_attr(feature = "replay", derive(Deserialize))]
737pub struct LinearGradientCacheKey {
738    pub size: DeviceIntSize,
739    pub start: PointKey,
740    pub end: PointKey,
741    pub scale: PointKey,
742    pub extend_mode: ExtendMode,
743    pub stops: Vec<GradientStopKey>,
744    pub reversed_stops: bool,
745}