use rustc_hash::FxHashMap;
use valo_dl::{BlendMode, DisplayList, MaskKind, Paint};
use valo_geometry::{Color, Matrix, Point, Rect};
use crate::frame::{PassColor, Step, TextureCopy};
use crate::pipelines::{Frag, PipelineKind};
use crate::raster::FillTarget;
use crate::renderer::RenderTarget;
use super::emit::{alpha_tint, PAYLOAD_GEOM, PAYLOAD_MISC};
use super::filters::{region_uv, LayerEffects, SharedBlur};
use super::Planner;
pub(super) struct BackdropRequest {
pub sigma_local: f32,
pub key: Option<u64>,
pub group_bounds: Option<Rect>,
}
struct BackdropSeed {
view: wgpu::TextureView,
region: Rect,
uv_max: [f32; 2],
}
pub(super) struct PassFrame {
pub color: PassColor,
pub depth: wgpu::TextureView,
pub src_texture: wgpu::Texture,
pub size: [u32; 2],
pub clear: Option<Color>,
pub first_segment_emitted: bool,
pub last_pass: Option<usize>,
pub steps: Vec<Step>,
pub pre_copies: Vec<TextureCopy>,
pub cull_rect: Rect,
pub z_denom: f32,
pub origin: Point,
pub transient: bool,
pub layer: Option<LayerInfo>,
}
impl PassFrame {
pub fn main(
color: PassColor,
depth: wgpu::TextureView,
target: &RenderTarget,
dl: &DisplayList,
transient: bool,
) -> Self {
Self {
color,
depth,
src_texture: target.texture.clone(),
size: target.size,
clear: target.clear,
first_segment_emitted: false,
last_pass: None,
steps: Vec::with_capacity(dl.draw_count() as usize * 2),
pre_copies: Vec::new(),
cull_rect: Rect::new(0.0, 0.0, target.size[0] as f32, target.size[1] as f32),
z_denom: (dl.depth_slots() + 1) as f32,
origin: Point::ZERO,
transient,
layer: None,
}
}
}
pub(super) struct LayerInfo {
pub rect: Rect,
pub paint: Paint,
pub mask_composite: Option<MaskKind>,
pub composite_z: f32,
pub resolve: wgpu::TextureView,
pub effects: LayerEffects,
}
pub(super) struct ResolvedLayer<'a> {
pub paint: &'a Paint,
pub mask: Option<MaskKind>,
pub bounds: &'a Rect,
pub base_slot: u32,
pub composite_slot: u32,
pub composite_z: f32,
pub can_elide: bool,
pub backdrop: Option<BackdropRequest>,
}
pub(super) enum Opened {
Skip,
Elided,
Layer,
}
impl Planner<'_> {
pub(super) fn open_layer(
&mut self,
base: &Matrix,
effect_transform: &Matrix,
layer: ResolvedLayer<'_>,
shared_blurs: &mut FxHashMap<u64, SharedBlur>,
) -> Opened {
let Some(rect) = self.layer_rect(base, layer.bounds) else {
if layer.mask.is_some() {
self.erase_frame_alpha(layer.composite_z);
}
return Opened::Skip;
};
if layer.can_elide {
self.stats.layers_elided += 1;
self.push_elision(layer.paint.color.a);
return Opened::Elided;
}
self.stats.layers_rendered += 1;
let seed = layer.backdrop.map(|request| {
self.backdrop_seed(base, effect_transform, &rect, request, shared_blurs)
});
let effects = LayerEffects::of(
layer.paint,
effect_transform.max_scale(),
effect_transform,
true,
);
self.push_layer_frame(
rect,
(layer.composite_slot - layer.base_slot) as f32,
layer.paint.clone(),
layer.mask,
layer.composite_z,
effects,
);
if let Some(seed) = seed {
self.emit_backdrop_seed(&rect, &seed);
}
Opened::Layer
}
fn backdrop_seed(
&mut self,
base: &Matrix,
effect_transform: &Matrix,
rect: &Rect,
request: BackdropRequest,
shared_blurs: &mut FxHashMap<u64, SharedBlur>,
) -> BackdropSeed {
let sigma = (request.sigma_local * effect_transform.max_scale()).max(0.05);
if let Some(shared) = request
.key
.and_then(|key| shared_blurs.get(&key))
.filter(|shared| (shared.sigma - sigma).abs() < 1e-3)
.filter(|shared| shared.source == self.frame().src_texture)
{
self.stats.shared_backdrops += 1;
return BackdropSeed {
view: shared.view.clone(),
region: shared.region,
uv_max: shared.uv_max,
};
}
let bounds = request
.group_bounds
.and_then(|union| base.map_rect(&union).intersect(&self.frame().cull_rect))
.unwrap_or(*rect);
let padded = bounds.expand((sigma * 3.0).ceil());
let region = padded.intersect(&self.frame().cull_rect).unwrap_or(bounds);
let blur = self.blur_of_target_region(®ion, sigma);
if let Some(key) = request.key {
shared_blurs.entry(key).or_insert(SharedBlur {
view: blur.view.clone(),
region,
uv_max: blur.uv_max,
sigma,
source: self.frame().src_texture.clone(),
});
}
self.stats.backdrops += 1;
BackdropSeed {
view: blur.view,
region,
uv_max: blur.uv_max,
}
}
fn emit_backdrop_seed(&mut self, rect: &Rect, seed: &BackdropSeed) {
let bind = self.emit.texture_bind(&seed.view);
let frame = self.frames.last_mut().expect("layer frame just pushed");
let mut record = self
.emit
.quad_record(frame, rect, [1.0, 1.0, 1.0, 1.0], 0.0);
record.set_payload(PAYLOAD_GEOM, region_uv(&seed.region, seed.uv_max));
self.emit.push_step(
frame,
PipelineKind::Draw(Frag::Image),
BlendMode::SrcOver,
record,
Some(bind),
None,
0.0,
);
}
pub(super) fn close_layer(&mut self) {
self.emit_segment();
let frame = self.frames.pop().expect("layer frame present");
let info = frame.layer.expect("close_layer only on layer frames");
self.composite_layer(&info);
}
fn layer_rect(&mut self, base: &Matrix, scope_bounds: &Rect) -> Option<Rect> {
if scope_bounds.is_empty() {
return None;
}
base.map_rect(scope_bounds)
.intersect(&self.frame().cull_rect)
}
pub(super) fn push_layer_frame(
&mut self,
rect: Rect,
z_denom: f32,
mut paint: Paint,
mask: Option<MaskKind>,
composite_z: f32,
effects: LayerEffects,
) {
paint.color.a *= self.elision_alpha();
let size = layer_texture_size(&rect);
let target = self.pool.take_layer(size, self.format, true);
self.frames.push(PassFrame {
color: PassColor::Layer {
msaa: target.msaa,
resolve: target.resolve.clone(),
},
depth: target.depth,
src_texture: target.resolve_texture,
size,
clear: Some(Color::TRANSPARENT),
first_segment_emitted: false,
last_pass: None,
steps: Vec::new(),
pre_copies: Vec::new(),
cull_rect: rect,
z_denom,
origin: Point::new(rect.x, rect.y),
transient: true,
layer: Some(LayerInfo {
rect,
paint,
mask_composite: mask,
composite_z,
resolve: target.resolve,
effects,
}),
});
}
fn push_elision(&mut self, alpha: f32) {
let combined = alpha * self.elisions.last().copied().unwrap_or(1.0);
self.elisions.push(combined);
}
pub(super) fn plan_via_implicit_layer(
&mut self,
device_bounds: Rect,
z: f32,
mode: BlendMode,
inner: impl FnOnce(&mut Self),
) {
let Some(rect) = device_bounds.intersect(&self.frame().cull_rect) else {
return;
};
self.stats.layers_rendered += 1;
let paint = Paint {
color: Color::WHITE,
blend_mode: mode,
..Default::default()
};
self.push_layer_frame(rect, 2.0, paint, None, z, LayerEffects::default());
let outer_elisions = std::mem::take(&mut self.elisions);
inner(self);
self.elisions = outer_elisions;
self.close_layer();
}
pub(super) fn plan_via_effect_layer_at(
&mut self,
device_bounds: Rect,
paint: &Paint,
current: &Matrix,
z: f32,
inner: impl FnOnce(&mut Self),
) {
let Some(rect) = device_bounds.intersect(&self.frame().cull_rect) else {
return;
};
self.stats.layers_rendered += 1;
let composite = Paint {
color: Color::WHITE,
blend_mode: paint.blend_mode,
..Default::default()
};
let effects = LayerEffects::of(paint, current.max_scale(), current, false);
self.push_layer_frame(rect, 2.0, composite, None, z, effects);
let outer_elisions = std::mem::take(&mut self.elisions);
inner(self);
self.elisions = outer_elisions;
self.close_layer();
}
pub(super) fn push_raster_frame(&mut self, target: &FillTarget, z_denom: f32) {
let attachments = self.pool.take_raster_attachments(target.size, self.format);
self.frames.push(PassFrame {
color: PassColor::Layer {
msaa: attachments.msaa,
resolve: target.view.clone(),
},
depth: attachments.depth,
src_texture: target.texture.clone(),
size: target.size,
clear: Some(Color::TRANSPARENT),
first_segment_emitted: false,
last_pass: None,
steps: Vec::new(),
pre_copies: Vec::new(),
cull_rect: Rect::new(0.0, 0.0, target.size[0] as f32, target.size[1] as f32),
z_denom,
origin: Point::ZERO,
transient: true,
layer: None,
});
}
pub(super) fn close_raster_frame(&mut self) {
self.emit_segment();
self.frames.pop().expect("raster frame present");
}
fn composite_layer(&mut self, info: &LayerInfo) {
if let Some(kind) = info.mask_composite {
return self.composite_mask_layer(info, kind);
}
let (view, uv) = self.composite_source(info);
let tint = alpha_tint(info.paint.color.a);
if info.paint.blend_mode.is_pipeline_blendable() {
let bind = self.emit.texture_bind(&view);
let frame = self.frames.last_mut().expect("frame stack never empty");
let mut record = self
.emit
.quad_record(frame, &info.rect, tint, info.composite_z);
record.set_payload(PAYLOAD_GEOM, uv);
self.emit.push_step(
frame,
PipelineKind::Draw(Frag::Image),
info.paint.blend_mode,
record,
Some(bind),
None,
info.composite_z,
);
} else {
let snapshot = self.break_pass(&info.rect);
let bind = self.emit.blend_bind(&snapshot, &view);
let frame = self.frames.last_mut().expect("frame stack never empty");
let mut record = self
.emit
.quad_record(frame, &info.rect, tint, info.composite_z);
record.set_payload(PAYLOAD_GEOM, uv);
self.emit
.set_blend_misc(frame, &mut record, info.paint.blend_mode);
self.emit.push_step(
frame,
PipelineKind::Draw(Frag::BlendTexture),
BlendMode::SrcOver,
record,
Some(bind),
None,
info.composite_z,
);
}
}
fn composite_mask_layer(&mut self, info: &LayerInfo, kind: MaskKind) {
let size = layer_texture_size(&info.rect);
let bind = self.emit.texture_bind(&info.resolve);
let frame = self.frames.last_mut().expect("frame stack never empty");
let extent = frame.cull_rect;
let mut record = self.emit.quad_record(
frame,
&extent,
alpha_tint(info.paint.color.a),
info.composite_z,
);
let (w, h) = (size[0] as f32, size[1] as f32);
record.set_payload(
PAYLOAD_GEOM,
[1.0 / w, 1.0 / h, -info.rect.x / w, -info.rect.y / h],
);
let luma = match kind {
MaskKind::Luminance => 1.0,
MaskKind::Alpha => 0.0,
};
record.set_payload(PAYLOAD_MISC, [luma, 0.0, 0.0, 0.0]);
self.emit.push_step(
frame,
PipelineKind::Draw(Frag::MaskComposite),
BlendMode::DstIn,
record,
Some(bind),
None,
info.composite_z,
);
}
fn erase_frame_alpha(&mut self, z: f32) {
let frame = self.frames.last_mut().expect("frame stack never empty");
let extent = frame.cull_rect;
let record = self.emit.quad_record(frame, &extent, [0.0; 4], z);
self.emit.push_step(
frame,
PipelineKind::Draw(Frag::Solid),
BlendMode::DstIn,
record,
None,
None,
z,
);
}
pub(super) fn frame(&self) -> &PassFrame {
self.frames.last().expect("frame stack never empty")
}
pub(super) fn frame_mut(&mut self) -> &mut PassFrame {
self.frames.last_mut().expect("frame stack never empty")
}
pub(super) fn elision_alpha(&self) -> f32 {
self.elisions.last().copied().unwrap_or(1.0)
}
}
pub(super) fn layer_texture_size(rect: &Rect) -> [u32; 2] {
[
rect.width.ceil().max(1.0) as u32,
rect.height.ceil().max(1.0) as u32,
]
}