use std::sync::Arc;
use rustc_hash::FxHashMap;
use valo_dl::{DisplayList, MaskBlur, Op, Paint};
use valo_geometry::{Matrix, Rect};
use super::backdrop::SharedBackdrop;
use super::draw_state::DrawState;
use super::layers::{LayerComposite, Opened, ResolvedLayer};
use super::source::{DrawSource, GlyphRun, Shape};
use super::Planner;
pub(super) struct ReplayState {
scopes: Vec<ScopeEntry>,
base: Matrix,
shared_backdrops: FxHashMap<u64, SharedBackdrop>,
filling_raster: bool,
}
struct ScopeEntry {
transform: Matrix,
slot_offset: u32,
group_alpha: f32,
on_restore: RestoreAction,
}
pub(super) enum RestoreAction {
None,
CloseLayer(LayerComposite),
}
impl ReplayState {
pub fn root() -> Self {
Self::rooted_at(Matrix::IDENTITY, 0, 1.0, false)
}
pub fn embedded(&self, base_slot: u32) -> Self {
let top = self.top();
Self::rooted_at(
top.transform,
top.slot_offset + base_slot,
top.group_alpha,
self.filling_raster,
)
}
pub fn raster_fill(base: Matrix) -> Self {
Self::rooted_at(base, 0, 1.0, true)
}
fn rooted_at(base: Matrix, slot_offset: u32, group_alpha: f32, filling_raster: bool) -> Self {
Self {
scopes: vec![ScopeEntry {
transform: base,
slot_offset,
group_alpha,
on_restore: RestoreAction::None,
}],
base,
shared_backdrops: FxHashMap::default(),
filling_raster,
}
}
fn top(&self) -> &ScopeEntry {
self.scopes.last().expect("builder balances scopes")
}
pub fn base(&self) -> &Matrix {
&self.base
}
pub fn device_bounds(&self, bounds: &Rect) -> Rect {
self.base.map_rect(bounds)
}
pub fn absolute(&self, slot: u32) -> u32 {
self.top().slot_offset + slot
}
fn transform(&mut self, local: &Matrix) {
let top = self.scopes.last_mut().expect("builder balances scopes");
top.transform = top.transform.then(local);
}
fn save(&mut self) {
let top = self.top();
self.push_scope(top.slot_offset, top.group_alpha, RestoreAction::None);
}
fn save_elided(&mut self, alpha: f32) {
let top = self.top();
self.push_scope(
top.slot_offset,
top.group_alpha * alpha,
RestoreAction::None,
);
}
fn save_layer(&mut self, composite: LayerComposite) {
let top = self.top();
let on_restore = RestoreAction::CloseLayer(composite);
self.push_scope(top.slot_offset, 1.0, on_restore);
}
fn restore(&mut self) -> RestoreAction {
let entry = self.scopes.pop().expect("builder balances scopes");
entry.on_restore
}
fn push_scope(&mut self, slot_offset: u32, group_alpha: f32, on_restore: RestoreAction) {
self.scopes.push(ScopeEntry {
transform: self.top().transform,
slot_offset,
group_alpha,
on_restore,
});
}
}
impl Planner<'_> {
pub(super) fn replay_list(&mut self, list: &DisplayList, state: &mut ReplayState) {
let mut ops = OpCursor::new(list.ops());
while let Some(op) = ops.next() {
self.plan.stats.ops += 1;
match op {
Op::Save => state.save(),
Op::Transform(local) => state.transform(local),
Op::SaveLayer { composite_slot, .. } => {
let at = self.draw_state(state, *composite_slot);
let layer = ResolvedLayer::of(op, list, state, at);
match self.open_layer(layer, &mut state.shared_backdrops) {
Opened::Skip => ops.skip_scope(),
Opened::Elided(alpha) => state.save_elided(alpha),
Opened::Layer(composite) => state.save_layer(composite),
}
}
Op::Restore => {
if let RestoreAction::CloseLayer(composite) = state.restore() {
self.close_layer(composite);
}
}
Op::ClipPath {
path,
fill_rule,
op: clip_op,
expiry_slot,
} => {
let at = self.draw_state(state, *expiry_slot);
self.drawing().clip(path, *fill_rule, *clip_op, &at);
}
Op::DrawDisplayList {
list,
bounds,
base_slot,
cache,
} => self.replay_embed(list, bounds, *base_slot, *cache, state),
Op::DrawRect { .. }
| Op::DrawPath { .. }
| Op::DrawImage { .. }
| Op::RRectBlur { .. }
| Op::GlyphRun { .. } => self.replay_draw(RecordedDraw::of(op, state), state),
}
}
}
fn replay_draw(&mut self, draw: RecordedDraw<'_>, state: &ReplayState) {
if self.contexts.top().area.culls(&draw.bounds) {
self.plan.stats.culled += 1;
return;
}
let at = self.at_slot(state, draw.slot);
self.plan_routed(draw.source, draw.paint, &at);
}
fn replay_embed(
&mut self,
list: &Arc<DisplayList>,
bounds: &Rect,
base_slot: u32,
cache: bool,
state: &ReplayState,
) {
if self.contexts.top().area.culls(&state.device_bounds(bounds)) {
self.plan.stats.culled += list.draw_count();
} else if cache && !state.filling_raster {
self.embed_cached_list(list, base_slot, state);
} else {
self.replay_embedded(list, base_slot, state);
}
}
pub(super) fn replay_embedded(
&mut self,
list: &Arc<DisplayList>,
base_slot: u32,
state: &ReplayState,
) {
self.replay_list(list, &mut state.embedded(base_slot));
}
pub(super) fn draw_state(&self, state: &ReplayState, slot: u32) -> DrawState {
self.at_slot(state, state.absolute(slot))
}
fn at_slot(&self, state: &ReplayState, slot: u32) -> DrawState {
let top = state.top();
DrawState {
transform: top.transform,
z: self.contexts.top().depth.z(slot),
alpha: top.group_alpha,
}
}
}
struct RecordedDraw<'a> {
source: DrawSource<'a>,
paint: &'a Paint,
bounds: Rect,
slot: u32,
}
impl<'a> RecordedDraw<'a> {
fn of(op: &'a Op, state: &ReplayState) -> Self {
let (source, paint, bounds, slot) = match op {
Op::DrawRect {
rect,
paint,
bounds,
slot,
} => {
let source = DrawSource::Shape {
shape: Shape::Rect(*rect),
ink: *rect,
};
(source, paint, state.device_bounds(bounds), slot)
}
Op::DrawPath {
path,
fill_rule,
paint,
content_bounds,
bounds,
slot,
} => {
let source = DrawSource::Shape {
shape: Shape::of_path(path, *fill_rule, &paint.style),
ink: *content_bounds,
};
(source, paint, state.device_bounds(bounds), slot)
}
Op::DrawImage {
image,
src,
dst,
sampling,
paint,
bounds,
slot,
} => {
let source = DrawSource::Image {
image,
src: *src,
dst: *dst,
sampling: *sampling,
};
(source, paint, state.device_bounds(bounds), slot)
}
Op::RRectBlur {
rect,
radii,
paint,
bounds,
slot,
} => {
let source = DrawSource::RRectBlur {
rect: *rect,
radii: *radii,
blur: paint.mask_blur.unwrap_or(MaskBlur::new(0.0)),
};
(source, paint, state.device_bounds(bounds), slot)
}
Op::GlyphRun {
font,
size,
synthesis,
paint,
glyphs,
content_bounds,
bounds,
slot,
..
} => {
let device_bounds = state.device_bounds(bounds);
let source = DrawSource::Glyphs(GlyphRun {
font,
size: *size,
synthesis: *synthesis,
glyphs,
content_bounds: *content_bounds,
device_bounds,
colour_filter: None,
});
(source, paint, device_bounds, slot)
}
other => unreachable!("replay_list hands over draw ops only, not {other:?}"),
};
Self {
source,
paint,
bounds,
slot: state.absolute(*slot),
}
}
}
struct OpCursor<'a> {
ops: &'a [Op],
next: usize,
}
impl<'a> OpCursor<'a> {
fn new(ops: &'a [Op]) -> Self {
Self { ops, next: 0 }
}
fn next(&mut self) -> Option<&'a Op> {
let op = self.ops.get(self.next)?;
self.next += 1;
Some(op)
}
fn skip_scope(&mut self) {
let mut depth = 1usize;
while depth > 0 {
match &self.ops[self.next] {
Op::Save | Op::SaveLayer { .. } => depth += 1,
Op::Restore => depth -= 1,
_ => {}
}
self.next += 1;
}
}
}