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