use anyrender::{Filter, PaintScene};
use kurbo::{Affine, Shape};
use peniko::Mix;
use std::sync::atomic::{AtomicU32, Ordering};
use std::{cell::Cell, sync::Arc};
const LAYER_LIMIT: u32 = 1024;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LayerSite {
ClipPath,
Effect,
Overflow,
OutsetShadow,
BackgroundImage,
InsetShadow,
Mask,
Border,
}
impl LayerSite {
pub const ALL: [LayerSite; 8] = [
LayerSite::ClipPath,
LayerSite::Effect,
LayerSite::Overflow,
LayerSite::OutsetShadow,
LayerSite::BackgroundImage,
LayerSite::InsetShadow,
LayerSite::Mask,
LayerSite::Border,
];
pub fn name(self) -> &'static str {
match self {
LayerSite::ClipPath => "clip-path",
LayerSite::Effect => "effect",
LayerSite::Overflow => "overflow",
LayerSite::OutsetShadow => "outset-shadow",
LayerSite::BackgroundImage => "bg-image",
LayerSite::InsetShadow => "inset-shadow",
LayerSite::Mask => "mask",
LayerSite::Border => "border",
}
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct SceneLayerCounts {
pub wanted: u32,
pub used: u32,
pub max_depth: u32,
pub by_site: [u32; 8],
}
static LATEST_WANTED: AtomicU32 = AtomicU32::new(0);
static LATEST_USED: AtomicU32 = AtomicU32::new(0);
static LATEST_MAX_DEPTH: AtomicU32 = AtomicU32::new(0);
#[allow(clippy::declare_interior_mutable_const)]
const ZERO: AtomicU32 = AtomicU32::new(0);
static LATEST_BY_SITE: [AtomicU32; 8] = [ZERO; 8];
pub fn latest_scene_layers() -> SceneLayerCounts {
let mut by_site = [0u32; 8];
for (slot, counter) in by_site.iter_mut().zip(LATEST_BY_SITE.iter()) {
*slot = counter.load(Ordering::Relaxed);
}
SceneLayerCounts {
wanted: LATEST_WANTED.load(Ordering::Relaxed),
used: LATEST_USED.load(Ordering::Relaxed),
max_depth: LATEST_MAX_DEPTH.load(Ordering::Relaxed),
by_site,
}
}
#[derive(Default)]
pub(crate) struct LayerManager {
layers_used: Cell<u32>,
layer_depth: Cell<u32>,
layers_wanted: Cell<u32>,
layer_depth_used: Cell<u32>,
by_site: [Cell<u32>; 8],
}
impl LayerManager {
pub(crate) fn publish(&self) {
LATEST_WANTED.store(self.layers_wanted.get(), Ordering::Relaxed);
LATEST_USED.store(self.layers_used.get(), Ordering::Relaxed);
LATEST_MAX_DEPTH.store(self.layer_depth_used.get(), Ordering::Relaxed);
for (counter, cell) in LATEST_BY_SITE.iter().zip(self.by_site.iter()) {
counter.store(cell.get(), Ordering::Relaxed);
}
}
pub(crate) fn note_unmanaged(&self, site: LayerSite) {
self.by_site[site as usize].update(|x| x + 1);
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn maybe_with_layer<S: PaintScene, F: FnOnce(&mut S)>(
&self,
scene: &mut S,
site: LayerSite,
condition: bool,
opacity: f32,
transform: Affine,
shape: &impl Shape,
filter: Option<Arc<Filter>>,
backdrop_filter: Option<Arc<Filter>>,
paint_layer: F,
) {
let layer_used = self.maybe_push_layer(
scene,
site,
condition,
opacity,
transform,
shape,
filter,
backdrop_filter,
);
paint_layer(scene);
self.maybe_pop_layer(scene, layer_used);
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn maybe_push_layer(
&self,
scene: &mut impl PaintScene,
site: LayerSite,
condition: bool,
opacity: f32,
transform: Affine,
shape: &impl Shape,
filter: Option<Arc<Filter>>,
backdrop_filter: Option<Arc<Filter>>,
) -> bool {
if !condition {
return false;
}
self.layers_wanted.update(|x| x + 1);
self.by_site[site as usize].update(|x| x + 1);
let layers_available = self.layers_used.get() <= LAYER_LIMIT;
if !layers_available {
return false;
}
if opacity == 1.0 && filter.is_none() && backdrop_filter.is_none() {
scene.push_clip_layer(transform, shape);
} else {
scene.push_layer(
Mix::Normal,
opacity,
transform,
shape,
filter,
backdrop_filter,
);
};
self.layers_used.update(|x| x + 1);
self.layer_depth.update(|x| x + 1);
self.layer_depth_used
.update(|x| x.max(self.layer_depth.get()));
true
}
pub(crate) fn maybe_pop_layer(&self, scene: &mut impl PaintScene, condition: bool) {
if condition {
scene.pop_layer();
self.layer_depth.update(|x| x - 1);
}
}
}