webrender 0.69.0

A GPU accelerated 2D renderer for web content
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

//! Linear gradients
//!
//! Specification: https://drafts.csswg.org/css-images-4/#linear-gradients
//!
//! Linear gradients are rendered via cached render tasks and composited with the image brush.

use euclid::approxeq::ApproxEq;
use euclid::point2;
use api::{ExtendMode, GradientStop};
use api::units::*;
use crate::pattern::gradient::linear_gradient_pattern;
use crate::pattern::{Pattern, PatternBuilder, PatternBuilderContext, PatternBuilderState};
use crate::scene_building::IsVisible;
use crate::intern::{Internable, InternDebug, Handle as InternHandle};
use crate::internal_types::LayoutPrimitiveInfo;
use crate::image_tiling::simplify_repeated_primitive;
use crate::prim_store::{PrimitiveKind, PrimitiveOpacity};
use crate::prim_store::{PrimKeyCommonData, PrimTemplateCommonData, PrimitiveStore};
use crate::prim_store::{NinePatchDescriptor, PointKey, SizeKey, InternablePrimitive};
use crate::segment::EdgeMask;
use super::{stops_and_min_alpha, GradientStopKey, apply_gradient_local_clip};
use std::ops::{Deref, DerefMut};
use std::mem::swap;

/// Identifying key for a linear gradient.
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
#[derive(Debug, Clone, Eq, PartialEq, Hash, MallocSizeOf)]
pub struct LinearGradientKey {
    pub common: PrimKeyCommonData,
    pub extend_mode: ExtendMode,
    pub start_point: PointKey,
    pub end_point: PointKey,
    /// Per-axis tile size encoded as a fraction of `common.prim_size`. The
    /// runtime `stretch_size` is `stretch_ratio * common.prim_size`.
    pub stretch_ratio: SizeKey,
    pub tile_spacing: SizeKey,
    pub stops: Vec<GradientStopKey>,
    pub reverse_stops: bool,
    pub nine_patch: Option<Box<NinePatchDescriptor>>,
    pub enable_dithering: bool,
}

impl LinearGradientKey {
    pub fn new(
        info: &LayoutPrimitiveInfo,
        linear_grad: LinearGradient,
    ) -> Self {
        LinearGradientKey {
            common: info.into(),
            extend_mode: linear_grad.extend_mode,
            start_point: linear_grad.start_point,
            end_point: linear_grad.end_point,
            stretch_ratio: linear_grad.stretch_ratio,
            tile_spacing: linear_grad.tile_spacing,
            stops: linear_grad.stops,
            reverse_stops: linear_grad.reverse_stops,
            nine_patch: linear_grad.nine_patch,
            enable_dithering: linear_grad.enable_dithering,
        }
    }
}

impl InternDebug for LinearGradientKey {}

#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
#[derive(Debug, MallocSizeOf)]
pub struct LinearGradientTemplate {
    pub common: PrimTemplateCommonData,
    pub extend_mode: ExtendMode,
    pub start_point: LayoutPoint,
    pub end_point: LayoutPoint,
    /// Per-axis fraction of `common.prim_size` covered by one tile of the
    /// gradient pattern. Multiply by `common.prim_size` at use to recover the
    /// absolute stretch_size.
    pub stretch_ratio: LayoutSize,
    pub tile_spacing: LayoutSize,
    pub stops_opacity: PrimitiveOpacity,
    pub stops: Vec<GradientStop>,
    pub border_nine_patch: Option<Box<NinePatchDescriptor>>,
    pub reverse_stops: bool,
}

impl PatternBuilder for LinearGradientTemplate {
    fn build(
        &self,
        _sub_rect: Option<DeviceRect>,
        offset: LayoutVector2D,
        ctx: &PatternBuilderContext,
        state: &mut PatternBuilderState,
    ) -> Pattern {
        let (start, end) = if self.reverse_stops {
            (self.end_point, self.start_point)
        } else {
            (self.start_point, self.end_point)
        };
        // LinearGradientTemplate stores the start and end points relative to the
        // primitive origin, but the shader works with start/end points in "proper"
        // layout coordinates (relative to the primitive's spatial node).
        let offset = offset + ctx.prim_origin.to_vector();
        linear_gradient_pattern(
            start + offset,
            end + offset,
            self.extend_mode,
            &self.stops,
            ctx.fb_config.is_software,
            state.frame_gpu_data,
        )
    }
}

