Skip to main content

cranpose_render_common/
layer_composition.rs

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