Skip to main content

fret_core/scene/
composite.rs

1use super::{EffectQuality, Rect};
2
3#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
4pub enum BlendMode {
5    /// Premultiplied alpha-over (the baseline compositing contract; ADR 0040).
6    #[default]
7    Over,
8    /// Additive blending (used for glow/beam).
9    Add,
10    /// Multiply blending (used for grain/darken overlays).
11    Multiply,
12    /// Screen blending (used for light overlays).
13    Screen,
14    /// Channel-wise darken (`min(dst, src)` for color channels).
15    ///
16    /// This mode is bounded and portable because it maps to a fixed-function blend operation
17    /// (no destination sampling).
18    Darken,
19    /// Channel-wise lighten (`max(dst, src)` for color channels).
20    ///
21    /// This mode is bounded and portable because it maps to a fixed-function blend operation
22    /// (no destination sampling).
23    Lighten,
24    /// Subtract (`dst - src`, clamped to `[0, 1]`) for color channels.
25    ///
26    /// This mode is bounded and portable because it maps to a fixed-function blend operation
27    /// (no destination sampling).
28    Subtract,
29}
30
31impl BlendMode {
32    pub const COUNT: usize = 7;
33
34    pub const ALL: [BlendMode; Self::COUNT] = [
35        BlendMode::Over,
36        BlendMode::Add,
37        BlendMode::Multiply,
38        BlendMode::Screen,
39        BlendMode::Darken,
40        BlendMode::Lighten,
41        BlendMode::Subtract,
42    ];
43
44    pub const fn pipeline_index(self) -> usize {
45        match self {
46            BlendMode::Over => 0,
47            BlendMode::Add => 1,
48            BlendMode::Multiply => 2,
49            BlendMode::Screen => 3,
50            BlendMode::Darken => 4,
51            BlendMode::Lighten => 5,
52            BlendMode::Subtract => 6,
53        }
54    }
55}
56
57/// Descriptor for an isolated compositing group (ADR 0247).
58///
59/// The group is rendered into an offscreen intermediate and then composited back onto the parent
60/// target using the requested `mode`. `bounds` is a computation bound (not an implicit clip).
61#[non_exhaustive]
62#[derive(Debug, Clone, Copy, PartialEq)]
63pub struct CompositeGroupDesc {
64    /// Computation bounds (not an implicit clip), see ADR 0247.
65    pub bounds: Rect,
66    pub mode: BlendMode,
67    pub quality: EffectQuality,
68    /// Group-level opacity multiplier applied when the group is composited back to its parent.
69    ///
70    /// This enables CSS-like isolated opacity semantics (e.g. `saveLayerAlpha`): overlapping
71    /// children inside the group blend with each other normally, then the final group result is
72    /// multiplied by this opacity.
73    ///
74    /// Default: `1.0`.
75    pub opacity: f32,
76}
77
78impl CompositeGroupDesc {
79    pub const fn new(bounds: Rect, mode: BlendMode, quality: EffectQuality) -> Self {
80        Self {
81            bounds,
82            mode,
83            quality,
84            opacity: 1.0,
85        }
86    }
87
88    pub fn with_opacity(mut self, opacity: f32) -> Self {
89        self.opacity = opacity;
90        self
91    }
92}