impl Deref for LinearGradientTemplate {
    type Target = PrimTemplateCommonData;
    fn deref(&self) -> &Self::Target {
        &self.common
    }
}

impl DerefMut for LinearGradientTemplate {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.common
    }
}

/// Perform a few optimizations to the gradient that are relevant to scene building.
///
/// Mutates `prim_rect`, `tile_size`, `start`, `end` to bake in the simplifications
/// (repeated-tile collapse, equivalent-to-stretching on either axis, clip-induced
/// offsets). Decomposition into per-segment quads is no longer done here -- the
/// caller emits a single `LinearGradient` prim and prepare-time runs
/// [`decompose_axis_aligned_gradient`] against the snapped prim_rect when the
/// gradient is eligible. Doing the decomposition at frame-build keeps adjacent
/// segments phase-aligned with the snapped outer prim, even when the frame-time
/// snap pass nudges the outer rect.
pub fn optimize_linear_gradient(
    prim_rect: &mut LayoutRect,
    tile_size: &mut LayoutSize,
    mut tile_spacing: LayoutSize,
    clip_rect: &LayoutRect,
    start: &mut LayoutPoint,
    end: &mut LayoutPoint,
) {
    simplify_repeated_primitive(&tile_size, &mut tile_spacing, prim_rect);

    let vertical = start.x.approx_eq(&end.x);
    let horizontal = start.y.approx_eq(&end.y);

    let horizontally_tiled = prim_rect.width() > tile_size.width;
    let vertically_tiled = prim_rect.height() > tile_size.height;

    // Check whether the tiling is equivalent to stretching on either axis.
    // Stretching the gradient is more efficient than repeating it.
    if vertically_tiled && horizontal && tile_spacing.height == 0.0 {
        tile_size.height = prim_rect.height();
    }

    if horizontally_tiled && vertical && tile_spacing.width == 0.0 {
        tile_size.width = prim_rect.width();
    }

    let offset = apply_gradient_local_clip(
        prim_rect,
        &tile_size,
        &tile_spacing,
        &clip_rect
    );

    // The size of gradient render tasks depends on the tile_size. No need to generate
    // large stretch sizes that will be clipped to the bounds of the primitive.
    tile_size.width = tile_size.width.min(prim_rect.width());
    tile_size.height = tile_size.height.min(prim_rect.height());

    *start += offset;
    *end += offset;
}

/// Whether a linear gradient is eligible for the fast-path two-stop-per-segment
/// decomposition at prepare time. Inputs are the values produced by
/// `optimize_linear_gradient` (i.e. already simplified and clip-adjusted).
pub fn linear_gradient_decomposes(
    prim_rect: &LayoutRect,
    tile_size: LayoutSize,
    tile_spacing: LayoutSize,
    start: LayoutPoint,
    end: LayoutPoint,
    extend_mode: ExtendMode,
    stops: &[GradientStop],
    enable_dithering: bool,
) -> bool {
    if extend_mode != ExtendMode::Clamp || stops.is_empty() {
        return false;
    }

    let vertical = start.x.approx_eq(&end.x);
    let horizontal = start.y.approx_eq(&end.y);

    if !vertical && !horizontal {
        return false;
    }

    if vertical && horizontal {
        return false;
    }

    if !tile_spacing.is_empty() {
        return false;
    }

    let horizontally_tiled = prim_rect.width() > tile_size.width;
    let vertically_tiled = prim_rect.height() > tile_size.height;
    if vertically_tiled || horizontally_tiled {
        return false;
    }

    if !enable_dithering &&
        ((horizontal && tile_size.width < 256.0)
        || (vertical && tile_size.height < 256.0)) {
        return false;
    }

    true
}

