1use euclid::{vec2, size2};
12use api::{ColorF, ColorU, ExtendMode, GradientStop, PremultipliedColorF};
13use api::units::*;
14use crate::pattern::{Pattern, PatternBuilder, PatternBuilderContext, PatternBuilderState, PatternKind, PatternShaderInput, PatternTextureInput};
15use crate::prim_store::gradient::GradientKind;
16use crate::scene_building::IsVisible;
17use crate::frame_builder::FrameBuildingState;
18use crate::intern::{Internable, InternDebug, Handle as InternHandle};
19use crate::internal_types::LayoutPrimitiveInfo;
20use crate::prim_store::{BrushSegment, GradientTileRange, InternablePrimitive};
21use crate::prim_store::{PrimitiveInstanceKind, PrimitiveOpacity};
22use crate::prim_store::{PrimKeyCommonData, PrimTemplateCommonData, PrimitiveStore};
23use crate::prim_store::{NinePatchDescriptor, PointKey, SizeKey, FloatKey};
24use crate::render_task::{RenderTask, RenderTaskKind};
25use crate::render_task_graph::RenderTaskId;
26use crate::render_task_cache::{RenderTaskCacheKeyKind, RenderTaskCacheKey, RenderTaskParent};
27use crate::renderer::{GpuBufferAddress, GpuBufferBuilder};
28
29use std::{hash, ops::{Deref, DerefMut}};
30use super::{
31 stops_and_min_alpha, GradientStopKey, GradientGpuBlockBuilder,
32 apply_gradient_local_clip, gpu_gradient_stops_blocks,
33 write_gpu_gradient_stops_linear, write_gpu_gradient_stops_tree,
34};
35
36#[cfg_attr(feature = "capture", derive(Serialize))]
38#[cfg_attr(feature = "replay", derive(Deserialize))]
39#[derive(Debug, Clone, MallocSizeOf, PartialEq)]
40pub struct RadialGradientParams {
41 pub start_radius: f32,
42 pub end_radius: f32,
43 pub ratio_xy: f32,
44}
45
46impl Eq for RadialGradientParams {}
47
48impl hash::Hash for RadialGradientParams {
49 fn hash<H: hash::Hasher>(&self, state: &mut H) {
50 self.start_radius.to_bits().hash(state);
51 self.end_radius.to_bits().hash(state);
52 self.ratio_xy.to_bits().hash(state);
53 }
54}
55
56#[cfg_attr(feature = "capture", derive(Serialize))]
58#[cfg_attr(feature = "replay", derive(Deserialize))]
59#[derive(Debug, Clone, Eq, PartialEq, Hash, MallocSizeOf)]
60pub struct RadialGradientKey {
61 pub common: PrimKeyCommonData,
62 pub extend_mode: ExtendMode,
63 pub center: PointKey,
64 pub params: RadialGradientParams,
65 pub stretch_size: SizeKey,
66 pub stops: Vec<GradientStopKey>,
67 pub tile_spacing: SizeKey,
68 pub nine_patch: Option<Box<NinePatchDescriptor>>,
69}
70
71impl RadialGradientKey {
72 pub fn new(
73 info: &LayoutPrimitiveInfo,
74 radial_grad: RadialGradient,
75 ) -> Self {
76 RadialGradientKey {
77 common: info.into(),
78 extend_mode: radial_grad.extend_mode,
79 center: radial_grad.center,
80 params: radial_grad.params,
81 stretch_size: radial_grad.stretch_size,
82 stops: radial_grad.stops,
83 tile_spacing: radial_grad.tile_spacing,
84 nine_patch: radial_grad.nine_patch,
85 }
86 }
87}
88
89impl InternDebug for RadialGradientKey {}
90
91#[cfg_attr(feature = "capture", derive(Serialize))]
92#[cfg_attr(feature = "replay", derive(Deserialize))]
93#[derive(MallocSizeOf)]
94#[derive(Debug)]
95pub struct RadialGradientTemplate {
96 pub common: PrimTemplateCommonData,
97 pub extend_mode: ExtendMode,
98 pub params: RadialGradientParams,
99 pub center: DevicePoint,
100 pub task_size: DeviceIntSize,
101 pub scale: DeviceVector2D,
102 pub stretch_size: LayoutSize,
103 pub tile_spacing: LayoutSize,
104 pub brush_segments: Vec<BrushSegment>,
105 pub stops_opacity: PrimitiveOpacity,
106 pub stops: Vec<GradientStop>,
107 pub src_color: Option<RenderTaskId>,
108}
109
110impl PatternBuilder for RadialGradientTemplate {
111 fn build(
112 &self,
113 _sub_rect: Option<DeviceRect>,
114 ctx: &PatternBuilderContext,
115 state: &mut PatternBuilderState,
116 ) -> Pattern {
117 let no_scale = DeviceVector2D::one();
120
121 if ctx.fb_config.precise_radial_gradients {
122 radial_gradient_pattern(
123 self.center,
124 no_scale,
125 &self.params,
126 self.extend_mode,
127 &self.stops,
128 ctx.fb_config.is_software,
129 state.frame_gpu_data,
130 )
131 } else {
132 radial_gradient_pattern_with_table(
133 self.center,
134 no_scale,
135 &self.params,
136 self.extend_mode,
137 &self.stops,
138 state.frame_gpu_data,
139 )
140 }
141 }
142
143 fn get_base_color(
144 &self,
145 _ctx: &PatternBuilderContext,
146 ) -> ColorF {
147 ColorF::WHITE
148 }
149
150 fn use_shared_pattern(
151 &self,
152 ) -> bool {
153 true
154 }
155}
156
157impl Deref for RadialGradientTemplate {
158 type Target = PrimTemplateCommonData;
159 fn deref(&self) -> &Self::Target {
160 &self.common
161 }
162}
163
164impl DerefMut for RadialGradientTemplate {
165 fn deref_mut(&mut self) -> &mut Self::Target {
166 &mut self.common
167 }
168}
169
170impl From<RadialGradientKey> for RadialGradientTemplate {
171 fn from(item: RadialGradientKey) -> Self {
172 let common = PrimTemplateCommonData::with_key_common(item.common);
173 let mut brush_segments = Vec::new();
174
175 if let Some(ref nine_patch) = item.nine_patch {
176 brush_segments = nine_patch.create_segments(common.prim_rect.size());
177 }
178
179 let (stops, min_alpha) = stops_and_min_alpha(&item.stops);
180
181 let stops_opacity = PrimitiveOpacity::from_alpha(min_alpha);
185
186 let mut stretch_size: LayoutSize = item.stretch_size.into();
187 stretch_size.width = stretch_size.width.min(common.prim_rect.width());
188 stretch_size.height = stretch_size.height.min(common.prim_rect.height());
189
190 const MAX_SIZE: f32 = 1024.0;
194 let mut task_size: DeviceSize = stretch_size.cast_unit();
195 let mut scale = vec2(1.0, 1.0);
196 if task_size.width > MAX_SIZE {
197 scale.x = task_size.width/ MAX_SIZE;
198 task_size.width = MAX_SIZE;
199 }
200 if task_size.height > MAX_SIZE {
201 scale.y = task_size.height /MAX_SIZE;
202 task_size.height = MAX_SIZE;
203 }
204
205 RadialGradientTemplate {
206 common,
207 center: DevicePoint::new(item.center.x, item.center.y),
208 extend_mode: item.extend_mode,
209 params: item.params,
210 stretch_size,
211 task_size: task_size.ceil().to_i32(),
212 scale,
213 tile_spacing: item.tile_spacing.into(),
214 brush_segments,
215 stops_opacity,
216 stops,
217 src_color: None,
218 }
219 }
220}
221
222impl RadialGradientTemplate {
223 pub fn update(
228 &mut self,
229 frame_state: &mut FrameBuildingState,
230 ) {
231 if let Some(mut request) =
232 frame_state.gpu_cache.request(&mut self.common.gpu_cache_handle) {
233 request.push(PremultipliedColorF::WHITE);
235 request.push(PremultipliedColorF::WHITE);
236 request.push([
237 self.stretch_size.width,
238 self.stretch_size.height,
239 0.0,
240 0.0,
241 ]);
242
243 for segment in &self.brush_segments {
245 request.write_segment(
247 segment.local_rect,
248 segment.extra_data,
249 );
250 }
251 }
252
253 let task_size = self.task_size;
254 let cache_key = RadialGradientCacheKey {
255 size: task_size,
256 center: PointKey { x: self.center.x, y: self.center.y },
257 scale: PointKey { x: self.scale.x, y: self.scale.y },
258 start_radius: FloatKey(self.params.start_radius),
259 end_radius: FloatKey(self.params.end_radius),
260 ratio_xy: FloatKey(self.params.ratio_xy),
261 extend_mode: self.extend_mode,
262 stops: self.stops.iter().map(|stop| (*stop).into()).collect(),
263 };
264
265 let task_id = frame_state.resource_cache.request_render_task(
266 Some(RenderTaskCacheKey {
267 size: task_size,
268 kind: RenderTaskCacheKeyKind::RadialGradient(cache_key),
269 }),
270 false,
271 RenderTaskParent::Surface,
272 frame_state.gpu_cache,
273 &mut frame_state.frame_gpu_data.f32,
274 frame_state.rg_builder,
275 &mut frame_state.surface_builder,
276 &mut |rg_builder, gpu_buffer_builder, _| {
277 let stops = GradientGpuBlockBuilder::build(
278 false,
279 gpu_buffer_builder,
280 &self.stops,
281 );
282
283 rg_builder.add().init(RenderTask::new_dynamic(
284 task_size,
285 RenderTaskKind::RadialGradient(RadialGradientTask {
286 extend_mode: self.extend_mode,
287 center: self.center,
288 scale: self.scale,
289 params: self.params.clone(),
290 stops,
291 }),
292 ))
293 }
294 );
295
296 self.src_color = Some(task_id);
297
298 self.opacity = self.stops_opacity;
303 }
304}
305
306pub type RadialGradientDataHandle = InternHandle<RadialGradient>;
307
308#[derive(Debug, MallocSizeOf)]
309#[cfg_attr(feature = "capture", derive(Serialize))]
310#[cfg_attr(feature = "replay", derive(Deserialize))]
311pub struct RadialGradient {
312 pub extend_mode: ExtendMode,
313 pub center: PointKey,
314 pub params: RadialGradientParams,
315 pub stretch_size: SizeKey,
316 pub stops: Vec<GradientStopKey>,
317 pub tile_spacing: SizeKey,
318 pub nine_patch: Option<Box<NinePatchDescriptor>>,
319}
320
321impl Internable for RadialGradient {
322 type Key = RadialGradientKey;
323 type StoreData = RadialGradientTemplate;
324 type InternData = ();
325 const PROFILE_COUNTER: usize = crate::profiler::INTERNED_RADIAL_GRADIENTS;
326}
327
328impl InternablePrimitive for RadialGradient {
329 fn into_key(
330 self,
331 info: &LayoutPrimitiveInfo,
332 ) -> RadialGradientKey {
333 RadialGradientKey::new(info, self)
334 }
335
336 fn make_instance_kind(
337 _key: RadialGradientKey,
338 data_handle: RadialGradientDataHandle,
339 _prim_store: &mut PrimitiveStore,
340 ) -> PrimitiveInstanceKind {
341 PrimitiveInstanceKind::RadialGradient {
342 data_handle,
343 visible_tiles_range: GradientTileRange::empty(),
344 use_legacy_path: true,
345 }
346 }
347}
348
349impl IsVisible for RadialGradient {
350 fn is_visible(&self) -> bool {
351 true
352 }
353}
354
355#[derive(Debug)]
356#[cfg_attr(feature = "capture", derive(Serialize))]
357#[cfg_attr(feature = "replay", derive(Deserialize))]
358pub struct RadialGradientTask {
359 pub extend_mode: ExtendMode,
360 pub center: DevicePoint,
361 pub scale: DeviceVector2D,
362 pub params: RadialGradientParams,
363 pub stops: GpuBufferAddress,
364}
365
366impl RadialGradientTask {
367 pub fn to_instance(&self, target_rect: &DeviceIntRect) -> RadialGradientInstance {
368 RadialGradientInstance {
369 task_rect: target_rect.to_f32(),
370 center: self.center,
371 scale: self.scale,
372 start_radius: self.params.start_radius,
373 end_radius: self.params.end_radius,
374 ratio_xy: self.params.ratio_xy,
375 extend_mode: self.extend_mode as i32,
376 gradient_stops_address: self.stops.as_int(),
377 }
378 }
379}
380
381#[cfg_attr(feature = "capture", derive(Serialize))]
385#[cfg_attr(feature = "replay", derive(Deserialize))]
386#[repr(C)]
387#[derive(Clone, Debug)]
388pub struct RadialGradientInstance {
389 pub task_rect: DeviceRect,
390 pub center: DevicePoint,
391 pub scale: DeviceVector2D,
392 pub start_radius: f32,
393 pub end_radius: f32,
394 pub ratio_xy: f32,
395 pub extend_mode: i32,
396 pub gradient_stops_address: i32,
397}
398
399#[derive(Clone, Debug, Hash, PartialEq, Eq)]
400#[cfg_attr(feature = "capture", derive(Serialize))]
401#[cfg_attr(feature = "replay", derive(Deserialize))]
402pub struct RadialGradientCacheKey {
403 pub size: DeviceIntSize,
404 pub center: PointKey,
405 pub scale: PointKey,
406 pub start_radius: FloatKey,
407 pub end_radius: FloatKey,
408 pub ratio_xy: FloatKey,
409 pub extend_mode: ExtendMode,
410 pub stops: Vec<GradientStopKey>,
411}
412
413pub fn optimize_radial_gradient(
426 prim_rect: &mut LayoutRect,
427 stretch_size: &mut LayoutSize,
428 center: &mut LayoutPoint,
429 tile_spacing: &mut LayoutSize,
430 clip_rect: &LayoutRect,
431 radius: LayoutSize,
432 end_offset: f32,
433 extend_mode: ExtendMode,
434 stops: &[GradientStopKey],
435 solid_parts: &mut dyn FnMut(&LayoutRect, ColorU),
436) {
437 let offset = apply_gradient_local_clip(
438 prim_rect,
439 stretch_size,
440 tile_spacing,
441 clip_rect
442 );
443
444 *center += offset;
445
446 if extend_mode != ExtendMode::Clamp || stops.is_empty() {
447 return;
448 }
449
450 let min = prim_rect.min + center.to_vector() - radius.to_vector() * end_offset;
452 let max = prim_rect.min + center.to_vector() + radius.to_vector() * end_offset;
453
454 let gradient_rect = LayoutRect::from_origin_and_size(
456 prim_rect.min,
457 *stretch_size,
458 );
459
460 let mut l = (min.x - gradient_rect.min.x).max(0.0).floor();
463 let mut t = (min.y - gradient_rect.min.y).max(0.0).floor();
464 let mut r = (gradient_rect.max.x - max.x).max(0.0).floor();
465 let mut b = (gradient_rect.max.y - max.y).max(0.0).floor();
466
467 let is_tiled = prim_rect.width() > stretch_size.width + tile_spacing.width
468 || prim_rect.height() > stretch_size.height + tile_spacing.height;
469
470 let bg_color = stops.last().unwrap().color;
471
472 if bg_color.a != 0 && is_tiled {
473 return;
476 }
477
478 if bg_color.a != 0 || (is_tiled && tile_spacing.is_empty()) {
485 let threshold = 128.0;
486 if l < threshold { l = 0.0 }
487 if t < threshold { t = 0.0 }
488 if r < threshold { r = 0.0 }
489 if b < threshold { b = 0.0 }
490 }
491
492 if l + t + r + b == 0.0 {
493 return;
495 }
496
497 if bg_color.a != 0 {
500 if l != 0.0 && t != 0.0 {
501 let solid_rect = LayoutRect::from_origin_and_size(
502 gradient_rect.min,
503 size2(l, t),
504 );
505 solid_parts(&solid_rect, bg_color);
506 }
507
508 if l != 0.0 && b != 0.0 {
509 let solid_rect = LayoutRect::from_origin_and_size(
510 gradient_rect.bottom_left() - vec2(0.0, b),
511 size2(l, b),
512 );
513 solid_parts(&solid_rect, bg_color);
514 }
515
516 if t != 0.0 && r != 0.0 {
517 let solid_rect = LayoutRect::from_origin_and_size(
518 gradient_rect.top_right() - vec2(r, 0.0),
519 size2(r, t),
520 );
521 solid_parts(&solid_rect, bg_color);
522 }
523
524 if r != 0.0 && b != 0.0 {
525 let solid_rect = LayoutRect::from_origin_and_size(
526 gradient_rect.bottom_right() - vec2(r, b),
527 size2(r, b),
528 );
529 solid_parts(&solid_rect, bg_color);
530 }
531
532 if l != 0.0 {
533 let solid_rect = LayoutRect::from_origin_and_size(
534 gradient_rect.min + vec2(0.0, t),
535 size2(l, gradient_rect.height() - t - b),
536 );
537 solid_parts(&solid_rect, bg_color);
538 }
539
540 if r != 0.0 {
541 let solid_rect = LayoutRect::from_origin_and_size(
542 gradient_rect.top_right() + vec2(-r, t),
543 size2(r, gradient_rect.height() - t - b),
544 );
545 solid_parts(&solid_rect, bg_color);
546 }
547
548 if t != 0.0 {
549 let solid_rect = LayoutRect::from_origin_and_size(
550 gradient_rect.min + vec2(l, 0.0),
551 size2(gradient_rect.width() - l - r, t),
552 );
553 solid_parts(&solid_rect, bg_color);
554 }
555
556 if b != 0.0 {
557 let solid_rect = LayoutRect::from_origin_and_size(
558 gradient_rect.bottom_left() + vec2(l, -b),
559 size2(gradient_rect.width() - l - r, b),
560 );
561 solid_parts(&solid_rect, bg_color);
562 }
563 }
564
565 prim_rect.min.x += l;
568 prim_rect.min.y += t;
569
570 stretch_size.width -= l + r;
571 stretch_size.height -= b + t;
572
573 center.x -= l;
574 center.y -= t;
575
576 tile_spacing.width += l + r;
577 tile_spacing.height += t + b;
578}
579
580pub fn radial_gradient_pattern_with_table(
581 center: DevicePoint,
582 scale: DeviceVector2D,
583 params: &RadialGradientParams,
584 extend_mode: ExtendMode,
585 stops: &[GradientStop],
586 gpu_buffer_builder: &mut GpuBufferBuilder
587) -> Pattern {
588 let mut writer = gpu_buffer_builder.f32.write_blocks(2);
589 writer.push_one([
590 center.x,
591 center.y,
592 scale.x,
593 scale.y,
594 ]);
595 writer.push_one([
596 params.start_radius,
597 params.end_radius,
598 params.ratio_xy,
599 if extend_mode == ExtendMode::Repeat { 1.0 } else { 0.0 }
600 ]);
601 let gradient_address = writer.finish();
602
603 let stops_address = GradientGpuBlockBuilder::build(
604 false,
605 &mut gpu_buffer_builder.f32,
606 &stops,
607 );
608
609 let is_opaque = stops.iter().all(|stop| stop.color.a >= 1.0);
610
611 Pattern {
612 kind: PatternKind::RadialGradient,
613 shader_input: PatternShaderInput(
614 gradient_address.as_int(),
615 stops_address.as_int(),
616 ),
617 texture_input: PatternTextureInput::default(),
618 base_color: ColorF::WHITE,
619 is_opaque,
620 }
621}
622
623pub fn radial_gradient_pattern(
624 center: DevicePoint,
625 scale: DeviceVector2D,
626 params: &RadialGradientParams,
627 extend_mode: ExtendMode,
628 stops: &[GradientStop],
629 is_software: bool,
630 gpu_buffer_builder: &mut GpuBufferBuilder
631) -> Pattern {
632 let num_blocks = 2 + gpu_gradient_stops_blocks(stops.len(), !is_software);
633 let mut writer = gpu_buffer_builder.f32.write_blocks(num_blocks);
634 writer.push_one([
635 center.x,
636 center.y,
637 scale.x,
638 scale.y,
639 ]);
640 writer.push_one([
641 params.start_radius,
642 params.end_radius,
643 params.ratio_xy,
644 0.0,
645 ]);
646
647 let is_opaque = if is_software {
648 write_gpu_gradient_stops_linear(stops, GradientKind::Radial, extend_mode, &mut writer)
653 } else {
654 write_gpu_gradient_stops_tree(stops, GradientKind::Radial, extend_mode, &mut writer)
657 };
658 let gradient_address = writer.finish();
659
660 Pattern {
661 kind: PatternKind::Gradient,
662 shader_input: PatternShaderInput(
663 gradient_address.as_int(),
664 0,
665 ),
666 texture_input: PatternTextureInput::default(),
667 base_color: ColorF::WHITE,
668 is_opaque,
669 }
670}