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