epaint/
stats.rs

1//! Collect statistics about what is being painted.
2
3use crate::{ClippedShape, Galley, Mesh, Primitive, Shape};
4
5/// Size of the elements in a vector/array.
6#[derive(Clone, Copy, PartialEq)]
7enum ElementSize {
8    Unknown,
9    Homogeneous(usize),
10    Heterogenous,
11}
12
13impl Default for ElementSize {
14    fn default() -> Self {
15        Self::Unknown
16    }
17}
18
19/// Aggregate information about a bunch of allocations.
20#[derive(Clone, Copy, Default, PartialEq)]
21pub struct AllocInfo {
22    element_size: ElementSize,
23    num_allocs: usize,
24    num_elements: usize,
25    num_bytes: usize,
26}
27
28impl<T> From<&[T]> for AllocInfo {
29    fn from(slice: &[T]) -> Self {
30        Self::from_slice(slice)
31    }
32}
33
34impl std::ops::Add for AllocInfo {
35    type Output = Self;
36
37    fn add(self, rhs: Self) -> Self {
38        use ElementSize::{Heterogenous, Homogeneous, Unknown};
39        let element_size = match (self.element_size, rhs.element_size) {
40            (Heterogenous, _) | (_, Heterogenous) => Heterogenous,
41            (Unknown, other) | (other, Unknown) => other,
42            (Homogeneous(lhs), Homogeneous(rhs)) if lhs == rhs => Homogeneous(lhs),
43            _ => Heterogenous,
44        };
45
46        Self {
47            element_size,
48            num_allocs: self.num_allocs + rhs.num_allocs,
49            num_elements: self.num_elements + rhs.num_elements,
50            num_bytes: self.num_bytes + rhs.num_bytes,
51        }
52    }
53}
54
55impl std::ops::AddAssign for AllocInfo {
56    fn add_assign(&mut self, rhs: Self) {
57        *self = *self + rhs;
58    }
59}
60
61impl std::iter::Sum for AllocInfo {
62    fn sum<I>(iter: I) -> Self
63    where
64        I: Iterator<Item = Self>,
65    {
66        let mut sum = Self::default();
67        for value in iter {
68            sum += value;
69        }
70        sum
71    }
72}
73
74impl AllocInfo {
75    // pub fn from_shape(shape: &Shape) -> Self {
76    //     match shape {
77    //         Shape::Noop
78    //         Shape::Vec(shapes) => Self::from_shapes(shapes)
79    //         | Shape::Circle { .. }
80    //         | Shape::LineSegment { .. }
81    //         | Shape::Rect { .. } => Self::default(),
82    //         Shape::Path { points, .. } => Self::from_slice(points),
83    //         Shape::Text { galley, .. } => Self::from_galley(galley),
84    //         Shape::Mesh(mesh) => Self::from_mesh(mesh),
85    //     }
86    // }
87
88    pub fn from_galley(galley: &Galley) -> Self {
89        Self::from_slice(galley.text().as_bytes())
90            + Self::from_slice(&galley.rows)
91            + galley.rows.iter().map(Self::from_galley_row).sum()
92    }
93
94    fn from_galley_row(row: &crate::text::PlacedRow) -> Self {
95        Self::from_mesh(&row.visuals.mesh) + Self::from_slice(&row.glyphs)
96    }
97
98    pub fn from_mesh(mesh: &Mesh) -> Self {
99        Self::from_slice(&mesh.indices) + Self::from_slice(&mesh.vertices)
100    }
101
102    pub fn from_slice<T>(slice: &[T]) -> Self {
103        use std::mem::size_of;
104        let element_size = size_of::<T>();
105        Self {
106            element_size: ElementSize::Homogeneous(element_size),
107            num_allocs: 1,
108            num_elements: slice.len(),
109            num_bytes: std::mem::size_of_val(slice),
110        }
111    }
112
113    pub fn num_elements(&self) -> usize {
114        assert!(
115            self.element_size != ElementSize::Heterogenous,
116            "Heterogenous element size"
117        );
118        self.num_elements
119    }
120
121    pub fn num_allocs(&self) -> usize {
122        self.num_allocs
123    }
124
125    pub fn num_bytes(&self) -> usize {
126        self.num_bytes
127    }
128
129    pub fn megabytes(&self) -> String {
130        megabytes(self.num_bytes())
131    }
132
133    pub fn format(&self, what: &str) -> String {
134        if self.num_allocs() == 0 {
135            format!("{:6} {:16}", 0, what)
136        } else if self.num_allocs() == 1 {
137            format!(
138                "{:6} {:16}  {}       1 allocation",
139                self.num_elements,
140                what,
141                self.megabytes()
142            )
143        } else if self.element_size != ElementSize::Heterogenous {
144            format!(
145                "{:6} {:16}  {}     {:3} allocations",
146                self.num_elements(),
147                what,
148                self.megabytes(),
149                self.num_allocs()
150            )
151        } else {
152            format!(
153                "{:6} {:16}  {}     {:3} allocations",
154                "",
155                what,
156                self.megabytes(),
157                self.num_allocs()
158            )
159        }
160    }
161}
162
163/// Collected allocation statistics for shapes and meshes.
164#[derive(Clone, Copy, Default)]
165pub struct PaintStats {
166    pub shapes: AllocInfo,
167    pub shape_text: AllocInfo,
168    pub shape_path: AllocInfo,
169    pub shape_mesh: AllocInfo,
170    pub shape_vec: AllocInfo,
171    pub num_callbacks: usize,
172
173    pub text_shape_vertices: AllocInfo,
174    pub text_shape_indices: AllocInfo,
175
176    /// Number of separate clip rectangles
177    pub clipped_primitives: AllocInfo,
178    pub vertices: AllocInfo,
179    pub indices: AllocInfo,
180}
181
182impl PaintStats {
183    pub fn from_shapes(shapes: &[ClippedShape]) -> Self {
184        let mut stats = Self::default();
185        stats.shape_path.element_size = ElementSize::Heterogenous; // nicer display later
186        stats.shape_vec.element_size = ElementSize::Heterogenous; // nicer display later
187
188        stats.shapes = AllocInfo::from_slice(shapes);
189        for ClippedShape { shape, .. } in shapes {
190            stats.add(shape);
191        }
192        stats
193    }
194
195    fn add(&mut self, shape: &Shape) {
196        match shape {
197            Shape::Vec(shapes) => {
198                // self += PaintStats::from_shapes(&shapes); // TODO(emilk)
199                self.shapes += AllocInfo::from_slice(shapes);
200                self.shape_vec += AllocInfo::from_slice(shapes);
201                for shape in shapes {
202                    self.add(shape);
203                }
204            }
205            Shape::Noop
206            | Shape::Circle { .. }
207            | Shape::Ellipse { .. }
208            | Shape::LineSegment { .. }
209            | Shape::Rect { .. }
210            | Shape::CubicBezier(_)
211            | Shape::QuadraticBezier(_) => {}
212            Shape::Path(path_shape) => {
213                self.shape_path += AllocInfo::from_slice(&path_shape.points);
214            }
215            Shape::Text(text_shape) => {
216                self.shape_text += AllocInfo::from_galley(&text_shape.galley);
217
218                for row in &text_shape.galley.rows {
219                    self.text_shape_indices += AllocInfo::from_slice(&row.visuals.mesh.indices);
220                    self.text_shape_vertices += AllocInfo::from_slice(&row.visuals.mesh.vertices);
221                }
222            }
223            Shape::Mesh(mesh) => {
224                self.shape_mesh += AllocInfo::from_mesh(mesh);
225            }
226            Shape::Callback(_) => {
227                self.num_callbacks += 1;
228            }
229        }
230    }
231
232    pub fn with_clipped_primitives(
233        mut self,
234        clipped_primitives: &[crate::ClippedPrimitive],
235    ) -> Self {
236        self.clipped_primitives += AllocInfo::from_slice(clipped_primitives);
237        for clipped_primitive in clipped_primitives {
238            if let Primitive::Mesh(mesh) = &clipped_primitive.primitive {
239                self.vertices += AllocInfo::from_slice(&mesh.vertices);
240                self.indices += AllocInfo::from_slice(&mesh.indices);
241            }
242        }
243        self
244    }
245}
246
247fn megabytes(size: usize) -> String {
248    format!("{:.2} MB", size as f64 / 1e6)
249}