use std::sync::Arc;
use rustc_hash::FxHashMap;
use valo_dl::{DisplayList, Op};
use valo_geometry::{Matrix, Rect};
use crate::raster::{FillTarget, QuadSource, RasterVerdict};
use super::filters::SharedBlur;
use super::layers::{BackdropRequest, Opened, ResolvedLayer};
use super::route::{DrawSource, GlyphRun};
use super::Planner;
pub(super) struct ReplayState {
scopes: Vec<ScopeEntry>,
slot_offset: i64,
base: Matrix,
shared_blurs: FxHashMap<u64, SharedBlur>,
}
struct ScopeEntry {
transform: Matrix,
on_restore: RestoreAction,
}
enum RestoreAction {
None,
PopGroupAlpha,
CloseLayer {
outer_slot_offset: i64,
outer_elisions: Vec<f32>,
},
}
impl ReplayState {
pub fn root() -> Self {
Self::nested(Matrix::IDENTITY, 0)
}
pub fn nested(base: Matrix, slot_offset: i64) -> Self {
Self {
scopes: vec![ScopeEntry {
transform: base,
on_restore: RestoreAction::None,
}],
slot_offset,
base,
shared_blurs: FxHashMap::default(),
}
}
fn top(&self) -> &ScopeEntry {
self.scopes.last().expect("builder balances scopes")
}
fn push_scope(&mut self, on_restore: RestoreAction) {
self.scopes.push(ScopeEntry {
transform: self.top().transform,
on_restore,
});
}
}
impl Planner<'_> {
pub(super) fn replay_list(&mut self, dl: &DisplayList, state: &mut ReplayState) {
let ops = dl.ops();
let mut i = 0;
while i < ops.len() {
self.stats.ops += 1;
match &ops[i] {
Op::Save => state.push_scope(RestoreAction::None),
Op::Transform(t) => {
let top = state.scopes.last_mut().expect("builder balances scopes");
top.transform = top.transform.then(t);
}
Op::SaveLayer {
paint,
mask_composite,
scope_bounds,
base_slot,
composite_slot,
can_elide,
backdrop_sigma,
backdrop_key,
} => {
let backdrop = backdrop_sigma.map(|sigma_local| {
let key = backdrop_key
.filter(|&k| dl.backdrop_group(k).is_some_and(|g| g.sigma.is_some()));
BackdropRequest {
sigma_local,
key,
group_bounds: key
.and_then(|k| dl.backdrop_group(k))
.map(|group| group.union_bounds),
}
});
let composite_z = self.slot_z(state, *composite_slot);
let effect_transform = state.top().transform;
let list_base = state.base;
match self.open_layer(
&list_base,
&effect_transform,
ResolvedLayer {
paint,
mask: *mask_composite,
bounds: scope_bounds,
base_slot: *base_slot,
composite_slot: *composite_slot,
composite_z,
can_elide: *can_elide,
backdrop,
},
&mut state.shared_blurs,
) {
Opened::Skip => {
i = skip_scope(ops, i) + 1;
continue;
}
Opened::Elided => state.push_scope(RestoreAction::PopGroupAlpha),
Opened::Layer => {
let outer_slot_offset =
std::mem::replace(&mut state.slot_offset, -(*base_slot as i64));
let outer_elisions = std::mem::take(&mut self.elisions);
state.push_scope(RestoreAction::CloseLayer {
outer_slot_offset,
outer_elisions,
});
}
}
}
Op::Restore => {
let entry = state.scopes.pop().expect("builder balances scopes");
match entry.on_restore {
RestoreAction::None => {}
RestoreAction::PopGroupAlpha => {
self.elisions.pop();
}
RestoreAction::CloseLayer {
outer_slot_offset,
outer_elisions,
} => {
state.slot_offset = outer_slot_offset;
self.elisions = outer_elisions;
self.close_layer();
}
}
}
Op::DrawRect {
rect,
paint,
bounds,
slot,
} => {
if !self.culled(&state.base, bounds, 1) {
let z = self.slot_z(state, *slot);
let current = state.top().transform;
let device_bounds = state.base.map_rect(bounds);
self.plan_routed(DrawSource::Rect(rect), paint, ¤t, device_bounds, z);
}
}
Op::DrawPath {
path,
fill_rule,
paint,
bounds,
slot,
} => {
if !self.culled(&state.base, bounds, 1) {
let z = self.slot_z(state, *slot);
let current = state.top().transform;
let source = DrawSource::Path {
path,
rule: *fill_rule,
};
let device_bounds = state.base.map_rect(bounds);
self.plan_routed(source, paint, ¤t, device_bounds, z);
}
}
Op::DrawImage {
image,
src,
dst,
sampling,
paint,
bounds,
slot,
} => {
if !self.culled(&state.base, bounds, 1) {
let z = self.slot_z(state, *slot);
let current = state.top().transform;
let source = DrawSource::Image {
image,
src,
dst,
sampling: *sampling,
};
let device_bounds = state.base.map_rect(bounds);
self.plan_routed(source, paint, ¤t, device_bounds, z);
}
}
Op::RRectBlur {
rect,
radii,
paint,
bounds,
slot,
} => {
if !self.culled(&state.base, bounds, 1) {
let z = self.slot_z(state, *slot);
let current = state.top().transform;
let source = DrawSource::RRectBlur {
rect,
radii: *radii,
};
let device_bounds = state.base.map_rect(bounds);
self.plan_routed(source, paint, ¤t, device_bounds, z);
}
}
Op::GlyphRun {
font,
size,
paint,
glyphs,
bounds,
slot,
} => {
if !self.culled(&state.base, bounds, 1) {
let z = self.slot_z(state, *slot);
let current = state.top().transform;
let source = DrawSource::Glyphs(GlyphRun {
font,
size: *size,
glyphs,
device_bounds: state.base.map_rect(bounds),
});
let device_bounds = state.base.map_rect(bounds);
self.plan_routed(source, paint, ¤t, device_bounds, z);
}
}
Op::ClipPath {
path,
fill_rule,
op,
expiry_slot,
..
} => {
let z = self.slot_z(state, *expiry_slot);
let current = state.top().transform;
self.plan_clip(path, *fill_rule, *op, ¤t, z);
}
Op::DrawDisplayList {
list,
bounds,
base_slot,
cache,
} => {
if !self.culled(&state.base, bounds, list.draw_count()) {
if *cache && !self.filling_raster {
self.embed_cached_list(list, *base_slot, state);
} else {
self.replay_embedded(list, *base_slot, state);
}
}
}
}
i += 1;
}
}
fn replay_embedded(&mut self, list: &Arc<DisplayList>, base_slot: u32, state: &ReplayState) {
let embed = state.top().transform;
let mut child = ReplayState::nested(embed, state.slot_offset + base_slot as i64);
self.replay_list(list, &mut child);
}
fn embed_cached_list(&mut self, list: &Arc<DisplayList>, base_slot: u32, state: &ReplayState) {
let embed = state.top().transform;
let [_, shear_b, shear_c, ..] = embed.to_affine();
if shear_b != 0.0 || shear_c != 0.0 || !embed.is_affine() {
return self.replay_embedded(list, base_slot, state);
}
let verdict = self
.emit
.raster_verdict(self.rasters, list, embed.max_scale());
match verdict {
RasterVerdict::Quad(source) => self.plan_raster_quad(&source, &embed, base_slot, state),
RasterVerdict::Fill(target) => {
let source = target.quad_source();
self.plan_one_raster_fill(list, target);
self.plan_raster_quad(&source, &embed, base_slot, state);
}
RasterVerdict::Inline => self.replay_embedded(list, base_slot, state),
}
}
fn plan_raster_quad(
&mut self,
source: &QuadSource,
embed: &Matrix,
base_slot: u32,
state: &ReplayState,
) {
self.stats.raster_quads += 1;
let mapped = embed.map_rect(&source.content_bounds);
let ratio = embed.max_scale() / source.content_scale.max(1e-6);
let exact = (ratio - 1.0).abs() < 1e-3;
let extent = if exact {
[source.size[0] as f32, source.size[1] as f32]
} else {
[source.size[0] as f32 * ratio, source.size[1] as f32 * ratio]
};
let dest = if exact {
Rect::new(mapped.x.round(), mapped.y.round(), extent[0], extent[1])
} else {
mapped
};
let z = self.slot_z(state, base_slot);
let frame = self.frames.last_mut().expect("frame stack never empty");
self.emit
.raster_quad_step(frame, &dest, extent, &source.view, z);
}
fn plan_one_raster_fill(&mut self, list: &Arc<DisplayList>, target: FillTarget) {
self.stats.raster_fills += 1;
self.filling_raster = true;
let base = Matrix::scale(target.content_scale, target.content_scale).then(
&Matrix::translation(-target.content_bounds.x, -target.content_bounds.y),
);
self.push_raster_frame(&target, (list.depth_slots() + 1) as f32);
let outer_elisions = std::mem::take(&mut self.elisions);
let mut state = ReplayState::nested(base, 0);
self.replay_list(list, &mut state);
self.close_raster_frame();
self.elisions = outer_elisions;
self.filling_raster = false;
}
fn culled(&mut self, base: &Matrix, bounds: &Rect, draws: u32) -> bool {
let visible = base.map_rect(bounds).intersects(&self.frame().cull_rect);
if !visible {
self.stats.culled += draws;
}
!visible
}
fn slot_z(&self, state: &ReplayState, slot: u32) -> f32 {
(state.slot_offset + slot as i64) as f32 / self.frame().z_denom
}
}
fn skip_scope(ops: &[Op], open_index: usize) -> usize {
let mut depth = 0usize;
let mut i = open_index;
loop {
match &ops[i] {
Op::Save | Op::SaveLayer { .. } => depth += 1,
Op::Restore => {
depth -= 1;
if depth == 0 {
return i;
}
}
_ => {}
}
i += 1;
}
}