webrender/prim_store/gradient/
conic.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
5//! Conic gradients
6//!
7//! Specification: https://drafts.csswg.org/css-images-4/#conic-gradients
8//!
9//! Conic gradients are rendered via cached render tasks and composited with the image brush.
10
11use euclid::vec2;
12use api::{ColorF, ExtendMode, GradientStop, PremultipliedColorF};
13use api::units::*;
14use crate::pattern::{Pattern, PatternBuilder, PatternBuilderContext, PatternBuilderState, PatternKind, PatternShaderInput, PatternTextureInput};
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::prim_store::{BrushSegment, GradientTileRange};
20use crate::prim_store::{PrimitiveInstanceKind, PrimitiveOpacity, FloatKey};
21use crate::prim_store::{PrimKeyCommonData, PrimTemplateCommonData, PrimitiveStore};
22use crate::prim_store::{NinePatchDescriptor, PointKey, SizeKey, InternablePrimitive};
23use crate::render_task::{RenderTask, RenderTaskKind};
24use crate::render_task_graph::RenderTaskId;
25use crate::render_task_cache::{RenderTaskCacheKeyKind, RenderTaskCacheKey, RenderTaskParent};
26use crate::renderer::{GpuBufferAddress, GpuBufferBuilder};
27
28use std::{hash, ops::{Deref, DerefMut}};
29use super::{stops_and_min_alpha, GradientStopKey, GradientGpuBlockBuilder};
30
31/// Hashable conic gradient parameters, for use during prim interning.
32#[cfg_attr(feature = "capture", derive(Serialize))]
33#[cfg_attr(feature = "replay", derive(Deserialize))]
34#[derive(Debug, Clone, MallocSizeOf, PartialEq)]
35pub struct ConicGradientParams {
36    pub angle: f32, // in radians
37    pub start_offset: f32,
38    pub end_offset: f32,
39}
40
41impl Eq for ConicGradientParams {}
42
43impl hash::Hash for ConicGradientParams {
44    fn hash<H: hash::Hasher>(&self, state: &mut H) {
45        self.angle.to_bits().hash(state);
46        self.start_offset.to_bits().hash(state);
47        self.end_offset.to_bits().hash(state);
48    }
49}
50
51/// Identifying key for a line decoration.
52#[cfg_attr(feature = "capture", derive(Serialize))]
53#[cfg_attr(feature = "replay", derive(Deserialize))]
54#[derive(Debug, Clone, Eq, PartialEq, Hash, MallocSizeOf)]
55pub struct ConicGradientKey {
56    pub common: PrimKeyCommonData,
57    pub extend_mode: ExtendMode,
58    pub center: PointKey,
59    pub params: ConicGradientParams,
60    pub stretch_size: SizeKey,
61    pub stops: Vec<GradientStopKey>,
62    pub tile_spacing: SizeKey,
63    pub nine_patch: Option<Box<NinePatchDescriptor>>,
64}
65
66impl ConicGradientKey {
67    pub fn new(
68        info: &LayoutPrimitiveInfo,
69        conic_grad: ConicGradient,
70    ) -> Self {
71        ConicGradientKey {
72            common: info.into(),
73            extend_mode: conic_grad.extend_mode,
74            center: conic_grad.center,
75            params: conic_grad.params,
76            stretch_size: conic_grad.stretch_size,
77            stops: conic_grad.stops,
78            tile_spacing: conic_grad.tile_spacing,
79            nine_patch: conic_grad.nine_patch,
80        }
81    }
82}
83
84impl InternDebug for ConicGradientKey {}
85
86#[cfg_attr(feature = "capture", derive(Serialize))]
87#[cfg_attr(feature = "replay", derive(Deserialize))]
88#[derive(MallocSizeOf)]
89pub struct ConicGradientTemplate {
90    pub common: PrimTemplateCommonData,
91    pub extend_mode: ExtendMode,
92    pub center: DevicePoint,
93    pub params: ConicGradientParams,
94    pub task_size: DeviceIntSize,
95    pub scale: DeviceVector2D,
96    pub stretch_size: LayoutSize,
97    pub tile_spacing: LayoutSize,
98    pub brush_segments: Vec<BrushSegment>,
99    pub stops_opacity: PrimitiveOpacity,
100    pub stops: Vec<GradientStop>,
101    pub src_color: Option<RenderTaskId>,
102}
103
104impl PatternBuilder for ConicGradientTemplate {
105    fn build(
106        &self,
107        _sub_rect: Option<DeviceRect>,
108        _ctx: &PatternBuilderContext,
109        state: &mut PatternBuilderState,
110    ) -> Pattern {
111        // The scaling parameter is used to compensate for when we reduce the size
112        // of the render task for cached gradients. Here we aren't applying any.
113        let no_scale = DeviceVector2D::one();
114
115        conic_gradient_pattern(
116            self.center,
117            no_scale,
118            &self.params,
119            self.extend_mode,
120            &self.stops,
121            state.frame_gpu_data,
122        )
123    }
124
125    fn get_base_color(
126        &self,
127        _ctx: &PatternBuilderContext,
128    ) -> ColorF {
129        ColorF::WHITE
130    }
131
132    fn use_shared_pattern(
133        &self,
134    ) -> bool {
135        true
136    }
137}
138
139impl Deref for ConicGradientTemplate {
140    type Target = PrimTemplateCommonData;
141    fn deref(&self) -> &Self::Target {
142        &self.common
143    }
144}
145
146impl DerefMut for ConicGradientTemplate {
147    fn deref_mut(&mut self) -> &mut Self::Target {
148        &mut self.common
149    }
150}
151
152impl From<ConicGradientKey> for ConicGradientTemplate {
153    fn from(item: ConicGradientKey) -> Self {
154        let common = PrimTemplateCommonData::with_key_common(item.common);
155        let mut brush_segments = Vec::new();
156
157        if let Some(ref nine_patch) = item.nine_patch {
158            brush_segments = nine_patch.create_segments(common.prim_rect.size());
159        }
160
161        let (stops, min_alpha) = stops_and_min_alpha(&item.stops);
162
163        // Save opacity of the stops for use in
164        // selecting which pass this gradient
165        // should be drawn in.
166        let stops_opacity = PrimitiveOpacity::from_alpha(min_alpha);
167
168        let mut stretch_size: LayoutSize = item.stretch_size.into();
169        stretch_size.width = stretch_size.width.min(common.prim_rect.width());
170        stretch_size.height = stretch_size.height.min(common.prim_rect.height());
171
172        fn approx_eq(a: f32, b: f32) -> bool { (a - b).abs() < 0.01 }
173
174        // Attempt to detect some of the common configurations with hard gradient stops. Allow
175        // those a higher maximum resolution to avoid the worst cases of aliasing artifacts with
176        // large conic gradients. A better solution would be to go back to rendering very large
177        // conic gradients via a brush shader instead of caching all of them (unclear whether
178        // it is important enough to warrant the better solution).
179        let mut has_hard_stops = false;
180        let mut prev_stop = None;
181        let offset_range = item.params.end_offset - item.params.start_offset;
182        for stop in &stops {
183            if offset_range <= 0.0 {
184                break;
185            }
186            if let Some(prev_offset) = prev_stop {
187                // Check whether two consecutive stops are very close (hard stops).
188                if stop.offset < prev_offset + 0.005 / offset_range {
189                    // a is the angle of the stop normalized into 0-1 space and repeating in the 0-0.25 range.
190                    // If close to 0.0 or 0.25 it means the stop is vertical or horizontal. For those, the lower
191                    // resolution isn't a big issue.
192                    let a = item.params.angle / (2.0 * std::f32::consts::PI)
193                        + item.params.start_offset
194                        + stop.offset / offset_range;
195                    let a = a.rem_euclid(0.25);
196
197                    if !approx_eq(a, 0.0) && !approx_eq(a, 0.25) {
198                        has_hard_stops = true;
199                        break;
200                    }
201                }
202            }
203            prev_stop = Some(stop.offset);
204        }
205
206        let max_size = if has_hard_stops {
207            2048.0
208        } else {
209            1024.0
210        };
211
212        // Avoid rendering enormous gradients. Radial gradients are mostly made of soft transitions,
213        // so it is unlikely that rendering at a higher resolution that 1024 would produce noticeable
214        // differences, especially with 8 bits per channel.
215        let mut task_size: DeviceSize = stretch_size.cast_unit();
216        let mut scale = vec2(1.0, 1.0);
217        if task_size.width > max_size {
218            scale.x = task_size.width / max_size;
219            task_size.width = max_size;
220        }
221        if task_size.height > max_size {
222            scale.y = task_size.height / max_size;
223            task_size.height = max_size;
224        }
225
226        ConicGradientTemplate {
227            common,
228            center: DevicePoint::new(item.center.x, item.center.y),
229            extend_mode: item.extend_mode,
230            params: item.params,
231            stretch_size,
232            task_size: task_size.ceil().to_i32(),
233            scale,
234            tile_spacing: item.tile_spacing.into(),
235            brush_segments,
236            stops_opacity,
237            stops,
238            src_color: None,
239        }
240    }
241}
242
243impl ConicGradientTemplate {
244    /// Update the GPU cache for a given primitive template. This may be called multiple
245    /// times per frame, by each primitive reference that refers to this interned
246    /// template. The initial request call to the GPU cache ensures that work is only
247    /// done if the cache entry is invalid (due to first use or eviction).
248    pub fn update(
249        &mut self,
250        frame_state: &mut FrameBuildingState,
251    ) {
252        if let Some(mut request) =
253            frame_state.gpu_cache.request(&mut self.common.gpu_cache_handle) {
254            // write_prim_gpu_blocks
255            request.push(PremultipliedColorF::WHITE);
256            request.push(PremultipliedColorF::WHITE);
257            request.push([
258                self.stretch_size.width,
259                self.stretch_size.height,
260                0.0,
261                0.0,
262            ]);
263
264            // write_segment_gpu_blocks
265            for segment in &self.brush_segments {
266                // has to match VECS_PER_SEGMENT
267                request.write_segment(
268                    segment.local_rect,
269                    segment.extra_data,
270                );
271            }
272        }
273
274        let cache_key = ConicGradientCacheKey {
275            size: self.task_size,
276            center: PointKey { x: self.center.x, y: self.center.y },
277            scale: PointKey { x: self.scale.x, y: self.scale.y },
278            start_offset: FloatKey(self.params.start_offset),
279            end_offset: FloatKey(self.params.end_offset),
280            angle: FloatKey(self.params.angle),
281            extend_mode: self.extend_mode,
282            stops: self.stops.iter().map(|stop| (*stop).into()).collect(),
283        };
284
285        let task_id = frame_state.resource_cache.request_render_task(
286            Some(RenderTaskCacheKey {
287                size: self.task_size,
288                kind: RenderTaskCacheKeyKind::ConicGradient(cache_key),
289            }),
290            false,
291            RenderTaskParent::Surface,
292            frame_state.gpu_cache,
293            &mut frame_state.frame_gpu_data.f32,
294            frame_state.rg_builder,
295            &mut frame_state.surface_builder,
296            &mut |rg_builder, gpu_buffer_builder, _| {
297                let stops = GradientGpuBlockBuilder::build(
298                    false,
299                    gpu_buffer_builder,
300                    &self.stops,
301                );
302
303                rg_builder.add().init(RenderTask::new_dynamic(
304                    self.task_size,
305                    RenderTaskKind::ConicGradient(ConicGradientTask {
306                        extend_mode: self.extend_mode,
307                        scale: self.scale,
308                        center: self.center,
309                        params: self.params.clone(),
310                        stops,
311                    }),
312                ))
313            }
314        );
315
316        self.src_color = Some(task_id);
317
318        // Tile spacing is always handled by decomposing into separate draw calls so the
319        // primitive opacity is equivalent to stops opacity. This might change to being
320        // set to non-opaque in the presence of tile spacing if/when tile spacing is handled
321        // in the same way as with the image primitive.
322        self.opacity = self.stops_opacity;
323    }
324}
325
326pub type ConicGradientDataHandle = InternHandle<ConicGradient>;
327
328#[derive(Debug, MallocSizeOf)]
329#[cfg_attr(feature = "capture", derive(Serialize))]
330#[cfg_attr(feature = "replay", derive(Deserialize))]
331pub struct ConicGradient {
332    pub extend_mode: ExtendMode,
333    pub center: PointKey,
334    pub params: ConicGradientParams,
335    pub stretch_size: SizeKey,
336    pub stops: Vec<GradientStopKey>,
337    pub tile_spacing: SizeKey,
338    pub nine_patch: Option<Box<NinePatchDescriptor>>,
339}
340
341impl Internable for ConicGradient {
342    type Key = ConicGradientKey;
343    type StoreData = ConicGradientTemplate;
344    type InternData = ();
345    const PROFILE_COUNTER: usize = crate::profiler::INTERNED_CONIC_GRADIENTS;
346}
347
348impl InternablePrimitive for ConicGradient {
349    fn into_key(
350        self,
351        info: &LayoutPrimitiveInfo,
352    ) -> ConicGradientKey {
353        ConicGradientKey::new(info, self)
354    }
355
356    fn make_instance_kind(
357        _key: ConicGradientKey,
358        data_handle: ConicGradientDataHandle,
359        _prim_store: &mut PrimitiveStore,
360    ) -> PrimitiveInstanceKind {
361        PrimitiveInstanceKind::ConicGradient {
362            data_handle,
363            visible_tiles_range: GradientTileRange::empty(),
364            cached: true,
365        }
366    }
367}
368
369impl IsVisible for ConicGradient {
370    fn is_visible(&self) -> bool {
371        true
372    }
373}
374
375#[derive(Debug)]
376#[cfg_attr(feature = "capture", derive(Serialize))]
377#[cfg_attr(feature = "replay", derive(Deserialize))]
378pub struct ConicGradientTask {
379    pub extend_mode: ExtendMode,
380    pub center: DevicePoint,
381    pub scale: DeviceVector2D,
382    pub params: ConicGradientParams,
383    pub stops: GpuBufferAddress,
384}
385
386impl ConicGradientTask {
387    pub fn to_instance(&self, target_rect: &DeviceIntRect) -> ConicGradientInstance {
388        ConicGradientInstance {
389            task_rect: target_rect.to_f32(),
390            center: self.center,
391            scale: self.scale,
392            start_offset: self.params.start_offset,
393            end_offset: self.params.end_offset,
394            angle: self.params.angle,
395            extend_mode: self.extend_mode as i32,
396            gradient_stops_address: self.stops.as_int(),
397        }
398    }
399}
400
401/// The per-instance shader input of a radial gradient render task.
402///
403/// Must match the RADIAL_GRADIENT instance description in renderer/vertex.rs.
404#[cfg_attr(feature = "capture", derive(Serialize))]
405#[cfg_attr(feature = "replay", derive(Deserialize))]
406#[repr(C)]
407#[derive(Clone, Debug)]
408pub struct ConicGradientInstance {
409    pub task_rect: DeviceRect,
410    pub center: DevicePoint,
411    pub scale: DeviceVector2D,
412    pub start_offset: f32,
413    pub end_offset: f32,
414    pub angle: f32,
415    pub extend_mode: i32,
416    pub gradient_stops_address: i32,
417}
418
419#[derive(Clone, Debug, Hash, PartialEq, Eq)]
420#[cfg_attr(feature = "capture", derive(Serialize))]
421#[cfg_attr(feature = "replay", derive(Deserialize))]
422pub struct ConicGradientCacheKey {
423    pub size: DeviceIntSize,
424    pub center: PointKey,
425    pub scale: PointKey,
426    pub start_offset: FloatKey,
427    pub end_offset: FloatKey,
428    pub angle: FloatKey,
429    pub extend_mode: ExtendMode,
430    pub stops: Vec<GradientStopKey>,
431}
432
433pub fn conic_gradient_pattern(
434    center: DevicePoint,
435    scale: DeviceVector2D,
436    params: &ConicGradientParams,
437    extend_mode: ExtendMode,
438    stops: &[GradientStop],
439    gpu_buffer_builder: &mut GpuBufferBuilder
440) -> Pattern {
441    let mut writer = gpu_buffer_builder.f32.write_blocks(2);
442    writer.push_one([
443        center.x,
444        center.y,
445        scale.x,
446        scale.y,
447    ]);
448    writer.push_one([
449        params.start_offset,
450        params.end_offset,
451        params.angle,
452        if extend_mode == ExtendMode::Repeat { 1.0 } else { 0.0 }
453    ]);
454    let gradient_address = writer.finish();
455
456    let stops_address = GradientGpuBlockBuilder::build(
457        false,
458        &mut gpu_buffer_builder.f32,
459        &stops,
460    );
461
462    let is_opaque = stops.iter().all(|stop| stop.color.a >= 1.0);
463
464    Pattern {
465        kind: PatternKind::ConicGradient,
466        shader_input: PatternShaderInput(
467            gradient_address.as_int(),
468            stops_address.as_int(),
469        ),
470        texture_input: PatternTextureInput::default(),
471        base_color: ColorF::WHITE,
472        is_opaque,
473    }
474}