use valo_dl::ColorFilter;
use valo_geometry::{Matrix, Rect};
use crate::pool::TargetPool;
use super::draw_context::PixelArea;
use super::emit::StepEmitter;
use super::filter_output::{Merge, RuntimeOutput};
use super::plan_writer::PlanWriter;
use super::shading::Shading;
use super::snapshot::{Placement, Snapshot};
pub(super) struct FilterPasses<'p, 'a> {
pub pool: &'p mut TargetPool,
pub emit: &'p mut StepEmitter<'a>,
pub plan: &'p mut PlanWriter,
pub format: wgpu::TextureFormat,
}
impl FilterPasses<'_, '_> {
pub fn push_resample(&mut self, source: &Snapshot, region: &Rect) -> Snapshot {
let into = PixelArea::over(*region).placement();
let shading = self.emit.resample_shading(source, &into);
self.push_region_pass(region, shading)
}
pub fn push_scaled_resample(
&mut self,
source: &Snapshot,
region: &Rect,
scale: f32,
) -> Snapshot {
let scaled = Scaled::of(region, scale);
let target = self.pool.take_filter(scaled.placement.size, self.format);
self.push_scaled_copy(source, &scaled, target.view.clone());
Snapshot {
view: target.view,
placement: scaled.placement,
}
}
pub fn push_pyramid(&mut self, source: &Snapshot, region: &Rect, scale: f32) -> Snapshot {
let scaled = Scaled::of(region, scale);
let size = scaled.placement.size;
let pyramid = self.pool.take_pyramid(size, self.format);
self.push_scaled_copy(source, &scaled, pyramid.levels[0].clone());
for (level, view) in pyramid.levels.iter().enumerate().skip(1) {
let above = level_size(size, level - 1);
let here = level_size(size, level);
let shading =
self.emit
.pyramid_downsample_shading(&pyramid.levels[level - 1], above, here);
let quad = Rect::new(0.0, 0.0, here[0] as f32, here[1] as f32);
self.push_pass(view.clone(), self.format, &quad, here, shading);
}
Snapshot {
view: pyramid.view,
placement: scaled.placement,
}
}
fn push_scaled_copy(&mut self, source: &Snapshot, scaled: &Scaled, target: wgpu::TextureView) {
let shading = self.emit.resample_shading(source, &scaled.placement);
let size = scaled.placement.size;
self.push_pass(target, self.format, &scaled.quad, size, shading);
}
pub fn push_runtime(&mut self, output: &RuntimeOutput) -> Snapshot {
let region = output.region;
let varying_to_local = output
.tree_to_local()
.then(&Matrix::translation(region.x, region.y));
let shading = self.emit.runtime_filter_shading(
&output.shader,
&output.source,
&output.local_bounds(),
&varying_to_local,
&output.effect_transform,
1.0,
);
self.push_region_pass(®ion, shading)
}
pub fn push_recolour(
&mut self,
source: &Snapshot,
filter: ColorFilter,
input_alpha: f32,
) -> Snapshot {
let region = source.placement.coverage();
let shading = self
.emit
.recolour_pass_shading(source, ®ion, filter, input_alpha);
self.push_region_pass(®ion, shading)
}
pub fn push_merge(&mut self, first: &Snapshot, second: &Snapshot, merge: Merge) -> Snapshot {
let region = first
.placement
.coverage()
.union(&second.placement.coverage());
let shading = self.emit.merge_pass_shading(first, second, ®ion, merge);
self.push_region_pass(®ion, shading)
}
fn push_region_pass(&mut self, region: &Rect, shading: Shading) -> Snapshot {
let area = PixelArea::over(*region);
let target = self.pool.take_filter(area.size(), self.format);
let quad = Rect::new(0.0, 0.0, region.width, region.height);
self.push_pass(
target.view.clone(),
self.format,
&quad,
area.size(),
shading,
);
Snapshot {
view: target.view,
placement: area.placement(),
}
}
pub fn push_pass(
&mut self,
target: wgpu::TextureView,
format: wgpu::TextureFormat,
quad: &Rect,
extent: [u32; 2],
shading: Shading,
) {
let draw = self.emit.filter_draw(format, quad, extent, shading);
self.plan.push_filter_pass(target, draw);
}
}
struct Scaled {
placement: Placement,
quad: Rect,
}
impl Scaled {
fn of(region: &Rect, scale: f32) -> Self {
let (width, height) = (region.width * scale, region.height * scale);
Self {
placement: Placement {
size: [width.ceil().max(1.0) as u32, height.ceil().max(1.0) as u32],
transform: Matrix::translation(region.x, region.y)
.then(&Matrix::scale(1.0 / scale, 1.0 / scale)),
placed: false,
},
quad: Rect::new(0.0, 0.0, width, height),
}
}
}
fn level_size(size: [u32; 2], level: usize) -> [u32; 2] {
size.map(|size| (size >> level).max(1))
}