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