use rustc_hash::FxHashMap;
use valo_dl::{Backdrop, DisplayList, ImageFilter, TileMode};
use valo_geometry::{Matrix, Rect};
use crate::pipelines::PipelineBlend;
use super::draw_state::DrawState;
use super::filter_output::FilterOutput;
use super::filter_tree::{Filter, FilterInput};
use super::gaussian::{basis_of, expanded, BlurBounds, BlurPlan};
use super::layers::round_out;
use super::snapshot::Snapshot;
use super::Planner;
pub(super) struct BackdropRequest {
pub filter: ImageFilter,
pub shared: Option<SharedGroup>,
}
pub(super) struct SharedGroup {
pub key: u64,
pub bounds: Rect,
}
impl BackdropRequest {
pub fn of(backdrop: &Backdrop, list: &DisplayList, base: &Matrix) -> Self {
let shared = backdrop
.shared_key
.and_then(|key| list.backdrop_group(key))
.filter(|group| group.filter.is_some())
.map(|group| SharedGroup {
key: group.key,
bounds: base.map_rect(&group.union_bounds),
});
Self {
filter: backdrop.filter.clone(),
shared,
}
}
}
pub(super) struct SharedBackdrop {
pub snapshot: Snapshot,
pub filter: ImageFilter,
pub local_to_target: Matrix,
pub source: wgpu::Texture,
}
impl SharedBackdrop {
pub fn matches(&self, filter: &ImageFilter, local_to_target: &Matrix) -> bool {
if self.filter != *filter {
return false;
}
if reads_within_bounds(filter) {
self.local_to_target == *local_to_target
} else {
basis_of(&self.local_to_target) == basis_of(local_to_target)
}
}
}
impl Planner<'_> {
pub(super) fn render_backdrop_seed(
&mut self,
composite: &DrawState,
rect: &Rect,
request: BackdropRequest,
shared_backdrops: &mut FxHashMap<u64, SharedBackdrop>,
) -> Option<FilterOutput<'static>> {
let effect_transform = &composite.transform;
if let Some(shared) = request
.shared
.as_ref()
.and_then(|group| shared_backdrops.get(&group.key))
.filter(|shared| shared.matches(&request.filter, effect_transform))
.filter(|shared| shared.source == self.contexts.top().src_texture)
{
self.plan.stats.shared_backdrops += 1;
return Some(FilterOutput::Snapshot(shared.snapshot.clone()));
}
let area = self.contexts.top().area;
let hint = request
.shared
.as_ref()
.and_then(|group| group.bounds.intersect(&area.rect()))
.unwrap_or(*rect);
let output = self.render_backdrop(&hint, &request.filter, composite)?;
self.plan.stats.backdrops += 1;
let Some(SharedGroup { key, .. }) = request.shared else {
return Some(output);
};
let snapshot = self.snapshot_output(output);
shared_backdrops.entry(key).or_insert(SharedBackdrop {
snapshot: snapshot.clone(),
filter: request.filter,
local_to_target: *effect_transform,
source: self.contexts.top().src_texture.clone(),
});
Some(FilterOutput::Snapshot(snapshot))
}
pub(super) fn draw_backdrop_seed(&mut self, seed: &FilterOutput<'_>) {
let floor = self.contexts.top().depth.floor();
self.drawing().draw_filter_output(
seed,
PipelineBlend::SrcOver,
&DrawState::in_layer(Matrix::IDENTITY, floor),
);
}
pub(super) fn render_backdrop(
&mut self,
hint: &Rect,
filter: &ImageFilter,
composite: &DrawState,
) -> Option<FilterOutput<'static>> {
let local_to_target = &composite.transform;
let parent = self.split_for_copy(composite.z);
let tree = FilterInput::Source.with_image_filter(Some(filter), TileMode::Mirror);
match backdrop_blur(&tree, &parent, hint, local_to_target) {
Some(blur) => {
let blurred = self.filter_passes().push_blur(&blur, &parent.view);
Some(FilterOutput::Snapshot(blurred))
}
None => self.render_parent_filters(&parent, hint, &tree, local_to_target),
}
}
fn render_parent_filters(
&mut self,
parent: &Snapshot,
hint: &Rect,
tree: &FilterInput<'static>,
local_to_target: &Matrix,
) -> Option<FilterOutput<'static>> {
let placement = &parent.placement;
let needed = tree
.source_coverage(local_to_target, hint)
.intersect(&placement.coverage())?;
let region = placement
.transform
.map_rect(&round_out(&placement.texel_rect(&needed)));
let source = self.filter_passes().push_resample(parent, ®ion);
Some(self.render_filter_tree(tree, &source, local_to_target, None))
}
}
fn backdrop_blur(
tree: &FilterInput<'_>,
parent: &Snapshot,
hint: &Rect,
local_to_target: &Matrix,
) -> Option<BlurPlan> {
let FilterInput::Filter(node) = tree else {
return None;
};
let (
Filter::Blur {
bounds, tile_mode, ..
},
FilterInput::Source,
) = (&node.filter, &node.input)
else {
return None;
};
let blur = node.filter.blur(local_to_target)?;
let input_hint = expanded(hint, blur.local_padding());
let bounds = bounds.as_ref().map(|rect| BlurBounds {
rect,
local_to_tree: local_to_target,
});
BlurPlan::new(
&blur,
&parent.placement,
Some(&input_hint),
*tile_mode,
bounds,
)
}
fn reads_within_bounds(filter: &ImageFilter) -> bool {
match filter {
ImageFilter::Blur { bounds, .. } => bounds.is_some(),
ImageFilter::Color(_) | ImageFilter::DropShadow { .. } => false,
ImageFilter::Compose { outer, inner } => {
reads_within_bounds(outer) || reads_within_bounds(inner)
}
}
}