Skip to main content

webrender/pattern/
gradient.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
5use api::units::*;
6use api::{ColorF, ExtendMode, GradientStop};
7use crate::pattern::{Pattern, PatternKind, PatternShaderInput, PatternTextureInput};
8use crate::renderer::{BlendMode, GpuBufferBuilder, GpuBufferWriterF};
9
10#[repr(u8)]
11#[derive(Copy, Clone, Debug)]
12pub enum GradientKind {
13    Linear = 0,
14    Radial = 1,
15    Conic = 2,
16}
17
18pub fn linear_gradient_pattern(
19    start: LayoutPoint,
20    end: LayoutPoint,
21    extend_mode: ExtendMode,
22    stops: &[GradientStop],
23    _is_software: bool,
24    gpu_buffer_builder: &mut GpuBufferBuilder
25) -> Pattern {
26    let num_blocks = 2 + gpu_gradient_stops_blocks(stops.len());
27    let mut writer = gpu_buffer_builder.f32.write_blocks(num_blocks);
28    writer.push_one([
29        start.x,
30        start.y,
31        end.x,
32        end.y,
33    ]);
34    writer.push_one([
35        0.0,
36        0.0,
37        0.0,
38        0.0,
39    ]);
40
41    let is_opaque = write_gpu_gradient_stops_tree(stops, GradientKind::Linear, extend_mode, &mut writer);
42
43    let gradient_address = writer.finish();
44
45    Pattern {
46        kind: PatternKind::Gradient,
47        shader_input: PatternShaderInput(
48            gradient_address.as_int(),
49            0,
50        ),
51        texture_input: PatternTextureInput::default(),
52        base_color: ColorF::WHITE,
53        is_opaque,
54        blend_mode: BlendMode::PremultipliedAlpha,
55    }
56}
57
58pub fn radial_gradient_pattern(
59    center: LayoutPoint,
60    scale: DeviceVector2D,
61    start_radius: f32,
62    end_radius: f32,
63    ratio_xy: f32,
64    extend_mode: ExtendMode,
65    stops: &[GradientStop],
66    _is_software: bool,
67    gpu_buffer_builder: &mut GpuBufferBuilder
68) -> Pattern {
69    let num_blocks = 2 + gpu_gradient_stops_blocks(stops.len());
70    let mut writer = gpu_buffer_builder.f32.write_blocks(num_blocks);
71    writer.push_one([
72        center.x,
73        center.y,
74        scale.x,
75        scale.y,
76    ]);
77    writer.push_one([
78        start_radius,
79        end_radius,
80        ratio_xy,
81        0.0,
82    ]);
83
84    let is_opaque = write_gpu_gradient_stops_tree(stops, GradientKind::Radial, extend_mode, &mut writer);
85
86    let gradient_address = writer.finish();
87
88    Pattern {
89        kind: PatternKind::Gradient,
90        shader_input: PatternShaderInput(
91            gradient_address.as_int(),
92            0,
93        ),
94        texture_input: PatternTextureInput::default(),
95        base_color: ColorF::WHITE,
96        is_opaque,
97        blend_mode: BlendMode::PremultipliedAlpha,
98    }
99}
100
101pub fn conic_gradient_pattern(
102    center: LayoutPoint,
103    scale: DeviceVector2D,
104    angle: f32, // in radians
105    start_offset: f32,
106    end_offset: f32,
107    extend_mode: ExtendMode,
108    stops: &[GradientStop],
109    gpu_buffer_builder: &mut GpuBufferBuilder
110) -> Pattern {
111    let num_blocks = 2 + gpu_gradient_stops_blocks(stops.len());
112    let mut writer = gpu_buffer_builder.f32.write_blocks(num_blocks);
113    writer.push_one([
114        center.x,
115        center.y,
116        scale.x,
117        scale.y,
118    ]);
119    writer.push_one([
120        start_offset,
121        end_offset,
122        angle,
123        0.0,
124    ]);
125    let is_opaque = write_gpu_gradient_stops_tree(stops, GradientKind::Conic, extend_mode, &mut writer);
126    let gradient_address = writer.finish();
127
128    Pattern {
129        kind: PatternKind::Gradient,
130        shader_input: PatternShaderInput(
131            gradient_address.as_int(),
132            0,
133        ),
134        texture_input: PatternTextureInput::default(),
135        base_color: ColorF::WHITE,
136        is_opaque,
137        blend_mode: BlendMode::PremultipliedAlpha,
138    }
139}
140
141
142fn write_gpu_gradient_stops_header_and_colors(
143    stops: &[GradientStop],
144    kind: GradientKind,
145    extend_mode: ExtendMode,
146    writer: &mut GpuBufferWriterF,
147) -> bool {
148    // Write the header.
149    writer.push_one([
150        (kind as u8) as f32,
151        stops.len() as f32,
152        if extend_mode == ExtendMode::Repeat { 1.0 } else { 0.0 },
153        0.0
154    ]);
155
156    // Write the stop colors.
157    let mut is_opaque = true;
158    for stop in stops {
159        writer.push_one(stop.color.premultiplied());
160        is_opaque &= stop.color.a == 1.0;
161    }
162
163    is_opaque
164}
165
166// Push stop offsets in rearranged order so that the search can be carried
167// out as an implicit tree traversal.
168//
169// The structure of the tree is:
170//  - Each level is plit into 5 partitions.
171//  - The root level has one node (4 offsets -> 5 partitions).
172//  - Each level has 5 more nodes than the previous one.
173//  - Levels are pushed one by one starting from the root
174//
175// ```ascii
176// level : indices
177// ------:---------
178//   0   :                                                               24     ...
179//   1   :          4         9            14             19             |      ...
180//   2   :  0,1,2,3,|,5,6,7,8,|10,11,12,13,| ,15,16,17,18,| ,20,21,22,23,| ,25, ...
181// ```
182//
183// In the example above:
184// - The first (root) contains a single block containing the stop offsets from
185//   indices [24, 49, 74, 99].
186// - The second level contains blocks of offsets from indices [4, 9, 14, 19],
187//   [29, 34, 39, 44], etc.
188// - The third (leaf) level contains blocks from indices [0,1,2,3], [5,6,7,8],
189//   [15, 16, 17, 18], etc.
190//
191// Placeholder offsets (1.0) are used when a level has more capacity than the
192// input number of stops.
193//
194// Conceptually, blocks [0,1,2,3] and [5,6,7,8] are the first two children of
195// the node [4,9,14,19], separated by the offset from index 4.
196// Links are not explicitly represented via pointers or indices. Instead the
197// position in the buffer is sufficient to represent the level and index of the
198// stop (at the expense of having to store extra padding to round up each tree
199// level to its power-of-5-aligned size).
200//
201// This scheme is meant to make the traversal efficient loading offsets in
202// blocks of 4. The shader can converge to the leaf in very few loads.
203pub fn write_gpu_gradient_stops_tree(
204    stops: &[GradientStop],
205    kind: GradientKind,
206    extend_mode: ExtendMode,
207    writer: &mut GpuBufferWriterF,
208) -> bool {
209    let is_opaque = write_gpu_gradient_stops_header_and_colors(
210        stops,
211        kind,
212        extend_mode,
213        writer
214    );
215
216    let num_stops = stops.len();
217    let mut num_levels = 1;
218    let mut index_stride = 5;
219    let mut next_index_stride = 1;
220    // Number of 4-offsets blocks for the current level.
221    // The root has 1, then each level has 5 more than the previous one.
222    let mut num_blocks_for_level = 1;
223    let mut offset_blocks = 1;
224    while offset_blocks * 4 < num_stops {
225        num_blocks_for_level *= 5;
226        offset_blocks += num_blocks_for_level;
227
228        num_levels += 1;
229        index_stride *= 5;
230        next_index_stride *= 5;
231    }
232
233    // Fix offset_blocks up to account for the fact that we don't
234    // store the entirety of the last level;
235    let num_blocks_for_last_level = num_blocks_for_level.min(num_stops / 5 + 1);
236
237    // Reset num_blocks_for_level for the traversal.
238    num_blocks_for_level = 1;
239
240    // Go over each level, starting from the root.
241    for level in 0..num_levels {
242        // This scheme rounds up the number of offsets to store for each
243        // level to the next power of 5, which can represent a lot of wasted
244        // space, especially for the last levels. We need each level to start
245        // at a specific power-of-5-aligned offset so we can't get around the
246        // wasted space for all levels except the last one (which has the most
247        // waste).
248        let is_last_level = level == num_levels - 1;
249        let num_blocks = if is_last_level {
250            num_blocks_for_last_level
251        } else {
252            num_blocks_for_level
253        };
254
255        for block_idx in 0..num_blocks {
256            let mut block = [1.0; 4];
257            for i in 0..4 {
258                let linear_idx = block_idx * index_stride
259                    + i * next_index_stride
260                    + next_index_stride - 1;
261
262                if linear_idx < num_stops {
263                    block[i] = stops[linear_idx].offset;
264                }
265            }
266            writer.push_one(block);
267        }
268
269        index_stride = next_index_stride;
270        next_index_stride /= 5;
271        num_blocks_for_level *= 5;
272    }
273
274    return is_opaque;
275}
276
277fn gpu_gradient_stops_blocks(num_stops: usize) -> usize {
278    let header_blocks = 1;
279    let color_blocks = num_stops;
280
281    // If this is changed, matching changes should be made to the
282    // equivalent code in write_gpu_gradient_stops_tree.
283    let mut num_blocks_for_level = 1;
284    let mut offset_blocks = 1;
285    while offset_blocks * 4 < num_stops {
286        num_blocks_for_level *= 5;
287        offset_blocks += num_blocks_for_level;
288    }
289
290    // Fix the capacity up to account for the fact that we don't
291    // store the entirety of the last level;
292    let num_blocks_for_last_level = num_blocks_for_level.min(num_stops / 5 + 1);
293    offset_blocks -= num_blocks_for_level;
294    offset_blocks += num_blocks_for_last_level;
295
296    header_blocks + color_blocks + offset_blocks
297}