1use 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#[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
151pub 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: &mut dyn FnMut(&LayoutRect, LayoutPoint, LayoutPoint, &[GradientStopKey], EdgeAaSegmentMask)
167) -> bool {
168 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 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 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 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 !enable_dithering &&
228 ((horizontal && tile_size.width < 256.0)
229 || (vertical && tile_size.height < 256.0)) {
230 return false;
231 }
232
233 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 let reverse_stops = start.x > end.x;
275
276 if reverse_stops {
278 stops.reverse();
279 swap(start, end);
280 }
281
282 let mut prev = *stops.first().unwrap();
285 let mut last = *stops.last().unwrap();
286
287 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 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 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 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 task_size.height = 1.0;
413 }
414
415 if vertical {
416 task_size.width = 1.0;
418 }
419
420 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 stops.swap(0, 1);
448 }
449 }
450
451 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 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 if self.cached {
502 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 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 for segment in &self.brush_segments {
529 request.write_segment(
531 segment.local_rect,
532 segment.extra_data,
533 );
534 }
535 }
536
537 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#[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#[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}