egui/crates/epaint/src/stats.rs

246 lines
7.1 KiB
Rust
Raw Normal View History

2021-01-17 09:52:01 +00:00
//! Collect statistics about what is being painted.
use crate::*;
2021-01-17 09:52:01 +00:00
/// Size of the elements in a vector/array.
#[derive(Clone, Copy, PartialEq)]
enum ElementSize {
Unknown,
Homogeneous(usize),
Heterogenous,
}
impl Default for ElementSize {
fn default() -> Self {
Self::Unknown
}
}
2021-01-17 09:52:01 +00:00
/// Aggregate information about a bunch of allocations.
#[derive(Clone, Copy, Default, PartialEq)]
pub struct AllocInfo {
element_size: ElementSize,
num_allocs: usize,
num_elements: usize,
num_bytes: usize,
}
impl<T> From<&[T]> for AllocInfo {
fn from(slice: &[T]) -> Self {
Self::from_slice(slice)
}
}
impl std::ops::Add for AllocInfo {
type Output = AllocInfo;
fn add(self, rhs: AllocInfo) -> AllocInfo {
use ElementSize::{Heterogenous, Homogeneous, Unknown};
let element_size = match (self.element_size, rhs.element_size) {
(Heterogenous, _) | (_, Heterogenous) => Heterogenous,
(Unknown, other) | (other, Unknown) => other,
(Homogeneous(lhs), Homogeneous(rhs)) if lhs == rhs => Homogeneous(lhs),
_ => Heterogenous,
};
AllocInfo {
element_size,
num_allocs: self.num_allocs + rhs.num_allocs,
num_elements: self.num_elements + rhs.num_elements,
num_bytes: self.num_bytes + rhs.num_bytes,
}
}
}
impl std::ops::AddAssign for AllocInfo {
fn add_assign(&mut self, rhs: AllocInfo) {
*self = *self + rhs;
}
}
impl std::iter::Sum for AllocInfo {
fn sum<I>(iter: I) -> Self
where
I: Iterator<Item = Self>,
{
let mut sum = Self::default();
for value in iter {
sum += value;
}
sum
}
}
impl AllocInfo {
2021-01-10 10:43:01 +00:00
// pub fn from_shape(shape: &Shape) -> Self {
// match shape {
// Shape::Noop
// Shape::Vec(shapes) => Self::from_shapes(shapes)
// | Shape::Circle { .. }
// | Shape::LineSegment { .. }
// | Shape::Rect { .. } => Self::default(),
// Shape::Path { points, .. } => Self::from_slice(points),
// Shape::Text { galley, .. } => Self::from_galley(galley),
2021-01-25 20:23:24 +00:00
// Shape::Mesh(mesh) => Self::from_mesh(mesh),
2020-12-29 00:13:14 +00:00
// }
// }
pub fn from_galley(galley: &Galley) -> Self {
New text layout (#682) This PR introduces a completely rewritten text layout engine which is simpler and more powerful. It allows mixing different text styles (heading, body, etc) and formats (color, underlining, strikethrough, …) in the same layout pass, and baked into the same `Galley`. This opens up the door to having a syntax-highlighed code editor, or a WYSIWYG markdown editor. One major change is the color is now baked in at layout time. However, many widgets changes text color on hovered. But we need to do the text layout before we know if it is hovered. Therefor the painter has an option to override the text color of a galley. ## Performance Text layout alone is about 20% slower, but a lot of that is because more tessellation is done upfront. Text tessellation is now a lot faster, but text layout + tessellation still lands at a net loss of 5-10% in performance. There are however a few tricks to speed it up (like using `smallvec`) which I am saving for later. Text layout is also cached, meaning that in most cases (when all text isn't changing each frame) text tessellation is actually more important (and that's more than 2x faster!). Sadly, the actual text cache lookup is significantly slower (300ns -> 600ns). That's because the `TextLayoutJob` is a lot bigger (it has more options, like underlining, fonts etc), so it is slower to hash and compare. I have an idea how to speed this up, but I need to do some other work before I can implement that. All in all, the performance impact on `demo_with_tesselate__realistic` is about 5-6% in the red. Not great; not terrible. The benefits are worth it, but I also think with some work I can get that down significantly, hopefully down to the old levels.
2021-09-03 16:18:00 +00:00
Self::from_slice(galley.text().as_bytes())
+ Self::from_slice(&galley.rows)
+ galley.rows.iter().map(Self::from_galley_row).sum()
}
fn from_galley_row(row: &crate::text::Row) -> Self {
New text layout (#682) This PR introduces a completely rewritten text layout engine which is simpler and more powerful. It allows mixing different text styles (heading, body, etc) and formats (color, underlining, strikethrough, …) in the same layout pass, and baked into the same `Galley`. This opens up the door to having a syntax-highlighed code editor, or a WYSIWYG markdown editor. One major change is the color is now baked in at layout time. However, many widgets changes text color on hovered. But we need to do the text layout before we know if it is hovered. Therefor the painter has an option to override the text color of a galley. ## Performance Text layout alone is about 20% slower, but a lot of that is because more tessellation is done upfront. Text tessellation is now a lot faster, but text layout + tessellation still lands at a net loss of 5-10% in performance. There are however a few tricks to speed it up (like using `smallvec`) which I am saving for later. Text layout is also cached, meaning that in most cases (when all text isn't changing each frame) text tessellation is actually more important (and that's more than 2x faster!). Sadly, the actual text cache lookup is significantly slower (300ns -> 600ns). That's because the `TextLayoutJob` is a lot bigger (it has more options, like underlining, fonts etc), so it is slower to hash and compare. I have an idea how to speed this up, but I need to do some other work before I can implement that. All in all, the performance impact on `demo_with_tesselate__realistic` is about 5-6% in the red. Not great; not terrible. The benefits are worth it, but I also think with some work I can get that down significantly, hopefully down to the old levels.
2021-09-03 16:18:00 +00:00
Self::from_mesh(&row.visuals.mesh) + Self::from_slice(&row.glyphs)
}
2021-01-25 20:23:24 +00:00
pub fn from_mesh(mesh: &Mesh) -> Self {
Self::from_slice(&mesh.indices) + Self::from_slice(&mesh.vertices)
}
pub fn from_slice<T>(slice: &[T]) -> Self {
use std::mem::size_of;
let element_size = size_of::<T>();
Self {
element_size: ElementSize::Homogeneous(element_size),
num_allocs: 1,
num_elements: slice.len(),
num_bytes: slice.len() * element_size,
}
}
pub fn num_elements(&self) -> usize {
assert!(self.element_size != ElementSize::Heterogenous);
self.num_elements
}
pub fn num_allocs(&self) -> usize {
self.num_allocs
}
pub fn num_bytes(&self) -> usize {
self.num_bytes
}
pub fn megabytes(&self) -> String {
megabytes(self.num_bytes())
}
pub fn format(&self, what: &str) -> String {
if self.num_allocs() == 0 {
2022-03-20 22:05:16 +00:00
format!("{:6} {:16}", 0, what)
} else if self.num_allocs() == 1 {
format!(
2022-03-20 22:05:16 +00:00
"{:6} {:16} {} 1 allocation",
self.num_elements,
what,
self.megabytes()
)
} else if self.element_size != ElementSize::Heterogenous {
format!(
2022-03-20 22:05:16 +00:00
"{:6} {:16} {} {:3} allocations",
self.num_elements(),
what,
self.megabytes(),
self.num_allocs()
)
} else {
format!(
2022-03-20 22:05:16 +00:00
"{:6} {:16} {} {:3} allocations",
"",
what,
self.megabytes(),
self.num_allocs()
)
}
}
}
2021-01-17 09:52:01 +00:00
/// Collected allocation statistics for shapes and meshes.
#[derive(Clone, Copy, Default)]
pub struct PaintStats {
pub shapes: AllocInfo,
pub shape_text: AllocInfo,
pub shape_path: AllocInfo,
pub shape_mesh: AllocInfo,
pub shape_vec: AllocInfo,
pub num_callbacks: usize,
pub text_shape_vertices: AllocInfo,
pub text_shape_indices: AllocInfo,
/// Number of separate clip rectangles
pub clipped_primitives: AllocInfo,
pub vertices: AllocInfo,
pub indices: AllocInfo,
}
impl PaintStats {
pub fn from_shapes(shapes: &[ClippedShape]) -> Self {
let mut stats = Self::default();
2021-01-10 10:43:01 +00:00
stats.shape_path.element_size = ElementSize::Heterogenous; // nicer display later
stats.shape_vec.element_size = ElementSize::Heterogenous; // nicer display later
2021-01-10 10:43:01 +00:00
stats.shapes = AllocInfo::from_slice(shapes);
for ClippedShape(_, shape) in shapes {
2021-01-10 10:43:01 +00:00
stats.add(shape);
2020-12-29 00:13:14 +00:00
}
stats
}
2021-01-10 10:43:01 +00:00
fn add(&mut self, shape: &Shape) {
match shape {
Shape::Vec(shapes) => {
// self += PaintStats::from_shapes(&shapes); // TODO
self.shapes += AllocInfo::from_slice(shapes);
self.shape_vec += AllocInfo::from_slice(shapes);
for shape in shapes {
self.add(shape);
}
}
2022-01-31 19:26:31 +00:00
Shape::Noop
| Shape::Circle { .. }
| Shape::LineSegment { .. }
| Shape::Rect { .. }
| Shape::CubicBezier(_)
| Shape::QuadraticBezier(_) => {}
Shape::Path(path_shape) => {
self.shape_path += AllocInfo::from_slice(&path_shape.points);
2020-12-29 00:13:14 +00:00
}
Shape::Text(text_shape) => {
self.shape_text += AllocInfo::from_galley(&text_shape.galley);
for row in &text_shape.galley.rows {
self.text_shape_indices += AllocInfo::from_slice(&row.visuals.mesh.indices);
self.text_shape_vertices += AllocInfo::from_slice(&row.visuals.mesh.vertices);
}
2021-01-10 10:43:01 +00:00
}
2021-01-25 20:23:24 +00:00
Shape::Mesh(mesh) => {
self.shape_mesh += AllocInfo::from_mesh(mesh);
2020-12-29 00:13:14 +00:00
}
Shape::Callback(_) => {
self.num_callbacks += 1;
}
}
}
pub fn with_clipped_primitives(
mut self,
clipped_primitives: &[crate::ClippedPrimitive],
) -> Self {
self.clipped_primitives += AllocInfo::from_slice(clipped_primitives);
for clipped_primitive in clipped_primitives {
if let Primitive::Mesh(mesh) = &clipped_primitive.primitive {
self.vertices += AllocInfo::from_slice(&mesh.vertices);
self.indices += AllocInfo::from_slice(&mesh.indices);
}
}
self
}
}
fn megabytes(size: usize) -> String {
format!("{:.2} MB", size as f64 / 1e6)
}