1use euclid::approxeq::ApproxEq;
12use euclid::{point2, vec2, size2};
13use api::{ExtendMode, GradientStop, LineOrientation, PremultipliedColorF, ColorF, ColorU};
14use api::units::*;
15use crate::scene_building::IsVisible;
16use crate::frame_builder::FrameBuildingState;
17use crate::intern::{Internable, InternDebug, Handle as InternHandle};
18use crate::internal_types::LayoutPrimitiveInfo;
19use crate::image_tiling::simplify_repeated_primitive;
20use crate::prim_store::{BrushSegment, GradientTileRange};
21use crate::prim_store::{PrimitiveInstanceKind, PrimitiveOpacity};
22use crate::prim_store::{PrimKeyCommonData, PrimTemplateCommonData, PrimitiveStore};
23use crate::prim_store::{NinePatchDescriptor, PointKey, SizeKey, InternablePrimitive};
24use crate::render_task::{RenderTask, RenderTaskKind};
25use crate::render_task_graph::RenderTaskId;
26use crate::render_task_cache::{RenderTaskCacheKeyKind, RenderTaskCacheKey, RenderTaskParent};
27use crate::renderer::GpuBufferAddress;
28use crate::segment::EdgeAaSegmentMask;
29use crate::util::pack_as_float;
30use super::{stops_and_min_alpha, GradientStopKey, GradientGpuBlockBuilder, apply_gradient_local_clip};
31use std::ops::{Deref, DerefMut};
32use std::mem::swap;
33
34pub const MAX_CACHED_SIZE: f32 = 1024.0;
35
36#[cfg_attr(feature = "capture", derive(Serialize))]
38#[cfg_attr(feature = "replay", derive(Deserialize))]
39#[derive(Debug, Clone, Eq, PartialEq, Hash, MallocSizeOf)]
40pub struct LinearGradientKey {
41 pub common: PrimKeyCommonData,
42 pub extend_mode: ExtendMode,
43 pub start_point: PointKey,
44 pub end_point: PointKey,
45 pub stretch_size: SizeKey,
46 pub tile_spacing: SizeKey,
47 pub stops: Vec<GradientStopKey>,
48 pub reverse_stops: bool,
49 pub cached: bool,
50 pub nine_patch: Option<Box<NinePatchDescriptor>>,
51 pub edge_aa_mask: EdgeAaSegmentMask,
52}
53
54impl LinearGradientKey {
55 pub fn new(
56 info: &LayoutPrimitiveInfo,
57 linear_grad: LinearGradient,
58 ) -> Self {
59 LinearGradientKey {
60 common: info.into(),
61 extend_mode: linear_grad.extend_mode,
62 start_point: linear_grad.start_point,
63 end_point: linear_grad.end_point,
64 stretch_size: linear_grad.stretch_size,
65 tile_spacing: linear_grad.tile_spacing,
66 stops: linear_grad.stops,
67 reverse_stops: linear_grad.reverse_stops,
68 cached: linear_grad.cached,
69 nine_patch: linear_grad.nine_patch,
70 edge_aa_mask: linear_grad.edge_aa_mask,
71 }
72 }
73}
74
75impl InternDebug for LinearGradientKey {}
76
77#[cfg_attr(feature = "capture", derive(Serialize))]
78#[cfg_attr(feature = "replay", derive(Deserialize))]
79#[derive(Debug, MallocSizeOf)]
80pub struct LinearGradientTemplate {
81 pub common: PrimTemplateCommonData,
82 pub extend_mode: ExtendMode,
83 pub start_point: DevicePoint,
84 pub end_point: DevicePoint,
85 pub task_size: DeviceIntSize,
86 pub scale: DeviceVector2D,
87 pub stretch_size: LayoutSize,
88 pub tile_spacing: LayoutSize,
89 pub stops_opacity: PrimitiveOpacity,
90 pub stops: Vec<GradientStop>,
91 pub brush_segments: Vec<BrushSegment>,
92 pub reverse_stops: bool,
93 pub is_fast_path: bool,
94 pub cached: bool,
95 pub src_color: Option<RenderTaskId>,
96}
97
98impl Deref for LinearGradientTemplate {
99 type Target = PrimTemplateCommonData;
100 fn deref(&self) -> &Self::Target {
101 &self.common
102 }
103}
104
105impl DerefMut for LinearGradientTemplate {
106 fn deref_mut(&mut self) -> &mut Self::Target {
107 &mut self.common
108 }
109}
110
111pub fn optimize_linear_gradient(
116 prim_rect: &mut LayoutRect,
117 tile_size: &mut LayoutSize,
118 mut tile_spacing: LayoutSize,
119 clip_rect: &LayoutRect,
120 start: &mut LayoutPoint,
121 end: &mut LayoutPoint,
122 extend_mode: ExtendMode,
123 stops: &mut [GradientStopKey],
124 callback: &mut dyn FnMut(&LayoutRect, LayoutPoint, LayoutPoint, &[GradientStopKey], EdgeAaSegmentMask)
126) -> bool {
127 simplify_repeated_primitive(&tile_size, &mut tile_spacing, prim_rect);
131
132 let vertical = start.x.approx_eq(&end.x);
133 let horizontal = start.y.approx_eq(&end.y);
134
135 let mut horizontally_tiled = prim_rect.width() > tile_size.width;
136 let mut vertically_tiled = prim_rect.height() > tile_size.height;
137
138 if vertically_tiled && horizontal && tile_spacing.height == 0.0 {
141 tile_size.height = prim_rect.height();
142 vertically_tiled = false;
143 }
144
145 if horizontally_tiled && vertical && tile_spacing.width == 0.0 {
146 tile_size.width = prim_rect.width();
147 horizontally_tiled = false;
148 }
149
150 let offset = apply_gradient_local_clip(
151 prim_rect,
152 &tile_size,
153 &tile_spacing,
154 &clip_rect
155 );
156
157 tile_size.width = tile_size.width.min(prim_rect.width());
160 tile_size.height = tile_size.height.min(prim_rect.height());
161
162 *start += offset;
163 *end += offset;
164
165 if extend_mode != ExtendMode::Clamp || stops.is_empty() {
170 return false;
171 }
172
173 if !vertical && !horizontal {
174 return false;
175 }
176
177 if vertical && horizontal {
178 return false;
179 }
180
181 if !tile_spacing.is_empty() || vertically_tiled || horizontally_tiled {
182 return false;
183 }
184
185 if (horizontal && tile_size.width < 256.0)
187 || (vertical && tile_size.height < 256.0) {
188
189 return false;
190 }
191
192 let adjust_rect = &mut |rect: &mut LayoutRect| {
200 if vertical {
201 swap(&mut rect.min.x, &mut rect.min.y);
202 swap(&mut rect.max.x, &mut rect.max.y);
203 }
204 };
205
206 let adjust_size = &mut |size: &mut LayoutSize| {
207 if vertical { swap(&mut size.width, &mut size.height); }
208 };
209
210 let adjust_point = &mut |p: &mut LayoutPoint| {
211 if vertical { swap(&mut p.x, &mut p.y); }
212 };
213
214 let clip_rect = match clip_rect.intersection(prim_rect) {
215 Some(clip) => clip,
216 None => {
217 return false;
218 }
219 };
220
221 adjust_rect(prim_rect);
222 adjust_point(start);
223 adjust_point(end);
224 adjust_size(tile_size);
225
226 let length = (end.x - start.x).abs();
227
228 let reverse_stops = start.x > end.x;
234
235 if reverse_stops {
237 stops.reverse();
238 swap(start, end);
239 }
240
241 let mut prev = *stops.first().unwrap();
244 let mut last = *stops.last().unwrap();
245
246 prev.offset = -start.x / length;
248 last.offset = (tile_size.width - start.x) / length;
249 if reverse_stops {
250 prev.offset = 1.0 - prev.offset;
251 last.offset = 1.0 - last.offset;
252 }
253
254 let (side_edges, first_edge, last_edge) = if vertical {
255 (
256 EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::RIGHT,
257 EdgeAaSegmentMask::TOP,
258 EdgeAaSegmentMask::BOTTOM
259 )
260 } else {
261 (
262 EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::BOTTOM,
263 EdgeAaSegmentMask::LEFT,
264 EdgeAaSegmentMask::RIGHT
265 )
266 };
267
268 let mut is_first = true;
269 let last_offset = last.offset;
270 for stop in stops.iter().chain((&[last]).iter()) {
271 let prev_stop = prev;
272 prev = *stop;
273
274 if prev_stop.color.a == 0 && stop.color.a == 0 {
275 continue;
276 }
277
278
279 let prev_offset = if reverse_stops { 1.0 - prev_stop.offset } else { prev_stop.offset };
280 let offset = if reverse_stops { 1.0 - stop.offset } else { stop.offset };
281
282 let segment_start = start.x + prev_offset * length;
284 let segment_end = start.x + offset * length;
285 let segment_length = segment_end - segment_start;
286
287 if segment_length <= 0.0 {
288 continue;
289 }
290
291 let mut segment_rect = *prim_rect;
292 segment_rect.min.x += segment_start;
293 segment_rect.max.x = segment_rect.min.x + segment_length;
294
295 let mut start = point2(0.0, 0.0);
296 let mut end = point2(segment_length, 0.0);
297
298 adjust_point(&mut start);
299 adjust_point(&mut end);
300 adjust_rect(&mut segment_rect);
301
302 let origin_before_clip = segment_rect.min;
303 segment_rect = match segment_rect.intersection(&clip_rect) {
304 Some(rect) => rect,
305 None => {
306 continue;
307 }
308 };
309 let offset = segment_rect.min - origin_before_clip;
310
311 start -= offset;
313 end -= offset;
314
315 let mut edge_flags = side_edges;
316 if is_first {
317 edge_flags |= first_edge;
318 is_first = false;
319 }
320 if stop.offset == last_offset {
321 edge_flags |= last_edge;
322 }
323
324 callback(
325 &segment_rect,
326 start,
327 end,
328 &[
329 GradientStopKey { offset: 0.0, .. prev_stop },
330 GradientStopKey { offset: 1.0, .. *stop },
331 ],
332 edge_flags,
333 );
334 }
335
336 true
337}
338
339impl From<LinearGradientKey> for LinearGradientTemplate {
340 fn from(item: LinearGradientKey) -> Self {
341
342 let mut common = PrimTemplateCommonData::with_key_common(item.common);
343 common.edge_aa_mask = item.edge_aa_mask;
344
345 let (mut stops, min_alpha) = stops_and_min_alpha(&item.stops);
346
347 let mut brush_segments = Vec::new();
348
349 if let Some(ref nine_patch) = item.nine_patch {
350 brush_segments = nine_patch.create_segments(common.prim_rect.size());
351 }
352
353 let stops_opacity = PrimitiveOpacity::from_alpha(min_alpha);
357
358 let start_point = DevicePoint::new(item.start_point.x, item.start_point.y);
359 let end_point = DevicePoint::new(item.end_point.x, item.end_point.y);
360 let tile_spacing: LayoutSize = item.tile_spacing.into();
361 let stretch_size: LayoutSize = item.stretch_size.into();
362 let mut task_size: DeviceSize = stretch_size.cast_unit();
363
364 let horizontal = start_point.y.approx_eq(&end_point.y);
365 let vertical = start_point.x.approx_eq(&end_point.x);
366
367 if horizontal {
368 task_size.height = 1.0;
370 }
371
372 if vertical {
373 task_size.width = 1.0;
375 }
376
377 let mut is_fast_path = false;
380 if item.cached && stops.len() == 2 && brush_segments.is_empty() {
381 if horizontal
382 && stretch_size.width >= common.prim_rect.width()
383 && start_point.x.approx_eq(&0.0)
384 && end_point.x.approx_eq(&stretch_size.width) {
385 is_fast_path = true;
386 task_size.width = task_size.width.min(256.0);
387 }
388 if vertical
389 && stretch_size.height >= common.prim_rect.height()
390 && start_point.y.approx_eq(&0.0)
391 && end_point.y.approx_eq(&stretch_size.height) {
392 is_fast_path = true;
393 task_size.height = task_size.height.min(256.0);
394 }
395
396 if stops[0].color == stops[1].color {
397 is_fast_path = true;
398 task_size = size2(1.0, 1.0);
399 }
400
401 if is_fast_path && item.reverse_stops {
402 stops.swap(0, 1);
405 }
406 }
407
408 let mut scale = vec2(1.0, 1.0);
413
414 if task_size.width > MAX_CACHED_SIZE {
415 scale.x = task_size.width / MAX_CACHED_SIZE;
416 task_size.width = MAX_CACHED_SIZE;
417 }
418
419 if task_size.height > MAX_CACHED_SIZE {
420 scale.y = task_size.height / MAX_CACHED_SIZE;
421 task_size.height = MAX_CACHED_SIZE;
422 }
423
424 LinearGradientTemplate {
425 common,
426 extend_mode: item.extend_mode,
427 start_point,
428 end_point,
429 task_size: task_size.ceil().to_i32(),
430 scale,
431 stretch_size,
432 tile_spacing,
433 stops_opacity,
434 stops,
435 brush_segments,
436 reverse_stops: item.reverse_stops,
437 is_fast_path,
438 cached: item.cached,
439 src_color: None,
440 }
441 }
442}
443
444impl LinearGradientTemplate {
445 pub fn update(
450 &mut self,
451 frame_state: &mut FrameBuildingState,
452 ) {
453 if let Some(mut request) = frame_state.gpu_cache.request(
454 &mut self.common.gpu_cache_handle
455 ) {
456
457 if self.cached {
459 request.push(PremultipliedColorF::WHITE);
461 request.push(PremultipliedColorF::WHITE);
462 request.push([
463 self.stretch_size.width,
464 self.stretch_size.height,
465 0.0,
466 0.0,
467 ]);
468 } else {
469 request.push([
471 self.start_point.x,
472 self.start_point.y,
473 self.end_point.x,
474 self.end_point.y,
475 ]);
476 request.push([
477 pack_as_float(self.extend_mode as u32),
478 self.stretch_size.width,
479 self.stretch_size.height,
480 0.0,
481 ]);
482 }
483
484 for segment in &self.brush_segments {
486 request.write_segment(
488 segment.local_rect,
489 segment.extra_data,
490 );
491 }
492 }
493
494 self.opacity = self.stops_opacity;
499
500 if !self.cached {
501 return;
502 }
503
504 let task_id = if self.is_fast_path {
505 let orientation = if self.task_size.width > self.task_size.height {
506 LineOrientation::Horizontal
507 } else {
508 LineOrientation::Vertical
509 };
510
511 let gradient = FastLinearGradientTask {
512 color0: self.stops[0].color.into(),
513 color1: self.stops[1].color.into(),
514 orientation,
515 };
516
517 frame_state.resource_cache.request_render_task(
518 Some(RenderTaskCacheKey {
519 size: self.task_size,
520 kind: RenderTaskCacheKeyKind::FastLinearGradient(gradient),
521 }),
522 false,
523 RenderTaskParent::Surface,
524 frame_state.gpu_cache,
525 &mut frame_state.frame_gpu_data.f32,
526 frame_state.rg_builder,
527 &mut frame_state.surface_builder,
528 &mut |rg_builder, _, _| {
529 rg_builder.add().init(RenderTask::new_dynamic(
530 self.task_size,
531 RenderTaskKind::FastLinearGradient(gradient),
532 ))
533 }
534 )
535 } else {
536 let cache_key = LinearGradientCacheKey {
537 size: self.task_size,
538 start: PointKey { x: self.start_point.x, y: self.start_point.y },
539 end: PointKey { x: self.end_point.x, y: self.end_point.y },
540 scale: PointKey { x: self.scale.x, y: self.scale.y },
541 extend_mode: self.extend_mode,
542 stops: self.stops.iter().map(|stop| (*stop).into()).collect(),
543 reversed_stops: self.reverse_stops,
544 };
545
546 frame_state.resource_cache.request_render_task(
547 Some(RenderTaskCacheKey {
548 size: self.task_size,
549 kind: RenderTaskCacheKeyKind::LinearGradient(cache_key),
550 }),
551 false,
552 RenderTaskParent::Surface,
553 frame_state.gpu_cache,
554 &mut frame_state.frame_gpu_data.f32,
555 frame_state.rg_builder,
556 &mut frame_state.surface_builder,
557 &mut |rg_builder, gpu_buffer_builder, _| {
558 let stops = Some(GradientGpuBlockBuilder::build(
559 self.reverse_stops,
560 gpu_buffer_builder,
561 &self.stops,
562 ));
563
564 rg_builder.add().init(RenderTask::new_dynamic(
565 self.task_size,
566 RenderTaskKind::LinearGradient(LinearGradientTask {
567 start: self.start_point,
568 end: self.end_point,
569 scale: self.scale,
570 extend_mode: self.extend_mode,
571 stops: stops.unwrap(),
572 }),
573 ))
574 }
575 )
576 };
577
578 self.src_color = Some(task_id);
579 }
580}
581
582pub type LinearGradientDataHandle = InternHandle<LinearGradient>;
583
584#[derive(Debug, MallocSizeOf)]
585#[cfg_attr(feature = "capture", derive(Serialize))]
586#[cfg_attr(feature = "replay", derive(Deserialize))]
587pub struct LinearGradient {
588 pub extend_mode: ExtendMode,
589 pub start_point: PointKey,
590 pub end_point: PointKey,
591 pub stretch_size: SizeKey,
592 pub tile_spacing: SizeKey,
593 pub stops: Vec<GradientStopKey>,
594 pub reverse_stops: bool,
595 pub nine_patch: Option<Box<NinePatchDescriptor>>,
596 pub cached: bool,
597 pub edge_aa_mask: EdgeAaSegmentMask,
598}
599
600impl Internable for LinearGradient {
601 type Key = LinearGradientKey;
602 type StoreData = LinearGradientTemplate;
603 type InternData = ();
604 const PROFILE_COUNTER: usize = crate::profiler::INTERNED_LINEAR_GRADIENTS;
605}
606
607impl InternablePrimitive for LinearGradient {
608 fn into_key(
609 self,
610 info: &LayoutPrimitiveInfo,
611 ) -> LinearGradientKey {
612 LinearGradientKey::new(info, self)
613 }
614
615 fn make_instance_kind(
616 key: LinearGradientKey,
617 data_handle: LinearGradientDataHandle,
618 _prim_store: &mut PrimitiveStore,
619 ) -> PrimitiveInstanceKind {
620 if key.cached {
621 PrimitiveInstanceKind::CachedLinearGradient {
622 data_handle,
623 visible_tiles_range: GradientTileRange::empty(),
624 }
625 } else {
626 PrimitiveInstanceKind::LinearGradient {
627 data_handle,
628 visible_tiles_range: GradientTileRange::empty(),
629 }
630 }
631 }
632}
633
634impl IsVisible for LinearGradient {
635 fn is_visible(&self) -> bool {
636 true
637 }
638}
639
640#[derive(Debug)]
641#[cfg_attr(feature = "capture", derive(Serialize))]
642pub struct LinearGradientPrimitive {
643 pub cache_segments: Vec<CachedGradientSegment>,
644 pub visible_tiles_range: GradientTileRange,
645}
646
647#[derive(Debug)]
648#[cfg_attr(feature = "capture", derive(Serialize))]
649pub struct CachedGradientSegment {
650 pub render_task: RenderTaskId,
651 pub local_rect: LayoutRect,
652}
653
654
655#[derive(Copy, Clone, Debug, Hash, MallocSizeOf, PartialEq, Eq)]
656#[cfg_attr(feature = "capture", derive(Serialize))]
657#[cfg_attr(feature = "replay", derive(Deserialize))]
658pub struct FastLinearGradientTask {
659 pub color0: ColorU,
660 pub color1: ColorU,
661 pub orientation: LineOrientation,
662}
663
664impl FastLinearGradientTask {
665 pub fn to_instance(&self, target_rect: &DeviceIntRect) -> FastLinearGradientInstance {
666 FastLinearGradientInstance {
667 task_rect: target_rect.to_f32(),
668 color0: ColorF::from(self.color0).premultiplied(),
669 color1: ColorF::from(self.color1).premultiplied(),
670 axis_select: match self.orientation {
671 LineOrientation::Horizontal => 0.0,
672 LineOrientation::Vertical => 1.0,
673 },
674 }
675 }
676}
677
678pub type FastLinearGradientCacheKey = FastLinearGradientTask;
679
680#[cfg_attr(feature = "capture", derive(Serialize))]
684#[cfg_attr(feature = "replay", derive(Deserialize))]
685#[repr(C)]
686#[derive(Clone, Debug)]
687pub struct FastLinearGradientInstance {
688 pub task_rect: DeviceRect,
689 pub color0: PremultipliedColorF,
690 pub color1: PremultipliedColorF,
691 pub axis_select: f32,
692}
693
694#[derive(Debug)]
695#[cfg_attr(feature = "capture", derive(Serialize))]
696#[cfg_attr(feature = "replay", derive(Deserialize))]
697pub struct LinearGradientTask {
698 pub start: DevicePoint,
699 pub end: DevicePoint,
700 pub scale: DeviceVector2D,
701 pub extend_mode: ExtendMode,
702 pub stops: GpuBufferAddress,
703}
704
705impl LinearGradientTask {
706 pub fn to_instance(&self, target_rect: &DeviceIntRect) -> LinearGradientInstance {
707 LinearGradientInstance {
708 task_rect: target_rect.to_f32(),
709 start: self.start,
710 end: self.end,
711 scale: self.scale,
712 extend_mode: self.extend_mode as i32,
713 gradient_stops_address: self.stops.as_int(),
714 }
715 }
716}
717
718#[cfg_attr(feature = "capture", derive(Serialize))]
722#[cfg_attr(feature = "replay", derive(Deserialize))]
723#[repr(C)]
724#[derive(Clone, Debug)]
725pub struct LinearGradientInstance {
726 pub task_rect: DeviceRect,
727 pub start: DevicePoint,
728 pub end: DevicePoint,
729 pub scale: DeviceVector2D,
730 pub extend_mode: i32,
731 pub gradient_stops_address: i32,
732}
733
734#[derive(Clone, Debug, Hash, PartialEq, Eq)]
735#[cfg_attr(feature = "capture", derive(Serialize))]
736#[cfg_attr(feature = "replay", derive(Deserialize))]
737pub struct LinearGradientCacheKey {
738 pub size: DeviceIntSize,
739 pub start: PointKey,
740 pub end: PointKey,
741 pub scale: PointKey,
742 pub extend_mode: ExtendMode,
743 pub stops: Vec<GradientStopKey>,
744 pub reversed_stops: bool,
745}