webrender/prim_store/
gradient.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::{
6    ColorF, ColorU, ExtendMode, GradientStop,
7    PremultipliedColorF, LineOrientation, PrimitiveFlags,
8};
9use api::units::{LayoutPoint, LayoutSize, LayoutVector2D};
10use crate::scene_building::IsVisible;
11use euclid::approxeq::ApproxEq;
12use crate::frame_builder::FrameBuildingState;
13use crate::gpu_cache::{GpuCacheHandle, GpuDataRequest};
14use crate::intern::{Internable, InternDebug, Handle as InternHandle};
15use crate::internal_types::LayoutPrimitiveInfo;
16use crate::prim_store::{BrushSegment, GradientTileRange, VectorKey};
17use crate::prim_store::{PrimitiveInstanceKind, PrimitiveOpacity, PrimitiveSceneData};
18use crate::prim_store::{PrimKeyCommonData, PrimTemplateCommonData, PrimitiveStore};
19use crate::prim_store::{NinePatchDescriptor, PointKey, SizeKey, InternablePrimitive};
20use crate::render_task_cache::RenderTaskCacheEntryHandle;
21use std::{hash, ops::{Deref, DerefMut}};
22use crate::util::pack_as_float;
23
24/// The maximum number of stops a gradient may have to use the fast path.
25pub const GRADIENT_FP_STOPS: usize = 4;
26
27/// A hashable gradient stop that can be used in primitive keys.
28#[cfg_attr(feature = "capture", derive(Serialize))]
29#[cfg_attr(feature = "replay", derive(Deserialize))]
30#[derive(Debug, Copy, Clone, MallocSizeOf, PartialEq)]
31pub struct GradientStopKey {
32    pub offset: f32,
33    pub color: ColorU,
34}
35
36impl GradientStopKey {
37    pub fn empty() -> Self {
38        GradientStopKey {
39            offset: 0.0,
40            color: ColorU::new(0, 0, 0, 0),
41        }
42    }
43}
44
45impl Into<GradientStopKey> for GradientStop {
46    fn into(self) -> GradientStopKey {
47        GradientStopKey {
48            offset: self.offset,
49            color: self.color.into(),
50        }
51    }
52}
53
54impl Eq for GradientStopKey {}
55
56impl hash::Hash for GradientStopKey {
57    fn hash<H: hash::Hasher>(&self, state: &mut H) {
58        self.offset.to_bits().hash(state);
59        self.color.hash(state);
60    }
61}
62
63/// Identifying key for a line decoration.
64#[cfg_attr(feature = "capture", derive(Serialize))]
65#[cfg_attr(feature = "replay", derive(Deserialize))]
66#[derive(Debug, Clone, Eq, PartialEq, Hash, MallocSizeOf)]
67pub struct LinearGradientKey {
68    pub common: PrimKeyCommonData,
69    pub extend_mode: ExtendMode,
70    pub start_point: PointKey,
71    pub end_point: PointKey,
72    pub stretch_size: SizeKey,
73    pub tile_spacing: SizeKey,
74    pub stops: Vec<GradientStopKey>,
75    pub reverse_stops: bool,
76    pub nine_patch: Option<Box<NinePatchDescriptor>>,
77}
78
79impl LinearGradientKey {
80    pub fn new(
81        flags: PrimitiveFlags,
82        prim_size: LayoutSize,
83        linear_grad: LinearGradient,
84    ) -> Self {
85        LinearGradientKey {
86            common: PrimKeyCommonData {
87                flags,
88                prim_size: prim_size.into(),
89            },
90            extend_mode: linear_grad.extend_mode,
91            start_point: linear_grad.start_point,
92            end_point: linear_grad.end_point,
93            stretch_size: linear_grad.stretch_size,
94            tile_spacing: linear_grad.tile_spacing,
95            stops: linear_grad.stops,
96            reverse_stops: linear_grad.reverse_stops,
97            nine_patch: linear_grad.nine_patch,
98        }
99    }
100}
101
102impl InternDebug for LinearGradientKey {}
103
104#[derive(Clone, Debug, Hash, MallocSizeOf, PartialEq, Eq)]
105#[cfg_attr(feature = "capture", derive(Serialize))]
106#[cfg_attr(feature = "replay", derive(Deserialize))]
107pub struct GradientCacheKey {
108    pub orientation: LineOrientation,
109    pub start_stop_point: VectorKey,
110    pub stops: [GradientStopKey; GRADIENT_FP_STOPS],
111}
112
113#[cfg_attr(feature = "capture", derive(Serialize))]
114#[cfg_attr(feature = "replay", derive(Deserialize))]
115#[derive(MallocSizeOf)]
116pub struct LinearGradientTemplate {
117    pub common: PrimTemplateCommonData,
118    pub extend_mode: ExtendMode,
119    pub start_point: LayoutPoint,
120    pub end_point: LayoutPoint,
121    pub stretch_size: LayoutSize,
122    pub tile_spacing: LayoutSize,
123    pub stops_opacity: PrimitiveOpacity,
124    pub stops: Vec<GradientStop>,
125    pub brush_segments: Vec<BrushSegment>,
126    pub reverse_stops: bool,
127    pub stops_handle: GpuCacheHandle,
128    /// If true, this gradient can be drawn via the fast path
129    /// (cache gradient, and draw as image).
130    pub supports_caching: bool,
131}
132
133impl Deref for LinearGradientTemplate {
134    type Target = PrimTemplateCommonData;
135    fn deref(&self) -> &Self::Target {
136        &self.common
137    }
138}
139
140impl DerefMut for LinearGradientTemplate {
141    fn deref_mut(&mut self) -> &mut Self::Target {
142        &mut self.common
143    }
144}
145
146impl From<LinearGradientKey> for LinearGradientTemplate {
147    fn from(item: LinearGradientKey) -> Self {
148        let common = PrimTemplateCommonData::with_key_common(item.common);
149        let mut min_alpha: f32 = 1.0;
150
151        // Check if we can draw this gradient via a fast path by caching the
152        // gradient in a smaller task, and drawing as an image.
153        // TODO(gw): Aim to reduce the constraints on fast path gradients in future,
154        //           although this catches the vast majority of gradients on real pages.
155        let mut supports_caching =
156            // No repeating support in fast path
157            item.extend_mode == ExtendMode::Clamp &&
158            // Gradient must cover entire primitive
159            item.tile_spacing.w + item.stretch_size.w >= common.prim_size.width &&
160            item.tile_spacing.h + item.stretch_size.h >= common.prim_size.height &&
161            // Must be a vertical or horizontal gradient
162            (item.start_point.x.approx_eq(&item.end_point.x) ||
163             item.start_point.y.approx_eq(&item.end_point.y)) &&
164            // Fast path supports a limited number of stops
165            item.stops.len() <= GRADIENT_FP_STOPS &&
166            // Fast path not supported on segmented (border-image) gradients.
167            item.nine_patch.is_none();
168
169        let mut prev_offset = None;
170        // Convert the stops to more convenient representation
171        // for the current gradient builder.
172        let stops: Vec<GradientStop> = item.stops.iter().map(|stop| {
173            let color: ColorF = stop.color.into();
174            min_alpha = min_alpha.min(color.a);
175
176            // The fast path doesn't support hard color stops, yet.
177            // Since the length of the gradient is a fixed size (512 device pixels), if there
178            // is a hard stop you will see bilinear interpolation with this method, instead
179            // of an abrupt color change.
180            if prev_offset == Some(stop.offset) {
181                supports_caching = false;
182            }
183
184            prev_offset = Some(stop.offset);
185
186            GradientStop {
187                offset: stop.offset,
188                color,
189            }
190        }).collect();
191
192        let mut brush_segments = Vec::new();
193
194        if let Some(ref nine_patch) = item.nine_patch {
195            brush_segments = nine_patch.create_segments(common.prim_size);
196        }
197
198        // Save opacity of the stops for use in
199        // selecting which pass this gradient
200        // should be drawn in.
201        let stops_opacity = PrimitiveOpacity::from_alpha(min_alpha);
202
203        LinearGradientTemplate {
204            common,
205            extend_mode: item.extend_mode,
206            start_point: item.start_point.into(),
207            end_point: item.end_point.into(),
208            stretch_size: item.stretch_size.into(),
209            tile_spacing: item.tile_spacing.into(),
210            stops_opacity,
211            stops,
212            brush_segments,
213            reverse_stops: item.reverse_stops,
214            stops_handle: GpuCacheHandle::new(),
215            supports_caching,
216        }
217    }
218}
219
220impl LinearGradientTemplate {
221    /// Update the GPU cache for a given primitive template. This may be called multiple
222    /// times per frame, by each primitive reference that refers to this interned
223    /// template. The initial request call to the GPU cache ensures that work is only
224    /// done if the cache entry is invalid (due to first use or eviction).
225    pub fn update(
226        &mut self,
227        frame_state: &mut FrameBuildingState,
228    ) {
229        if let Some(mut request) =
230            frame_state.gpu_cache.request(&mut self.common.gpu_cache_handle) {
231            // write_prim_gpu_blocks
232            request.push([
233                self.start_point.x,
234                self.start_point.y,
235                self.end_point.x,
236                self.end_point.y,
237            ]);
238            request.push([
239                pack_as_float(self.extend_mode as u32),
240                self.stretch_size.width,
241                self.stretch_size.height,
242                0.0,
243            ]);
244
245            // write_segment_gpu_blocks
246            for segment in &self.brush_segments {
247                // has to match VECS_PER_SEGMENT
248                request.write_segment(
249                    segment.local_rect,
250                    segment.extra_data,
251                );
252            }
253        }
254
255        if let Some(mut request) = frame_state.gpu_cache.request(&mut self.stops_handle) {
256            GradientGpuBlockBuilder::build(
257                self.reverse_stops,
258                &mut request,
259                &self.stops,
260            );
261        }
262
263        self.opacity = {
264            // If the coverage of the gradient extends to or beyond
265            // the primitive rect, then the opacity can be determined
266            // by the colors of the stops. If we have tiling / spacing
267            // then we just assume the gradient is translucent for now.
268            // (In the future we could consider segmenting in some cases).
269            let stride = self.stretch_size + self.tile_spacing;
270            if stride.width >= self.common.prim_size.width &&
271               stride.height >= self.common.prim_size.height {
272                self.stops_opacity
273            } else {
274               PrimitiveOpacity::translucent()
275            }
276        }
277    }
278}
279
280pub type LinearGradientDataHandle = InternHandle<LinearGradient>;
281
282#[derive(Debug, MallocSizeOf)]
283#[cfg_attr(feature = "capture", derive(Serialize))]
284#[cfg_attr(feature = "replay", derive(Deserialize))]
285pub struct LinearGradient {
286    pub extend_mode: ExtendMode,
287    pub start_point: PointKey,
288    pub end_point: PointKey,
289    pub stretch_size: SizeKey,
290    pub tile_spacing: SizeKey,
291    pub stops: Vec<GradientStopKey>,
292    pub reverse_stops: bool,
293    pub nine_patch: Option<Box<NinePatchDescriptor>>,
294}
295
296impl Internable for LinearGradient {
297    type Key = LinearGradientKey;
298    type StoreData = LinearGradientTemplate;
299    type InternData = PrimitiveSceneData;
300}
301
302impl InternablePrimitive for LinearGradient {
303    fn into_key(
304        self,
305        info: &LayoutPrimitiveInfo,
306    ) -> LinearGradientKey {
307        LinearGradientKey::new(
308            info.flags,
309            info.rect.size,
310            self
311        )
312    }
313
314    fn make_instance_kind(
315        _key: LinearGradientKey,
316        data_handle: LinearGradientDataHandle,
317        prim_store: &mut PrimitiveStore,
318        _reference_frame_relative_offset: LayoutVector2D,
319    ) -> PrimitiveInstanceKind {
320        let gradient_index = prim_store.linear_gradients.push(LinearGradientPrimitive {
321            cache_handle: None,
322            visible_tiles_range: GradientTileRange::empty(),
323        });
324
325        PrimitiveInstanceKind::LinearGradient {
326            data_handle,
327            gradient_index,
328        }
329    }
330}
331
332impl IsVisible for LinearGradient {
333    fn is_visible(&self) -> bool {
334        true
335    }
336}
337
338#[derive(Debug)]
339#[cfg_attr(feature = "capture", derive(Serialize))]
340pub struct LinearGradientPrimitive {
341    pub cache_handle: Option<RenderTaskCacheEntryHandle>,
342    pub visible_tiles_range: GradientTileRange,
343}
344
345////////////////////////////////////////////////////////////////////////////////
346
347/// Hashable radial gradient parameters, for use during prim interning.
348#[cfg_attr(feature = "capture", derive(Serialize))]
349#[cfg_attr(feature = "replay", derive(Deserialize))]
350#[derive(Debug, Clone, MallocSizeOf, PartialEq)]
351pub struct RadialGradientParams {
352    pub start_radius: f32,
353    pub end_radius: f32,
354    pub ratio_xy: f32,
355}
356
357impl Eq for RadialGradientParams {}
358
359impl hash::Hash for RadialGradientParams {
360    fn hash<H: hash::Hasher>(&self, state: &mut H) {
361        self.start_radius.to_bits().hash(state);
362        self.end_radius.to_bits().hash(state);
363        self.ratio_xy.to_bits().hash(state);
364    }
365}
366
367/// Identifying key for a line decoration.
368#[cfg_attr(feature = "capture", derive(Serialize))]
369#[cfg_attr(feature = "replay", derive(Deserialize))]
370#[derive(Debug, Clone, Eq, PartialEq, Hash, MallocSizeOf)]
371pub struct RadialGradientKey {
372    pub common: PrimKeyCommonData,
373    pub extend_mode: ExtendMode,
374    pub center: PointKey,
375    pub params: RadialGradientParams,
376    pub stretch_size: SizeKey,
377    pub stops: Vec<GradientStopKey>,
378    pub tile_spacing: SizeKey,
379    pub nine_patch: Option<Box<NinePatchDescriptor>>,
380}
381
382impl RadialGradientKey {
383    pub fn new(
384        flags: PrimitiveFlags,
385        prim_size: LayoutSize,
386        radial_grad: RadialGradient,
387    ) -> Self {
388        RadialGradientKey {
389            common: PrimKeyCommonData {
390                flags,
391                prim_size: prim_size.into(),
392            },
393            extend_mode: radial_grad.extend_mode,
394            center: radial_grad.center,
395            params: radial_grad.params,
396            stretch_size: radial_grad.stretch_size,
397            stops: radial_grad.stops,
398            tile_spacing: radial_grad.tile_spacing,
399            nine_patch: radial_grad.nine_patch,
400        }
401    }
402}
403
404impl InternDebug for RadialGradientKey {}
405
406#[cfg_attr(feature = "capture", derive(Serialize))]
407#[cfg_attr(feature = "replay", derive(Deserialize))]
408#[derive(MallocSizeOf)]
409pub struct RadialGradientTemplate {
410    pub common: PrimTemplateCommonData,
411    pub extend_mode: ExtendMode,
412    pub center: LayoutPoint,
413    pub params: RadialGradientParams,
414    pub stretch_size: LayoutSize,
415    pub tile_spacing: LayoutSize,
416    pub brush_segments: Vec<BrushSegment>,
417    pub stops: Vec<GradientStop>,
418    pub stops_handle: GpuCacheHandle,
419}
420
421impl Deref for RadialGradientTemplate {
422    type Target = PrimTemplateCommonData;
423    fn deref(&self) -> &Self::Target {
424        &self.common
425    }
426}
427
428impl DerefMut for RadialGradientTemplate {
429    fn deref_mut(&mut self) -> &mut Self::Target {
430        &mut self.common
431    }
432}
433
434impl From<RadialGradientKey> for RadialGradientTemplate {
435    fn from(item: RadialGradientKey) -> Self {
436        let common = PrimTemplateCommonData::with_key_common(item.common);
437        let mut brush_segments = Vec::new();
438
439        if let Some(ref nine_patch) = item.nine_patch {
440            brush_segments = nine_patch.create_segments(common.prim_size);
441        }
442
443        let stops = item.stops.iter().map(|stop| {
444            GradientStop {
445                offset: stop.offset,
446                color: stop.color.into(),
447            }
448        }).collect();
449
450        RadialGradientTemplate {
451            common,
452            center: item.center.into(),
453            extend_mode: item.extend_mode,
454            params: item.params,
455            stretch_size: item.stretch_size.into(),
456            tile_spacing: item.tile_spacing.into(),
457            brush_segments,
458            stops,
459            stops_handle: GpuCacheHandle::new(),
460        }
461    }
462}
463
464impl RadialGradientTemplate {
465    /// Update the GPU cache for a given primitive template. This may be called multiple
466    /// times per frame, by each primitive reference that refers to this interned
467    /// template. The initial request call to the GPU cache ensures that work is only
468    /// done if the cache entry is invalid (due to first use or eviction).
469    pub fn update(
470        &mut self,
471        frame_state: &mut FrameBuildingState,
472    ) {
473        if let Some(mut request) =
474            frame_state.gpu_cache.request(&mut self.common.gpu_cache_handle) {
475            // write_prim_gpu_blocks
476            request.push([
477                self.center.x,
478                self.center.y,
479                self.params.start_radius,
480                self.params.end_radius,
481            ]);
482            request.push([
483                self.params.ratio_xy,
484                pack_as_float(self.extend_mode as u32),
485                self.stretch_size.width,
486                self.stretch_size.height,
487            ]);
488
489            // write_segment_gpu_blocks
490            for segment in &self.brush_segments {
491                // has to match VECS_PER_SEGMENT
492                request.write_segment(
493                    segment.local_rect,
494                    segment.extra_data,
495                );
496            }
497        }
498
499        if let Some(mut request) = frame_state.gpu_cache.request(&mut self.stops_handle) {
500            GradientGpuBlockBuilder::build(
501                false,
502                &mut request,
503                &self.stops,
504            );
505        }
506
507        self.opacity = PrimitiveOpacity::translucent();
508    }
509}
510
511pub type RadialGradientDataHandle = InternHandle<RadialGradient>;
512
513#[derive(Debug, MallocSizeOf)]
514#[cfg_attr(feature = "capture", derive(Serialize))]
515#[cfg_attr(feature = "replay", derive(Deserialize))]
516pub struct RadialGradient {
517    pub extend_mode: ExtendMode,
518    pub center: PointKey,
519    pub params: RadialGradientParams,
520    pub stretch_size: SizeKey,
521    pub stops: Vec<GradientStopKey>,
522    pub tile_spacing: SizeKey,
523    pub nine_patch: Option<Box<NinePatchDescriptor>>,
524}
525
526impl Internable for RadialGradient {
527    type Key = RadialGradientKey;
528    type StoreData = RadialGradientTemplate;
529    type InternData = PrimitiveSceneData;
530}
531
532impl InternablePrimitive for RadialGradient {
533    fn into_key(
534        self,
535        info: &LayoutPrimitiveInfo,
536    ) -> RadialGradientKey {
537        RadialGradientKey::new(
538            info.flags,
539            info.rect.size,
540            self,
541        )
542    }
543
544    fn make_instance_kind(
545        _key: RadialGradientKey,
546        data_handle: RadialGradientDataHandle,
547        _prim_store: &mut PrimitiveStore,
548        _reference_frame_relative_offset: LayoutVector2D,
549    ) -> PrimitiveInstanceKind {
550        PrimitiveInstanceKind::RadialGradient {
551            data_handle,
552            visible_tiles_range: GradientTileRange::empty(),
553        }
554    }
555}
556
557impl IsVisible for RadialGradient {
558    fn is_visible(&self) -> bool {
559        true
560    }
561}
562
563////////////////////////////////////////////////////////////////////////////////
564
565// The gradient entry index for the first color stop
566pub const GRADIENT_DATA_FIRST_STOP: usize = 0;
567// The gradient entry index for the last color stop
568pub const GRADIENT_DATA_LAST_STOP: usize = GRADIENT_DATA_SIZE - 1;
569
570// The start of the gradient data table
571pub const GRADIENT_DATA_TABLE_BEGIN: usize = GRADIENT_DATA_FIRST_STOP + 1;
572// The exclusive bound of the gradient data table
573pub const GRADIENT_DATA_TABLE_END: usize = GRADIENT_DATA_LAST_STOP;
574// The number of entries in the gradient data table.
575pub const GRADIENT_DATA_TABLE_SIZE: usize = 128;
576
577// The number of entries in a gradient data: GRADIENT_DATA_TABLE_SIZE + first stop entry + last stop entry
578pub const GRADIENT_DATA_SIZE: usize = GRADIENT_DATA_TABLE_SIZE + 2;
579
580/// An entry in a gradient data table representing a segment of the gradient
581/// color space.
582#[derive(Debug, Copy, Clone)]
583#[repr(C)]
584struct GradientDataEntry {
585    start_color: PremultipliedColorF,
586    end_color: PremultipliedColorF,
587}
588
589impl GradientDataEntry {
590    fn white() -> Self {
591        Self {
592            start_color: PremultipliedColorF::WHITE,
593            end_color: PremultipliedColorF::WHITE,
594        }
595    }
596}
597
598// TODO(gw): Tidy this up to be a free function / module?
599struct GradientGpuBlockBuilder {}
600
601impl GradientGpuBlockBuilder {
602    /// Generate a color ramp filling the indices in [start_idx, end_idx) and interpolating
603    /// from start_color to end_color.
604    fn fill_colors(
605        start_idx: usize,
606        end_idx: usize,
607        start_color: &PremultipliedColorF,
608        end_color: &PremultipliedColorF,
609        entries: &mut [GradientDataEntry; GRADIENT_DATA_SIZE],
610    ) {
611        // Calculate the color difference for individual steps in the ramp.
612        let inv_steps = 1.0 / (end_idx - start_idx) as f32;
613        let step_r = (end_color.r - start_color.r) * inv_steps;
614        let step_g = (end_color.g - start_color.g) * inv_steps;
615        let step_b = (end_color.b - start_color.b) * inv_steps;
616        let step_a = (end_color.a - start_color.a) * inv_steps;
617
618        let mut cur_color = *start_color;
619
620        // Walk the ramp writing start and end colors for each entry.
621        for index in start_idx .. end_idx {
622            let entry = &mut entries[index];
623            entry.start_color = cur_color;
624            cur_color.r += step_r;
625            cur_color.g += step_g;
626            cur_color.b += step_b;
627            cur_color.a += step_a;
628            entry.end_color = cur_color;
629        }
630    }
631
632    /// Compute an index into the gradient entry table based on a gradient stop offset. This
633    /// function maps offsets from [0, 1] to indices in [GRADIENT_DATA_TABLE_BEGIN, GRADIENT_DATA_TABLE_END].
634    #[inline]
635    fn get_index(offset: f32) -> usize {
636        (offset.max(0.0).min(1.0) * GRADIENT_DATA_TABLE_SIZE as f32 +
637            GRADIENT_DATA_TABLE_BEGIN as f32)
638            .round() as usize
639    }
640
641    // Build the gradient data from the supplied stops, reversing them if necessary.
642    fn build(
643        reverse_stops: bool,
644        request: &mut GpuDataRequest,
645        src_stops: &[GradientStop],
646    ) {
647        // Preconditions (should be ensured by DisplayListBuilder):
648        // * we have at least two stops
649        // * first stop has offset 0.0
650        // * last stop has offset 1.0
651        let mut src_stops = src_stops.into_iter();
652        let mut cur_color = match src_stops.next() {
653            Some(stop) => {
654                debug_assert_eq!(stop.offset, 0.0);
655                stop.color.premultiplied()
656            }
657            None => {
658                error!("Zero gradient stops found!");
659                PremultipliedColorF::BLACK
660            }
661        };
662
663        // A table of gradient entries, with two colors per entry, that specify the start and end color
664        // within the segment of the gradient space represented by that entry. To lookup a gradient result,
665        // first the entry index is calculated to determine which two colors to interpolate between, then
666        // the offset within that entry bucket is used to interpolate between the two colors in that entry.
667        // This layout preserves hard stops, as the end color for a given entry can differ from the start
668        // color for the following entry, despite them being adjacent. Colors are stored within in BGRA8
669        // format for texture upload. This table requires the gradient color stops to be normalized to the
670        // range [0, 1]. The first and last entries hold the first and last color stop colors respectively,
671        // while the entries in between hold the interpolated color stop values for the range [0, 1].
672        let mut entries = [GradientDataEntry::white(); GRADIENT_DATA_SIZE];
673
674        if reverse_stops {
675            // Fill in the first entry (for reversed stops) with the first color stop
676            GradientGpuBlockBuilder::fill_colors(
677                GRADIENT_DATA_LAST_STOP,
678                GRADIENT_DATA_LAST_STOP + 1,
679                &cur_color,
680                &cur_color,
681                &mut entries,
682            );
683
684            // Fill in the center of the gradient table, generating a color ramp between each consecutive pair
685            // of gradient stops. Each iteration of a loop will fill the indices in [next_idx, cur_idx). The
686            // loop will then fill indices in [GRADIENT_DATA_TABLE_BEGIN, GRADIENT_DATA_TABLE_END).
687            let mut cur_idx = GRADIENT_DATA_TABLE_END;
688            for next in src_stops {
689                let next_color = next.color.premultiplied();
690                let next_idx = Self::get_index(1.0 - next.offset);
691
692                if next_idx < cur_idx {
693                    GradientGpuBlockBuilder::fill_colors(
694                        next_idx,
695                        cur_idx,
696                        &next_color,
697                        &cur_color,
698                        &mut entries,
699                    );
700                    cur_idx = next_idx;
701                }
702
703                cur_color = next_color;
704            }
705            if cur_idx != GRADIENT_DATA_TABLE_BEGIN {
706                error!("Gradient stops abruptly at {}, auto-completing to white", cur_idx);
707            }
708
709            // Fill in the last entry (for reversed stops) with the last color stop
710            GradientGpuBlockBuilder::fill_colors(
711                GRADIENT_DATA_FIRST_STOP,
712                GRADIENT_DATA_FIRST_STOP + 1,
713                &cur_color,
714                &cur_color,
715                &mut entries,
716            );
717        } else {
718            // Fill in the first entry with the first color stop
719            GradientGpuBlockBuilder::fill_colors(
720                GRADIENT_DATA_FIRST_STOP,
721                GRADIENT_DATA_FIRST_STOP + 1,
722                &cur_color,
723                &cur_color,
724                &mut entries,
725            );
726
727            // Fill in the center of the gradient table, generating a color ramp between each consecutive pair
728            // of gradient stops. Each iteration of a loop will fill the indices in [cur_idx, next_idx). The
729            // loop will then fill indices in [GRADIENT_DATA_TABLE_BEGIN, GRADIENT_DATA_TABLE_END).
730            let mut cur_idx = GRADIENT_DATA_TABLE_BEGIN;
731            for next in src_stops {
732                let next_color = next.color.premultiplied();
733                let next_idx = Self::get_index(next.offset);
734
735                if next_idx > cur_idx {
736                    GradientGpuBlockBuilder::fill_colors(
737                        cur_idx,
738                        next_idx,
739                        &cur_color,
740                        &next_color,
741                        &mut entries,
742                    );
743                    cur_idx = next_idx;
744                }
745
746                cur_color = next_color;
747            }
748            if cur_idx != GRADIENT_DATA_TABLE_END {
749                error!("Gradient stops abruptly at {}, auto-completing to white", cur_idx);
750            }
751
752            // Fill in the last entry with the last color stop
753            GradientGpuBlockBuilder::fill_colors(
754                GRADIENT_DATA_LAST_STOP,
755                GRADIENT_DATA_LAST_STOP + 1,
756                &cur_color,
757                &cur_color,
758                &mut entries,
759            );
760        }
761
762        for entry in entries.iter() {
763            request.push(entry.start_color);
764            request.push(entry.end_color);
765        }
766    }
767}
768
769#[test]
770#[cfg(target_pointer_width = "64")]
771fn test_struct_sizes() {
772    use std::mem;
773    // The sizes of these structures are critical for performance on a number of
774    // talos stress tests. If you get a failure here on CI, there's two possibilities:
775    // (a) You made a structure smaller than it currently is. Great work! Update the
776    //     test expectations and move on.
777    // (b) You made a structure larger. This is not necessarily a problem, but should only
778    //     be done with care, and after checking if talos performance regresses badly.
779    assert_eq!(mem::size_of::<LinearGradient>(), 72, "LinearGradient size changed");
780    assert_eq!(mem::size_of::<LinearGradientTemplate>(), 112, "LinearGradientTemplate size changed");
781    assert_eq!(mem::size_of::<LinearGradientKey>(), 80, "LinearGradientKey size changed");
782
783    assert_eq!(mem::size_of::<RadialGradient>(), 72, "RadialGradient size changed");
784    assert_eq!(mem::size_of::<RadialGradientTemplate>(), 120, "RadialGradientTemplate size changed");
785    assert_eq!(mem::size_of::<RadialGradientKey>(), 88, "RadialGradientKey size changed");
786}