/// Decompose an axis-aligned linear gradient into a sequence of two-stop
/// segments that tile end-to-end across `prim_rect`. Each callback invocation
/// is one segment, ready to be rendered as its own quad via the fast-path
/// gradient shader. Run at frame-build (against the snapped prim_rect) so
/// adjacent segments share a snapped boundary and tile without phase drift.
///
/// Caller must have verified eligibility via [`linear_gradient_decomposes`].
pub fn decompose_axis_aligned_gradient(
    prim_rect: &LayoutRect,
    tile_size: LayoutSize,
    start: LayoutPoint,
    end: LayoutPoint,
    stops: &[GradientStop],
    clip_rect: &LayoutRect,
    mut callback: impl FnMut(&LayoutRect, LayoutPoint, LayoutPoint, [GradientStop; 2], EdgeMask),
) {
    debug_assert!(!stops.is_empty());

    let vertical = start.x.approx_eq(&end.x);

    // Flip x/y when the gradient is vertical so the remaining math treats it
    // as horizontal; un-flip per-segment outputs at the end.
    let adjust_rect = &mut |rect: &mut LayoutRect| {
        if vertical {
            swap(&mut rect.min.x, &mut rect.min.y);
            swap(&mut rect.max.x, &mut rect.max.y);
        }
    };
    let adjust_size = &mut |size: &mut LayoutSize| {
        if vertical { swap(&mut size.width, &mut size.height); }
    };
    let adjust_point = &mut |p: &mut LayoutPoint| {
        if vertical { swap(&mut p.x, &mut p.y); }
    };

    let clip_rect = match clip_rect.intersection(prim_rect) {
        Some(clip) => clip,
        None => return,
    };

    let mut prim_rect = *prim_rect;
    let mut start = start;
    let mut end = end;
    let mut tile_size = tile_size;

    adjust_rect(&mut prim_rect);
    adjust_point(&mut start);
    adjust_point(&mut end);
    adjust_size(&mut tile_size);

    // `clip_rect` stays in the original (un-swapped) space — segment_rect
    // gets `adjust_rect` applied twice (once implicitly via the prim_rect
    // copy, once explicitly after computing per-segment extent) and lands
    // back in original space before this intersection.

    let length = (end.x - start.x).abs();

    // Match the pre-refactor optimiser: when the gradient line points in
    // decreasing-x (post-axis-swap), swap start/end and walk the stop list
    // in reverse, so the loop always processes stops in increasing-x
    // order. The pre-refactor code did this via `stops.reverse()` in
    // place; we can't mutate the template's stops here, so use a reversed
    // iterator and swap which end of the slice supplies the fake-stop
    // colour accordingly.
    let reverse_stops = start.x > end.x;
    if reverse_stops {
        swap(&mut start, &mut end);
    }

    let (first_stop, last_stop) = if reverse_stops {
        (*stops.last().unwrap(), *stops.first().unwrap())
    } else {
        (*stops.first().unwrap(), *stops.last().unwrap())
    };

    let mut prev = first_stop;
    let mut last = last_stop;
    prev.offset = -start.x / length;
    last.offset = (tile_size.width - start.x) / length;
    if reverse_stops {
        prev.offset = 1.0 - prev.offset;
        last.offset = 1.0 - last.offset;
    }

    let (side_edges, first_edge, last_edge) = if vertical {
        (
            EdgeMask::LEFT | EdgeMask::RIGHT,
            EdgeMask::TOP,
            EdgeMask::BOTTOM,
        )
    } else {
        (
            EdgeMask::TOP | EdgeMask::BOTTOM,
            EdgeMask::LEFT,
            EdgeMask::RIGHT,
        )
    };

    let mut is_first = true;
    let last_offset = last.offset;

    // Iterate stops in increasing-x order. When reverse_stops is set, walk the
    // backing slice in reverse instead of mutating it.
    let stops_iter: Box<dyn Iterator<Item = &GradientStop>> = if reverse_stops {
        Box::new(stops.iter().rev())
    } else {
        Box::new(stops.iter())
    };

    for stop in stops_iter.chain(std::iter::once(&last)) {
        let prev_stop = prev;
        prev = *stop;

        if prev_stop.color.a == 0.0 && stop.color.a == 0.0 {
            continue;
        }

        let prev_offset = if reverse_stops { 1.0 - prev_stop.offset } else { prev_stop.offset };
        let offset = if reverse_stops { 1.0 - stop.offset } else { stop.offset };

        // Segment_start and segment_end are in the gradient's pre-flip space
        // (relative to the prim's origin); the adjust_* helpers below restore
        // axis orientation when emitting.
        let segment_start = start.x + prev_offset * length;
        let segment_end = start.x + offset * length;
        let segment_length = segment_end - segment_start;

        if segment_length <= 0.0 {
            continue;
        }

        let mut segment_rect = prim_rect;
        segment_rect.min.x += segment_start;
        segment_rect.max.x = segment_rect.min.x + segment_length;

        let mut seg_start = point2(0.0, 0.0);
        let mut seg_end = point2(segment_length, 0.0);

        adjust_point(&mut seg_start);
        adjust_point(&mut seg_end);
        adjust_rect(&mut segment_rect);

        let origin_before_clip = segment_rect.min;
        segment_rect = match segment_rect.intersection(&clip_rect) {
            Some(rect) => rect,
            None => continue,
        };
        let clip_offset = segment_rect.min - origin_before_clip;
        seg_start -= clip_offset;
        seg_end -= clip_offset;

        let mut edge_flags = side_edges;
        if is_first {
            edge_flags |= first_edge;
            is_first = false;
        }
        if stop.offset == last_offset {
            edge_flags |= last_edge;
        }

        callback(
            &segment_rect,
            seg_start,
            seg_end,
            [
                GradientStop { offset: 0.0, color: prev_stop.color },
                GradientStop { offset: 1.0, color: stop.color },
            ],
            edge_flags,
        );
    }
}

