Skip to main content

cranpose_render_common/
layer_composition.rs

1use cranpose_ui_graphics::{BlendMode, CompositingStrategy, GraphicsLayer, RenderEffect};
2
3#[derive(Clone)]
4pub struct LayerIsolation {
5    pub effect: Option<RenderEffect>,
6    pub blend_mode: BlendMode,
7    pub composite_alpha: f32,
8}
9
10pub fn layer_requires_isolation(layer: &GraphicsLayer) -> bool {
11    let has_effect = layer.render_effect.is_some();
12    let has_layer_blend = layer.blend_mode != BlendMode::SrcOver;
13    match layer.compositing_strategy {
14        CompositingStrategy::Offscreen => true,
15        CompositingStrategy::Auto => has_effect || has_layer_blend || layer.alpha < 1.0,
16        CompositingStrategy::ModulateAlpha => has_effect || has_layer_blend,
17    }
18}
19
20fn isolation_composite_alpha(layer: &GraphicsLayer) -> f32 {
21    if layer.compositing_strategy == CompositingStrategy::ModulateAlpha {
22        1.0
23    } else {
24        GraphicsLayer::composite_alpha_8bit(layer.alpha)
25    }
26}
27
28fn folded_layer_alpha(layer: &GraphicsLayer) -> f32 {
29    if layer.compositing_strategy != CompositingStrategy::ModulateAlpha
30        && layer_requires_isolation(layer)
31    {
32        GraphicsLayer::composite_alpha_8bit(layer.alpha)
33    } else {
34        layer.alpha
35    }
36}
37
38pub fn effective_layer_isolation(layer: &GraphicsLayer) -> Option<LayerIsolation> {
39    layer_requires_isolation(layer).then(|| LayerIsolation {
40        effect: layer.render_effect.clone(),
41        blend_mode: layer.blend_mode,
42        composite_alpha: isolation_composite_alpha(layer),
43    })
44}
45
46/// The composite alpha and blend mode an isolated layer contributes at its
47/// parent, for callers that never read the isolation's render effect and would
48/// otherwise deep-clone it once per layer per frame.
49pub fn layer_composite_params(layer: &GraphicsLayer) -> Option<(f32, BlendMode)> {
50    layer_requires_isolation(layer).then(|| (isolation_composite_alpha(layer), layer.blend_mode))
51}
52
53pub fn layer_for_content(
54    layer: &GraphicsLayer,
55    isolation: Option<&LayerIsolation>,
56) -> GraphicsLayer {
57    let mut content = layer.clone();
58    if isolation.is_some() && layer.compositing_strategy != CompositingStrategy::ModulateAlpha {
59        content.alpha = 1.0;
60    }
61    content
62}
63
64pub fn local_content_layer(layer: &GraphicsLayer) -> GraphicsLayer {
65    GraphicsLayer {
66        alpha: folded_layer_alpha(layer),
67        color_filter: layer.color_filter,
68        ..GraphicsLayer::default()
69    }
70}
71
72/// `local_content_layer(&layer_for_content(layer, isolation))` without building
73/// either intermediate. The content layer differs from `layer` only in the
74/// alpha the isolation moves to the composite step, and the local layer keeps
75/// just that alpha and the colour filter, so the two clones the composed form
76/// performs — one `GraphicsLayer`, one `RenderEffect` — are pure waste.
77pub fn local_content_layer_for(layer: &GraphicsLayer) -> GraphicsLayer {
78    let alpha = if layer.compositing_strategy != CompositingStrategy::ModulateAlpha
79        && layer_requires_isolation(layer)
80    {
81        1.0
82    } else {
83        layer.alpha
84    };
85    GraphicsLayer {
86        alpha,
87        color_filter: layer.color_filter,
88        ..GraphicsLayer::default()
89    }
90}
91
92#[cfg(test)]
93#[path = "tests/layer_composition_tests.rs"]
94mod tests;