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