impl From<LinearGradientKey> for LinearGradientTemplate {
    fn from(item: LinearGradientKey) -> Self {

        let common = PrimTemplateCommonData::with_key_common(item.common);

        let (stops, min_alpha) = stops_and_min_alpha(&item.stops);

        // Save opacity of the stops for use in
        // selecting which pass this gradient
        // should be drawn in.
        let stops_opacity = PrimitiveOpacity::from_alpha(min_alpha);

        let start_point = LayoutPoint::new(item.start_point.x, item.start_point.y);
        let end_point = LayoutPoint::new(item.end_point.x, item.end_point.y);
        let tile_spacing: LayoutSize = item.tile_spacing.into();
        let stretch_ratio: LayoutSize = item.stretch_ratio.into();

        LinearGradientTemplate {
            common,
            extend_mode: item.extend_mode,
            start_point,
            end_point,
            stretch_ratio,
            tile_spacing,
            stops_opacity,
            stops,
            border_nine_patch: item.nine_patch,
            reverse_stops: item.reverse_stops,
        }
    }
}

pub type LinearGradientDataHandle = InternHandle<LinearGradient>;

#[derive(Debug, MallocSizeOf)]
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct LinearGradient {
    pub extend_mode: ExtendMode,
    pub start_point: PointKey,
    pub end_point: PointKey,
    /// Per-axis tile size encoded as a fraction of the prim's size. See
    /// [`LinearGradientKey::stretch_ratio`].
    pub stretch_ratio: SizeKey,
    pub tile_spacing: SizeKey,
    pub stops: Vec<GradientStopKey>,
    pub reverse_stops: bool,
    pub nine_patch: Option<Box<NinePatchDescriptor>>,
    pub edge_aa_mask: EdgeMask,
    pub enable_dithering: bool,
}

impl Internable for LinearGradient {
    type Key = LinearGradientKey;
    type StoreData = LinearGradientTemplate;
    type InternData = ();
    const PROFILE_COUNTER: usize = crate::profiler::INTERNED_LINEAR_GRADIENTS;
}

impl InternablePrimitive for LinearGradient {
    fn into_key(
        self,
        info: &LayoutPrimitiveInfo,
    ) -> LinearGradientKey {
        LinearGradientKey::new(info, self)
    }

    fn make_instance_kind(
        _key: LinearGradientKey,
        data_handle: LinearGradientDataHandle,
        _prim_store: &mut PrimitiveStore,
    ) -> PrimitiveKind {
        PrimitiveKind::LinearGradient {
            data_handle,
        }
    }
}

impl IsVisible for LinearGradient {
    fn is_visible(&self) -> bool {
        true
    }
}