1use std::hash::{Hash, Hasher};
10
11use kurbo::PathEl;
12use oxframe_core::BlendMode;
13
14use crate::draw::{DrawItem, Paint, Primitive, StrokeStyle};
15use crate::graph::NodeId;
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum PassKind {
20 Vector,
22 Composite,
24 Effect,
27}
28
29impl PassKind {
30 pub fn name(self) -> &'static str {
32 match self {
33 Self::Vector => "vector",
34 Self::Composite => "composite",
35 Self::Effect => "effect",
36 }
37 }
38}
39
40#[derive(Debug, Clone, PartialEq)]
42pub struct Pass {
43 pub kind: PassKind,
45 pub node: NodeId,
47 pub items: u32,
49 pub depth: u32,
51 pub opacity: f64,
53 pub blend: &'static str,
55 pub filtered: bool,
57 pub shader: Option<&'static str>,
59}
60
61pub fn pass_graph(items: &[DrawItem]) -> Vec<Pass> {
65 let mut stack = vec![Scope::frame()];
66 for item in items {
67 match &item.prim {
68 Primitive::PushLayer(layer) => {
69 flush(&mut stack);
70 stack.push(Scope {
71 node: item.node,
72 items: 0,
73 run: 0,
74 depth: stack.len() as u32,
75 layer: Some(LayerInfo {
76 opacity: layer.opacity,
77 blend: blend_name(layer.blend),
78 filtered: layer.has_filter() || layer.clip.is_some(),
79 }),
80 shader: None,
81 passes: Vec::new(),
82 });
83 }
84 Primitive::BeginEffect(shade) => {
85 flush(&mut stack);
86 stack.push(Scope {
87 node: item.node,
88 items: 0,
89 run: 0,
90 depth: stack.len() as u32,
91 layer: None,
92 shader: Some(shade.op.name()),
93 passes: Vec::new(),
94 });
95 }
96 Primitive::ReadFrame(shade) => {
97 flush(&mut stack);
98 let scope = stack.last_mut().expect("frame scope");
99 scope.passes.push(Pass {
100 kind: PassKind::Effect,
101 node: item.node,
102 items: 0,
103 depth: scope.depth,
104 opacity: shade.opacity,
105 blend: "normal",
106 filtered: false,
107 shader: Some(shade.op.name()),
108 });
109 }
110 Primitive::PopLayer | Primitive::EndEffect => {
111 flush(&mut stack);
112 let Some(mut scope) = stack.pop() else {
113 continue;
114 };
115 if stack.is_empty() {
116 stack.push(scope);
117 continue;
118 }
119 let info = scope.layer.unwrap_or(LayerInfo {
120 opacity: 1.0,
121 blend: "normal",
122 filtered: false,
123 });
124 let parent = stack.last_mut().expect("frame scope");
125 parent.items += scope.items;
126 parent.passes.append(&mut scope.passes);
127 parent.passes.push(Pass {
128 kind: if scope.shader.is_some() {
129 PassKind::Effect
130 } else {
131 PassKind::Composite
132 },
133 node: scope.node,
134 items: scope.items,
135 depth: scope.depth - 1,
136 opacity: info.opacity,
137 blend: info.blend,
138 filtered: info.filtered,
139 shader: scope.shader,
140 });
141 }
142 _ => {
143 let scope = stack.last_mut().expect("frame scope");
144 scope.items += 1;
145 scope.run += 1;
146 }
147 }
148 }
149 flush(&mut stack);
150 let mut passes = stack.pop().map(|s| s.passes).unwrap_or_default();
151 if passes.is_empty() {
152 passes.push(Pass {
153 kind: PassKind::Vector,
154 node: NodeId(0),
155 items: 0,
156 depth: 0,
157 opacity: 1.0,
158 blend: "normal",
159 filtered: false,
160 shader: None,
161 });
162 }
163 passes
164}
165
166struct LayerInfo {
167 opacity: f64,
168 blend: &'static str,
169 filtered: bool,
170}
171
172struct Scope {
173 node: NodeId,
174 items: u32,
175 run: u32,
176 depth: u32,
177 layer: Option<LayerInfo>,
178 shader: Option<&'static str>,
179 passes: Vec<Pass>,
180}
181
182impl Scope {
183 fn frame() -> Self {
184 Self {
185 node: NodeId(0),
186 items: 0,
187 run: 0,
188 depth: 0,
189 layer: None,
190 shader: None,
191 passes: Vec::new(),
192 }
193 }
194}
195
196fn flush(stack: &mut [Scope]) {
197 let Some(scope) = stack.last_mut() else {
198 return;
199 };
200 if scope.run == 0 {
201 return;
202 }
203 let run = scope.run;
204 scope.run = 0;
205 scope.passes.push(Pass {
206 kind: PassKind::Vector,
207 node: scope.node,
208 items: run,
209 depth: scope.depth,
210 opacity: 1.0,
211 blend: "normal",
212 filtered: false,
213 shader: None,
214 });
215}
216
217fn blend_name(blend: BlendMode) -> &'static str {
218 match blend {
219 BlendMode::Normal => "normal",
220 BlendMode::Multiply => "multiply",
221 BlendMode::Screen => "screen",
222 BlendMode::Overlay => "overlay",
223 BlendMode::Darken => "darken",
224 BlendMode::Lighten => "lighten",
225 BlendMode::ColorDodge => "color_dodge",
226 BlendMode::ColorBurn => "color_burn",
227 BlendMode::HardLight => "hard_light",
228 BlendMode::SoftLight => "soft_light",
229 BlendMode::Difference => "difference",
230 BlendMode::Exclusion => "exclusion",
231 BlendMode::Hue => "hue",
232 BlendMode::Saturation => "saturation",
233 BlendMode::Color => "color",
234 BlendMode::Luminosity => "luminosity",
235 }
236}
237
238pub fn layer_fingerprint(items: &[DrawItem], start: usize) -> u64 {
244 let mut hasher = std::collections::hash_map::DefaultHasher::new();
245 let mut depth = 0usize;
246 for item in &items[start..] {
247 item.node.0.hash(&mut hasher);
248 for c in item.transform.as_coeffs() {
249 c.to_bits().hash(&mut hasher);
250 }
251 match &item.prim {
252 Primitive::PushLayer(layer) => {
253 depth += 1;
254 if depth > 1 {
255 hash_layer(&mut hasher, layer);
256 }
257 }
258 Primitive::PopLayer => {
259 2u8.hash(&mut hasher);
260 depth -= 1;
261 if depth == 0 {
262 break;
263 }
264 }
265 Primitive::Fill { path, paint } => {
266 3u8.hash(&mut hasher);
267 hash_path(&mut hasher, path);
268 hash_paint(&mut hasher, paint);
269 }
270 Primitive::Stroke { path, paint, style } => {
271 4u8.hash(&mut hasher);
272 hash_path(&mut hasher, path);
273 hash_paint(&mut hasher, paint);
274 hash_stroke(&mut hasher, style);
275 }
276 Primitive::Image {
277 image,
278 rect,
279 opacity,
280 } => {
281 5u8.hash(&mut hasher);
282 image.id.hash(&mut hasher);
283 rect.x0.to_bits().hash(&mut hasher);
284 rect.y0.to_bits().hash(&mut hasher);
285 rect.x1.to_bits().hash(&mut hasher);
286 rect.y1.to_bits().hash(&mut hasher);
287 opacity.to_bits().hash(&mut hasher);
288 }
289 Primitive::ReadFrame(_) | Primitive::BeginEffect(_) | Primitive::EndEffect => {
290 6u8.hash(&mut hasher);
291 }
292 }
293 }
294 hasher.finish()
295}
296
297pub fn shadow_fingerprint(layer: &crate::draw::Layer) -> u64 {
301 let mut hasher = std::collections::hash_map::DefaultHasher::new();
302 layer.blur.to_bits().hash(&mut hasher);
303 if let Some(path) = &layer.clip {
304 hash_path(&mut hasher, path);
305 }
306 if let Some(shadow) = &layer.shadow {
307 hash_color(&mut hasher, shadow.color);
308 shadow.blur.to_bits().hash(&mut hasher);
309 }
310 hasher.finish()
311}
312
313fn hash_layer(hasher: &mut impl Hasher, layer: &crate::draw::Layer) {
314 1u8.hash(hasher);
315 layer.opacity.to_bits().hash(hasher);
316 blend_name(layer.blend).hash(hasher);
317 layer.blur.to_bits().hash(hasher);
318 if let Some(path) = &layer.clip {
319 hash_path(hasher, path);
320 }
321 if let Some(shadow) = &layer.shadow {
322 hash_color(hasher, shadow.color);
323 shadow.blur.to_bits().hash(hasher);
324 shadow.offset.x.to_bits().hash(hasher);
325 shadow.offset.y.to_bits().hash(hasher);
326 }
327}
328
329fn hash_path(hasher: &mut impl Hasher, path: &kurbo::BezPath) {
330 for el in path.elements() {
331 match el {
332 PathEl::MoveTo(p) => {
333 0u8.hash(hasher);
334 hash_point(hasher, *p);
335 }
336 PathEl::LineTo(p) => {
337 1u8.hash(hasher);
338 hash_point(hasher, *p);
339 }
340 PathEl::QuadTo(a, b) => {
341 2u8.hash(hasher);
342 hash_point(hasher, *a);
343 hash_point(hasher, *b);
344 }
345 PathEl::CurveTo(a, b, c) => {
346 3u8.hash(hasher);
347 hash_point(hasher, *a);
348 hash_point(hasher, *b);
349 hash_point(hasher, *c);
350 }
351 PathEl::ClosePath => 4u8.hash(hasher),
352 }
353 }
354}
355
356fn hash_point(hasher: &mut impl Hasher, p: kurbo::Point) {
357 p.x.to_bits().hash(hasher);
358 p.y.to_bits().hash(hasher);
359}
360
361fn hash_paint(hasher: &mut impl Hasher, paint: &Paint) {
362 match paint {
363 Paint::Solid(c) => {
364 0u8.hash(hasher);
365 hash_color(hasher, *c);
366 }
367 Paint::Gradient(g) => {
368 1u8.hash(hasher);
369 match g.kind {
370 oxframe_core::GradientKind::Linear { from, to } => {
371 0u8.hash(hasher);
372 hash_point(hasher, from);
373 hash_point(hasher, to);
374 }
375 oxframe_core::GradientKind::Radial { center, radius } => {
376 1u8.hash(hasher);
377 hash_point(hasher, center);
378 radius.to_bits().hash(hasher);
379 }
380 }
381 for (offset, color) in &g.stops {
382 offset.to_bits().hash(hasher);
383 hash_color(hasher, *color);
384 }
385 }
386 }
387}
388
389fn hash_color(hasher: &mut impl Hasher, c: oxframe_core::Color) {
390 c.r.to_bits().hash(hasher);
391 c.g.to_bits().hash(hasher);
392 c.b.to_bits().hash(hasher);
393 c.a.to_bits().hash(hasher);
394}
395
396fn hash_stroke(hasher: &mut impl Hasher, style: &StrokeStyle) {
397 style.width.to_bits().hash(hasher);
398 cap_tag(style.cap).hash(hasher);
399 join_tag(style.join).hash(hasher);
400 style.dash_offset.to_bits().hash(hasher);
401 for d in &style.dash {
402 d.to_bits().hash(hasher);
403 }
404}
405
406fn cap_tag(cap: crate::draw::LineCap) -> u8 {
407 use crate::draw::LineCap::{Butt, Round, Square};
408 match cap {
409 Butt => 0,
410 Round => 1,
411 Square => 2,
412 }
413}
414
415fn join_tag(join: crate::draw::LineJoin) -> u8 {
416 use crate::draw::LineJoin::{Bevel, Miter, Round};
417 match join {
418 Miter => 0,
419 Round => 1,
420 Bevel => 2,
421 }
422}
423
424#[cfg(test)]
425mod tests {
426 use oxframe_core::Color;
427
428 use super::*;
429 use crate::graph::SceneGraph;
430 use crate::{NodeBuilder, Rect, View};
431
432 #[test]
433 fn a_scene_without_effects_is_one_vector_pass() {
434 let graph = SceneGraph::new(80, 80);
435 graph.enter(|| {
436 View::current().add(Rect::new().size(20.0).fill(Color::WHITE));
437 View::current().add(Rect::new().size(10.0).x(15.0).fill(Color::BLACK));
438 });
439 let passes = pass_graph(&graph.draw_list());
440 assert_eq!(passes.len(), 1);
441 assert_eq!(passes[0].kind, PassKind::Vector);
442 assert!(passes[0].items >= 2);
443 }
444
445 #[test]
446 fn a_blur_is_a_composite_after_its_contents() {
447 let graph = SceneGraph::new(80, 80);
448 graph.enter(|| {
449 View::current().add(
450 Rect::new()
451 .size(20.0)
452 .fill(Color::WHITE)
453 .blur(2.0)
454 .name("soft"),
455 );
456 });
457 let passes = pass_graph(&graph.draw_list());
458 assert!(
459 passes.iter().any(|p| p.kind == PassKind::Vector),
460 "{passes:?}"
461 );
462 let composite = passes
463 .iter()
464 .find(|p| p.kind == PassKind::Composite)
465 .expect("blur opens a layer");
466 assert!(composite.filtered);
467 let vector_at = passes.iter().position(|p| p.kind == PassKind::Vector);
468 let composite_at = passes.iter().position(|p| p.kind == PassKind::Composite);
469 assert!(vector_at < composite_at);
470 }
471}