1use 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, 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 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 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
166pub 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 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 let num_blocks_for_last_level = num_blocks_for_level.min(num_stops / 5 + 1);
236
237 num_blocks_for_level = 1;
239
240 for level in 0..num_levels {
242 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 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 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}