ailloli_ui_render_wgpu/
plan.rs

1//! Lightweight per-layer draw statistics (bench / debug).
2
3use ailloli_ui_runtime::DrawCmd;
4
5use crate::clip::ClipRenderMode;
6use crate::renderer::LayerPass;
7
8/// Command counts and clip mode for one render layer.
9///
10/// All counts are exact `usize` counts from the layer's command slice. Boolean
11/// fields report whether the corresponding clip facility participates.
12///
13/// # Examples
14///
15/// ```
16/// use ailloli_ui_render_wgpu::{ClipRenderMode, LayerPlan};
17/// let plan = LayerPlan {
18///     rects: 1, rrects: 0, borders: 0, shadows: 0, ring_progresses: 0,
19///     polylines: 0, texts: 0, images: 0, has_clip: false,
20///     has_scissor: false, rounded_masks: 0, has_window_root_clip: false,
21///     clip_mode: ClipRenderMode::Scissor,
22/// };
23/// assert_eq!(plan.rects, 1);
24/// ```
25#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize)]
26pub struct LayerPlan {
27    /// Rectangle command count.
28    pub rects: usize,
29    /// Rounded-rectangle command count.
30    pub rrects: usize,
31    /// Rounded-border command count.
32    pub borders: usize,
33    /// Box-shadow command count.
34    pub shadows: usize,
35    /// Ring-progress command count.
36    pub ring_progresses: usize,
37    /// Polyline command count.
38    pub polylines: usize,
39    /// Text command count.
40    pub texts: usize,
41    /// Image command count.
42    pub images: usize,
43    /// Whether the logical clip stack is nonempty.
44    pub has_clip: bool,
45    /// Whether the resolved plan has a rectangular scissor.
46    pub has_scissor: bool,
47    /// Number of rounded masks in the resolved clip plan.
48    pub rounded_masks: usize,
49    /// Whether a clip entry is marked as the window-root clip.
50    pub has_window_root_clip: bool,
51    /// Selected GPU clip implementation.
52    pub clip_mode: ClipRenderMode,
53}
54
55/// Aggregated plan for all layers in a frame.
56///
57/// # Examples
58///
59/// ```
60/// use ailloli_ui_render_wgpu::RenderPlan;
61/// let plan = RenderPlan { layers: Vec::new() };
62/// assert!(plan.layers.is_empty());
63/// ```
64#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
65pub struct RenderPlan {
66    /// Layer summaries in input order.
67    pub layers: Vec<LayerPlan>,
68}
69
70/// Builds a [`RenderPlan`] from the layer passes about to be rendered.
71///
72/// # Examples
73///
74/// ```
75/// use ailloli_ui_render_wgpu::{build_render_plan, LayerPass};
76/// let commands = [];
77/// let layers = [LayerPass::new(&commands)];
78/// let plan = build_render_plan(&layers);
79/// assert_eq!(plan.layers.len(), 1);
80/// assert_eq!(plan.layers[0].rects, 0);
81/// ```
82pub fn build_render_plan(layers: &[LayerPass<'_>]) -> RenderPlan {
83    let mut out = Vec::with_capacity(layers.len());
84    for l in layers {
85        let mut rects = 0;
86        let mut rrects = 0;
87        let mut borders = 0;
88        let mut shadows = 0;
89        let mut ring_progresses = 0;
90        let mut polylines = 0;
91        let mut texts = 0;
92        let mut images = 0;
93        for cmd in l.cmds {
94            match cmd {
95                DrawCmd::Rect(_) => rects += 1,
96                DrawCmd::RRect(_) => rrects += 1,
97                DrawCmd::Border(_) => borders += 1,
98                DrawCmd::BoxShadow(_) => shadows += 1,
99                DrawCmd::RingProgress(_) => ring_progresses += 1,
100                DrawCmd::Polyline(_) => polylines += 1,
101                DrawCmd::Text(_) => texts += 1,
102                DrawCmd::Image(_) => images += 1,
103            }
104        }
105        out.push(LayerPlan {
106            rects,
107            rrects,
108            borders,
109            shadows,
110            ring_progresses,
111            polylines,
112            texts,
113            images,
114            has_clip: !l.clip.is_empty(),
115            has_scissor: l.clip_plan.scissor.is_some(),
116            rounded_masks: l.clip_plan.rounded_masks.len(),
117            has_window_root_clip: l.clip.entries().iter().any(|entry| entry.is_window_root),
118            clip_mode: l.clip_plan.clip_mode,
119        });
120    }
121    RenderPlan { layers: out }
122}
123
124#[cfg(test)]
125/// Verifies per-layer statistics for renderer-specific primitive commands.
126mod tests {
127    use super::*;
128    use ailloli_ui_core::{BoxShadow, Color, Point, Radius, Rect, StrokeStyle};
129    use ailloli_ui_runtime::{DrawBoxShadow, DrawCmd, DrawPolyline, DrawRingProgress};
130
131    #[test]
132    fn render_plan_counts_box_shadows() {
133        let cmds = vec![DrawCmd::BoxShadow(DrawBoxShadow {
134            rect: Rect::new(0.0, 0.0, 10.0, 10.0),
135            radius: Radius::uniform(4.0),
136            shadow: BoxShadow::new(0.0, 2.0, 4.0, 0.0, Color::BLACK),
137        })];
138        let layers = vec![LayerPass::new(&cmds)];
139
140        let plan = build_render_plan(&layers);
141        assert_eq!(plan.layers[0].shadows, 1);
142        assert_eq!(plan.layers[0].ring_progresses, 0);
143        assert_eq!(plan.layers[0].polylines, 0);
144        assert_eq!(plan.layers[0].rects, 0);
145    }
146
147    #[test]
148    fn render_plan_counts_ring_progresses() {
149        let cmds = vec![DrawCmd::RingProgress(DrawRingProgress {
150            rect: Rect::new(0.0, 0.0, 32.0, 32.0),
151            thickness: 4.0,
152            fraction: 0.66,
153            track_color: Color::BLACK,
154            fill_color: Color::WHITE,
155            start_angle: -std::f32::consts::FRAC_PI_2,
156        })];
157        let layers = vec![LayerPass::new(&cmds)];
158
159        let plan = build_render_plan(&layers);
160        assert_eq!(plan.layers[0].ring_progresses, 1);
161        assert_eq!(plan.layers[0].shadows, 0);
162        assert_eq!(plan.layers[0].polylines, 0);
163        assert_eq!(plan.layers[0].rects, 0);
164    }
165
166    #[test]
167    fn render_plan_counts_polylines() {
168        let cmds = vec![DrawCmd::Polyline(DrawPolyline {
169            points: vec![Point::new(0.0, 0.0), Point::new(10.0, 10.0)],
170            stroke: StrokeStyle::new(2.0, Color::WHITE),
171        })];
172        let layers = vec![LayerPass::new(&cmds)];
173
174        let plan = build_render_plan(&layers);
175        assert_eq!(plan.layers[0].polylines, 1);
176        assert_eq!(plan.layers[0].ring_progresses, 0);
177        assert_eq!(plan.layers[0].rects, 0);
178    }
179}