Skip to main content

cranpose_render_common/
layer_composition.rs

1use std::borrow::Cow;
2
3use cranpose_ui_graphics::{BlendMode, CompositingStrategy, GraphicsLayer, RenderEffect};
4
5/// What a layer needs to be composited from its own offscreen buffer.
6#[derive(Clone)]
7pub struct LayerIsolation {
8    /// The render effect applied to the buffer, if any.
9    pub effect: Option<RenderEffect>,
10    /// How the buffer blends onto its parent.
11    pub blend_mode: BlendMode,
12    /// Alpha applied when the buffer is composited, from 0.0 to 1.0. It is 1.0 for a layer that
13    /// modulates alpha in its content instead.
14    pub composite_alpha: f32,
15}
16
17/// Whether `layer` must be drawn into an offscreen buffer first, given its compositing strategy,
18/// effect, blend mode and alpha.
19#[inline]
20pub fn layer_requires_isolation(layer: &GraphicsLayer) -> bool {
21    let has_effect = layer.render_effect.is_some();
22    let has_layer_blend = layer.blend_mode != BlendMode::SrcOver;
23    match layer.compositing_strategy {
24        CompositingStrategy::Offscreen => true,
25        CompositingStrategy::Auto => has_effect || has_layer_blend || layer.alpha < 1.0,
26        CompositingStrategy::ModulateAlpha => has_effect || has_layer_blend,
27    }
28}
29
30fn isolation_composite_alpha(layer: &GraphicsLayer) -> f32 {
31    if layer.compositing_strategy == CompositingStrategy::ModulateAlpha {
32        1.0
33    } else {
34        GraphicsLayer::composite_alpha_8bit(layer.alpha)
35    }
36}
37
38/// The alpha `layer`'s content draws with when the layer is drawn in place:
39/// its own, or the 8-bit alpha of the isolated composite it needs.
40pub fn folded_layer_alpha(layer: &GraphicsLayer) -> f32 {
41    if layer.compositing_strategy != CompositingStrategy::ModulateAlpha
42        && layer_requires_isolation(layer)
43    {
44        GraphicsLayer::composite_alpha_8bit(layer.alpha)
45    } else {
46        layer.alpha
47    }
48}
49
50/// Returns what `layer` needs to be composited from its own buffer, or `None` when it can be
51/// drawn directly.
52pub fn effective_layer_isolation(layer: &GraphicsLayer) -> Option<LayerIsolation> {
53    layer_requires_isolation(layer).then(|| LayerIsolation {
54        effect: layer.render_effect.clone(),
55        blend_mode: layer.blend_mode,
56        composite_alpha: isolation_composite_alpha(layer),
57    })
58}
59
60/// The composite alpha and blend mode an isolated layer contributes at its
61/// parent, for callers that never read the isolation's render effect and would
62/// otherwise deep-clone it once per layer per frame.
63#[inline]
64pub fn layer_composite_params(layer: &GraphicsLayer) -> Option<(f32, BlendMode)> {
65    layer_requires_isolation(layer).then(|| (isolation_composite_alpha(layer), layer.blend_mode))
66}
67
68/// Returns the layer to draw the content with inside the offscreen buffer: `layer` with its alpha
69/// reset to one when `isolation` moves the alpha to the composite step.
70pub fn layer_for_content(
71    layer: &GraphicsLayer,
72    isolation: Option<&LayerIsolation>,
73) -> GraphicsLayer {
74    let mut content = layer.clone();
75    if isolation.is_some() && layer.compositing_strategy != CompositingStrategy::ModulateAlpha {
76        content.alpha = 1.0;
77    }
78    content
79}
80
81/// The alpha and colour filter of `layer_for_content(layer, isolation)`
82/// folded in place, without building it. The content layer differs from
83/// `layer` only in the alpha the isolation moves to the composite step, and
84/// only that alpha and the colour filter pass to the content, so the two
85/// clones the composed form performs — one `GraphicsLayer`, one
86/// `RenderEffect` — are pure waste. A layer that passes its content no
87/// alpha and no filter, as most do, lends the default layer.
88#[inline]
89pub fn local_content_layer_for(layer: &GraphicsLayer) -> Cow<'static, GraphicsLayer> {
90    let alpha = if layer.compositing_strategy != CompositingStrategy::ModulateAlpha
91        && layer_requires_isolation(layer)
92    {
93        1.0
94    } else {
95        layer.alpha
96    };
97    if alpha == 1.0 && layer.color_filter.is_none() {
98        return Cow::Borrowed(&GraphicsLayer::DEFAULT);
99    }
100    Cow::Owned(GraphicsLayer {
101        alpha,
102        color_filter: layer.color_filter.clone(),
103        ..GraphicsLayer::default()
104    })
105}
106
107#[cfg(test)]
108#[path = "tests/layer_composition_tests.rs"]
109mod tests;