use crate::filter::{FilterData, FilterLayerPlacement};
use crate::geometry::{RectU16, SizeU16};
use crate::mask::Mask;
use crate::peniko::BlendMode;
use crate::strip::Strip;
use crate::util::RectExt;
use alloc::vec::Vec;
use core::ops::Range;
use smallvec::SmallVec;
pub trait Drawable {
fn bbox(&self, strips: &[Strip]) -> Option<RectU16>;
}
#[derive(Debug)]
pub struct Node {
pub draws: Range<u32>,
pub layer: Option<u32>,
}
impl Node {
pub fn draws_in<'a, D>(&self, draws: &'a [D]) -> &'a [D] {
&draws[self.draws.start as usize..self.draws.end as usize]
}
}
#[derive(Debug)]
pub struct RecordedLayer {
pub props: LayerProps,
pub nodes: SmallVec<[Node; 2]>,
pub kind: RecordedLayerKind,
pub depth: usize,
pub bbox: RectU16,
}
#[derive(Debug)]
pub struct LayerProps {
pub blend_mode: BlendMode,
pub opacity: f32,
pub mask: Option<Mask>,
pub clip_path: Option<LayerClip>,
}
#[derive(Debug, Clone)]
pub struct LayerClip {
pub strip_range: Range<usize>,
pub thread_idx: u8,
pub bbox: RectU16,
}
#[derive(Debug)]
pub enum RecordedLayerKind {
Regular,
Filter {
filter_data: FilterData,
placement: FilterLayerPlacement,
},
}
impl RecordedLayer {
fn regular(props: LayerProps, depth: usize) -> Self {
Self {
props,
nodes: SmallVec::new(),
kind: RecordedLayerKind::Regular,
depth,
bbox: RectU16::ZERO,
}
}
fn filter(props: LayerProps, filter_plan: FilterData, depth: usize) -> Self {
Self {
props,
nodes: SmallVec::new(),
kind: RecordedLayerKind::Filter {
filter_data: filter_plan,
placement: FilterLayerPlacement::EMPTY,
},
depth,
bbox: RectU16::ZERO,
}
}
}
#[derive(Debug)]
pub struct CommandRecorder<D> {
pub scene_size: SizeU16,
pub nodes: Vec<Node>,
pub draws: Vec<D>,
pub layers: Vec<RecordedLayer>,
pub filter_layers: Vec<u32>,
pub root_is_blend_target: bool,
pub max_layer_depth: usize,
pub largest_layer_size: Option<SizeU16>,
pub largest_filter_layer_size: Option<SizeU16>,
pub has_non_default_blend: bool,
active_layer: Option<u32>,
layer_stack: Vec<OpenLayer>,
}
impl<D> Default for CommandRecorder<D> {
fn default() -> Self {
Self {
scene_size: SizeU16::ZERO,
nodes: Vec::new(),
draws: Vec::new(),
layers: Vec::new(),
filter_layers: Vec::new(),
root_is_blend_target: false,
max_layer_depth: 0,
largest_layer_size: None,
largest_filter_layer_size: None,
has_non_default_blend: false,
active_layer: None,
layer_stack: Vec::new(),
}
}
}
#[derive(Debug)]
struct OpenLayer {
id: u32,
bbox: RectU16,
parent_layer: Option<u32>,
}
impl<D> CommandRecorder<D> {
pub fn new(width: u16, height: u16) -> Self {
Self {
scene_size: snapped_scene_size(width, height),
..Self::default()
}
}
pub fn has_layers(&self) -> bool {
!self.layer_stack.is_empty()
}
#[inline]
pub fn reset(&mut self, width: u16, height: u16) {
self.scene_size = snapped_scene_size(width, height);
self.nodes.clear();
self.draws.clear();
self.layers.clear();
self.filter_layers.clear();
self.root_is_blend_target = false;
self.max_layer_depth = 0;
self.largest_layer_size = None;
self.largest_filter_layer_size = None;
self.has_non_default_blend = false;
self.active_layer = None;
self.layer_stack.clear();
}
#[inline]
pub fn push_layer(&mut self, props: LayerProps, filter_plan: Option<FilterData>) {
if let Some(filter_plan) = filter_plan {
self.push_filter_layer(props, filter_plan);
return;
}
self.push_regular_layer(props);
}
fn push_regular_layer(&mut self, props: LayerProps) {
let depth = self.layer_stack.len() + 1;
self.push_recorded_layer(RecordedLayer::regular(props, depth));
}
fn push_filter_layer(&mut self, props: LayerProps, filter_plan: FilterData) {
let depth = self.layer_stack.len() + 1;
let id = self.push_recorded_layer(RecordedLayer::filter(props, filter_plan, depth));
self.filter_layers.push(id);
}
fn push_recorded_layer(&mut self, layer: RecordedLayer) -> u32 {
let parent_layer = self.active_layer;
self.max_layer_depth = self.max_layer_depth.max(layer.depth);
if layer.props.blend_mode != BlendMode::default() {
self.has_non_default_blend = true;
if parent_layer.is_none() {
self.root_is_blend_target = true;
}
}
let id = self.push_layer_metadata(layer);
self.push_layer_node(id);
self.active_layer = Some(id);
self.layer_stack.push(OpenLayer {
id,
bbox: RectU16::INVERTED,
parent_layer,
});
id
}
pub fn pop_layer(&mut self) -> PoppedLayer {
let layer = self.layer_stack.pop().unwrap();
let id = layer.id;
let (popped_layer, bbox_in_parent) = {
let recorded_layer = &mut self.layers[id as usize];
match &mut recorded_layer.kind {
RecordedLayerKind::Regular => {
let mut bbox = layer.bbox;
if bbox.is_empty() {
bbox = RectU16::ZERO;
}
if let Some(clip_path) = &recorded_layer.props.clip_path {
bbox = bbox.intersect(clip_path.bbox);
}
recorded_layer.bbox = bbox;
let layer_size = bbox.into();
self.largest_layer_size = Some(
self.largest_layer_size
.map_or(layer_size, |current| current.max(layer_size)),
);
(PoppedLayer::Regular, bbox)
}
RecordedLayerKind::Filter {
filter_data: filter_plan,
placement,
} => {
*placement = FilterLayerPlacement::new(layer.bbox, filter_plan);
recorded_layer.bbox = placement.pixmap_bbox;
let filter_size = placement.pixmap_bbox.into();
self.largest_layer_size = Some(
self.largest_layer_size
.map_or(filter_size, |current| current.max(filter_size)),
);
self.largest_filter_layer_size = Some(
self.largest_filter_layer_size
.map_or(filter_size, |current| current.max(filter_size)),
);
(PoppedLayer::Filter, placement.dest_bbox)
}
}
};
self.active_layer = layer.parent_layer;
self.record_bbox(|| Some(bbox_in_parent));
popped_layer
}
#[inline]
fn active_node_mut(&mut self) -> Option<&mut Node> {
if let Some(id) = self.active_layer {
self.layers[id as usize].nodes.last_mut()
} else {
self.nodes.last_mut()
}
}
fn push_node(&mut self, node: Node) {
if let Some(id) = self.active_layer {
self.layers[id as usize].nodes.push(node);
} else {
self.nodes.push(node);
}
}
fn push_layer_node(&mut self, layer_id: u32) {
let draw_idx = self.draws.len() as u32;
match self.active_node_mut() {
Some(node) if node.layer.is_none() => {
node.layer = Some(layer_id);
}
_ => self.push_node(Node {
draws: draw_idx..draw_idx,
layer: Some(layer_id),
}),
}
}
fn push_layer_metadata(&mut self, layer: RecordedLayer) -> u32 {
let id = self.layers.len() as u32;
self.layers.push(layer);
id
}
fn record_bbox(&mut self, bbox: impl FnOnce() -> Option<RectU16>) {
let Some(layer) = self.layer_stack.last_mut() else {
return;
};
let Some(bbox) = bbox().and_then(|b| if b.is_empty() { None } else { Some(b) }) else {
return;
};
layer.bbox.union(bbox);
}
}
fn snapped_scene_size(width: u16, height: u16) -> SizeU16 {
RectU16::new(0, 0, width, height)
.snap_to_tile_coordinates()
.into()
}
impl<D: Drawable> CommandRecorder<D> {
#[inline]
pub fn push_draw(&mut self, draw: D, strips: &[Strip]) {
self.record_bbox(|| draw.bbox(strips));
let draw_idx = self.draws.len() as u32;
self.draws.push(draw);
match self.active_node_mut() {
Some(node) if node.layer.is_none() && node.draws.end == draw_idx => {
node.draws.end += 1;
}
_ => {
self.push_node(Node {
draws: draw_idx..draw_idx + 1,
layer: None,
});
}
};
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PoppedLayer {
Regular,
Filter,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::filter_effects::{Filter, FilterPrimitive};
use crate::geometry::PaddingU16;
use crate::kurbo::Affine;
use crate::peniko::Mix;
use crate::tile::Tile;
const DEFAULT_SIZE: u16 = 10;
#[derive(Debug)]
struct TestDraw;
impl Drawable for TestDraw {
fn bbox(&self, _strips: &[Strip]) -> Option<RectU16> {
Some(RectU16::new(0, 0, 64, 4))
}
}
#[derive(Debug)]
struct EmptyDraw;
impl Drawable for EmptyDraw {
fn bbox(&self, _strips: &[Strip]) -> Option<RectU16> {
None
}
}
fn layer_props() -> LayerProps {
LayerProps {
blend_mode: BlendMode::default(),
opacity: 1.0,
mask: None,
clip_path: None,
}
}
fn blended_layer_props() -> LayerProps {
LayerProps {
blend_mode: Mix::Multiply.into(),
..layer_props()
}
}
fn filter_data(filter_padding: PaddingU16, source_padding: PaddingU16) -> FilterData {
FilterData {
filter: Filter::from_primitive(FilterPrimitive::Offset { dx: 0.0, dy: 0.0 }),
transform: Affine::IDENTITY,
filter_padding,
source_padding,
}
}
fn assert_cmds(cmds: &[Node], expected: &[(Range<u32>, Option<u32>)]) {
assert_eq!(cmds.len(), expected.len());
for (cmd, (draws, layer)) in cmds.iter().zip(expected) {
assert_eq!(&cmd.draws, draws);
assert_eq!(cmd.layer, *layer);
}
}
fn layer_cmds(recorder: &CommandRecorder<TestDraw>, id: usize) -> &[Node] {
&recorder.layers[id].nodes
}
#[test]
fn scene_size_is_tile_aligned() {
let mut recorder = CommandRecorder::<TestDraw>::new(10, 10);
assert_eq!(recorder.scene_size, SizeU16::new(12));
recorder.reset(13, 7);
assert_eq!(recorder.scene_size, SizeU16::from_wh(16, 8));
recorder.reset(Tile::WIDTH * 5, Tile::HEIGHT * 3);
assert_eq!(
recorder.scene_size,
SizeU16::from_wh(Tile::WIDTH * 5, Tile::HEIGHT * 3)
);
}
#[test]
fn filter_placement_padding_expands_bbox() {
let placement = FilterLayerPlacement::new(
RectU16::new(8, 8, 16, 20),
&filter_data(PaddingU16::new(2, 4, 6, 8), PaddingU16::ZERO),
);
assert_eq!(placement.pixmap_bbox, RectU16::new(4, 4, 24, 28));
assert_eq!(placement.dest_bbox, RectU16::new(4, 4, 24, 28));
assert_eq!(placement.src_origin(), (0, 0));
}
#[test]
fn filter_placement_with_source_shift() {
let placement = FilterLayerPlacement::new(
RectU16::new(8, 12, 20, 24),
&filter_data(PaddingU16::new(6, 2, 4, 6), PaddingU16::new(10, 16, 0, 0)),
);
assert_eq!(placement.pixmap_bbox, RectU16::new(0, 8, 24, 32));
assert_eq!(placement.dest_bbox, RectU16::new(0, 0, 14, 16));
assert_eq!(placement.src_origin(), (10, 8));
}
#[test]
fn layer_behavior() {
let mut recorder = CommandRecorder::<TestDraw>::new(DEFAULT_SIZE, DEFAULT_SIZE);
recorder.push_layer(
layer_props(),
Some(filter_data(PaddingU16::ZERO, PaddingU16::ZERO)),
);
recorder.push_layer(
layer_props(),
Some(filter_data(PaddingU16::ZERO, PaddingU16::ZERO)),
);
recorder.push_layer(layer_props(), None);
recorder.push_draw(TestDraw, &[]);
assert_eq!(recorder.pop_layer(), PoppedLayer::Regular);
assert_eq!(recorder.pop_layer(), PoppedLayer::Filter);
recorder.push_layer(layer_props(), None);
recorder.push_draw(TestDraw, &[]);
assert_eq!(recorder.pop_layer(), PoppedLayer::Regular);
assert_eq!(recorder.pop_layer(), PoppedLayer::Filter);
assert_cmds(&recorder.nodes, &[(0..0, Some(0))]);
assert_cmds(
layer_cmds(&recorder, 0),
&[(0..0, Some(1)), (1..1, Some(3))],
);
assert_cmds(layer_cmds(&recorder, 1), &[(0..0, Some(2))]);
assert_cmds(layer_cmds(&recorder, 2), &[(0..1, None)]);
assert_cmds(layer_cmds(&recorder, 3), &[(1..2, None)]);
assert_eq!(recorder.draws.len(), 2);
assert_eq!(recorder.filter_layers, [0, 1]);
assert_eq!(
recorder
.layers
.iter()
.map(|layer| layer.depth)
.collect::<Vec<_>>(),
[1, 2, 3, 2]
);
assert_eq!(recorder.max_layer_depth, 3);
assert!(!recorder.has_non_default_blend);
assert_eq!(recorder.largest_layer_size, Some(SizeU16::from_wh(64, 4)));
assert_eq!(
recorder.largest_filter_layer_size,
Some(SizeU16::from_wh(64, 4))
);
}
#[test]
fn draw_batches_are_split_by_layers() {
let mut recorder = CommandRecorder::<TestDraw>::new(DEFAULT_SIZE, DEFAULT_SIZE);
recorder.push_draw(TestDraw, &[]);
recorder.push_draw(TestDraw, &[]);
recorder.push_layer(layer_props(), None);
recorder.push_draw(TestDraw, &[]);
recorder.pop_layer();
recorder.push_draw(TestDraw, &[]);
assert_cmds(&recorder.nodes, &[(0..2, Some(0)), (3..4, None)]);
assert_cmds(layer_cmds(&recorder, 0), &[(2..3, None)]);
}
#[test]
fn node_resolves_draw_range() {
let draws = [0, 1, 2, 3];
let node = Node {
draws: 1..3,
layer: None,
};
assert_eq!(node.draws_in(&draws), &[1, 2]);
}
#[test]
fn empty_draws_do_not_affect_layer_bounds() {
let mut recorder = CommandRecorder::<EmptyDraw>::new(DEFAULT_SIZE, DEFAULT_SIZE);
recorder.push_layer(layer_props(), None);
recorder.push_draw(EmptyDraw, &[]);
recorder.pop_layer();
assert!(recorder.layers[0].bbox.is_empty());
}
#[test]
fn disjoint_layer_bounds_are_empty_but_not_inverted() {
let mut recorder = CommandRecorder::<TestDraw>::new(DEFAULT_SIZE, DEFAULT_SIZE);
let mut props = layer_props();
props.clip_path = Some(LayerClip {
strip_range: 0..0,
thread_idx: 0,
bbox: RectU16::new(8, 8, 12, 12),
});
recorder.push_layer(props, None);
recorder.push_draw(TestDraw, &[]);
recorder.pop_layer();
assert_eq!(recorder.layers[0].bbox, RectU16::new(8, 8, 12, 8));
}
#[test]
fn blend_metadata_distinguishes_root_and_nested_targets() {
let mut recorder = CommandRecorder::<TestDraw>::new(DEFAULT_SIZE, DEFAULT_SIZE);
recorder.push_layer(layer_props(), None);
recorder.push_layer(blended_layer_props(), None);
assert!(!recorder.root_is_blend_target);
assert!(recorder.has_non_default_blend);
assert_eq!(recorder.max_layer_depth, 2);
recorder.pop_layer();
recorder.pop_layer();
recorder.push_layer(blended_layer_props(), None);
assert!(recorder.root_is_blend_target);
}
#[test]
fn reset_clears_all_metadata() {
let mut recorder = CommandRecorder::<TestDraw>::new(DEFAULT_SIZE, DEFAULT_SIZE);
recorder.push_layer(blended_layer_props(), None);
recorder.push_layer(
layer_props(),
Some(filter_data(PaddingU16::ZERO, PaddingU16::ZERO)),
);
recorder.push_draw(TestDraw, &[]);
recorder.pop_layer();
recorder.pop_layer();
assert!(recorder.root_is_blend_target);
assert!(recorder.has_non_default_blend);
assert_eq!(recorder.max_layer_depth, 2);
assert!(recorder.largest_layer_size.is_some());
assert!(recorder.largest_filter_layer_size.is_some());
recorder.reset(13, 7);
assert_eq!(recorder.scene_size, SizeU16::from_wh(16, 8));
assert!(!recorder.root_is_blend_target);
assert!(!recorder.has_non_default_blend);
assert_eq!(recorder.max_layer_depth, 0);
assert!(recorder.largest_layer_size.is_none());
assert!(recorder.largest_filter_layer_size.is_none());
assert!(recorder.filter_layers.is_empty());
}
}