From 4fc34cca450c545f1d982efc0b6a672bac933752 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 17 Oct 2020 10:57:25 +0200 Subject: [PATCH] [demo] Show detailed memory usage statistics of paint lists --- egui/src/context.rs | 45 +------ egui/src/demos/demo_window.rs | 2 +- egui/src/input.rs | 5 + egui/src/paint/mod.rs | 4 +- egui/src/paint/stats.rs | 224 ++++++++++++++++++++++++++++++++++ egui/src/paint/tessellator.rs | 6 + 6 files changed, 244 insertions(+), 42 deletions(-) create mode 100644 egui/src/paint/stats.rs diff --git a/egui/src/context.rs b/egui/src/context.rs index 921ec60b..e98ea981 100644 --- a/egui/src/context.rs +++ b/egui/src/context.rs @@ -8,17 +8,12 @@ use ahash::AHashMap; use crate::{ animation_manager::AnimationManager, mutex::{Mutex, MutexGuard}, - paint::*, + paint::{stats::*, *}, *, }; #[derive(Clone, Copy, Default)] -struct PaintStats { - num_jobs: usize, - num_primitives: usize, - num_vertices: usize, - num_triangles: usize, -} +struct SliceStats(usize, std::marker::PhantomData); #[derive(Clone, Debug, Default)] struct Options { @@ -225,20 +220,10 @@ impl Context { let mut paint_options = self.options.lock().paint_options; paint_options.aa_size = 1.0 / self.pixels_per_point(); let paint_commands = self.drain_paint_lists(); - let num_primitives = paint_commands.len(); + let paint_stats = PaintStats::from_paint_commands(&paint_commands); // TODO: internal allocations let paint_jobs = tessellator::tessellate_paint_commands(paint_commands, paint_options, self.fonts()); - - { - let mut stats = PaintStats::default(); - stats.num_jobs = paint_jobs.len(); - stats.num_primitives = num_primitives; - for (_, triangles) in &paint_jobs { - stats.num_vertices += triangles.vertices.len(); - stats.num_triangles += triangles.indices.len() / 3; - } - *self.paint_stats.lock() = stats; - } + *self.paint_stats.lock() = paint_stats.with_paint_jobs(&paint_jobs); paint_jobs } @@ -542,21 +527,12 @@ impl Context { pub fn inspection_ui(&self, ui: &mut Ui) { use crate::containers::*; - ui.style_mut().body_text_style = TextStyle::Monospace; CollapsingHeader::new("Input") .default_open(true) .show(ui, |ui| ui.input().clone().ui(ui)); - ui.collapsing("Stats", |ui| { - ui.label(format!( - "Screen size: {} x {} points, pixels_per_point: {:?}", - ui.input().screen_size.x, - ui.input().screen_size.y, - ui.input().pixels_per_point, - )); - - ui.heading("Painting:"); + ui.collapsing("Paint stats", |ui| { self.paint_stats.lock().ui(ui); }); } @@ -642,14 +618,3 @@ impl paint::PaintOptions { ui.checkbox(debug_ignore_clip_rects, "Ignore clip rectangles (debug)"); } } - -impl PaintStats { - pub fn ui(&self, ui: &mut Ui) { - ui.label(format!("Jobs: {}", self.num_jobs)) - .on_hover_text("Number of separate clip rectangles"); - ui.label(format!("Primitives: {}", self.num_primitives)) - .on_hover_text("Boxes, circles, text areas etc"); - ui.label(format!("Vertices: {}", self.num_vertices)); - ui.label(format!("Triangles: {}", self.num_triangles)); - } -} diff --git a/egui/src/demos/demo_window.rs b/egui/src/demos/demo_window.rs index 35ad9c9b..ec1c4cb0 100644 --- a/egui/src/demos/demo_window.rs +++ b/egui/src/demos/demo_window.rs @@ -84,7 +84,7 @@ impl DemoWindow { }); }); - CollapsingHeader::new("Painting") + CollapsingHeader::new("Paint with your mouse") .default_open(false) .show(ui, |ui| self.painting.ui(ui)); diff --git a/egui/src/input.rs b/egui/src/input.rs index e1a49f8e..b4ddadaa 100644 --- a/egui/src/input.rs +++ b/egui/src/input.rs @@ -208,6 +208,10 @@ impl InputState { } } + pub fn screen_rect(&self) -> Rect { + Rect::from_min_size(pos2(0.0, 0.0), self.screen_size) + } + pub fn wants_repaint(&self) -> bool { self.mouse.pressed || self.mouse.released @@ -372,6 +376,7 @@ impl InputState { events, } = self; + ui.style_mut().body_text_style = crate::paint::TextStyle::Monospace; ui.collapsing("Raw Input", |ui| raw.ui(ui)); crate::containers::CollapsingHeader::new("mouse") diff --git a/egui/src/paint/mod.rs b/egui/src/paint/mod.rs index 38343531..32eaab09 100644 --- a/egui/src/paint/mod.rs +++ b/egui/src/paint/mod.rs @@ -6,6 +6,7 @@ pub mod color; pub mod command; pub mod font; pub mod fonts; +pub mod stats; pub mod tessellator; mod texture_atlas; @@ -13,6 +14,7 @@ pub use { color::{Rgba, Srgba}, command::{PaintCmd, Stroke}, fonts::{FontDefinitions, Fonts, TextStyle}, - tessellator::{PaintJobs, PaintOptions, TextureId, Triangles, Vertex, WHITE_UV}, + stats::PaintStats, + tessellator::{PaintJob, PaintJobs, PaintOptions, TextureId, Triangles, Vertex, WHITE_UV}, texture_atlas::Texture, }; diff --git a/egui/src/paint/stats.rs b/egui/src/paint/stats.rs new file mode 100644 index 00000000..f6fdbfdd --- /dev/null +++ b/egui/src/paint/stats.rs @@ -0,0 +1,224 @@ +use crate::{paint::*, Rect}; + +#[derive(Clone, Copy, PartialEq)] +enum ElementSize { + Unknown, + Homogeneous(usize), + Heterogenous, +} + +impl Default for ElementSize { + fn default() -> Self { + Self::Unknown + } +} + +#[derive(Clone, Copy, Default, PartialEq)] +pub struct AllocInfo { + element_size: ElementSize, + num_allocs: usize, + num_elements: usize, + num_bytes: usize, +} + +impl 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 AllocInfo { + pub fn from_paint_cmd(cmd: &PaintCmd) -> Self { + match cmd { + PaintCmd::Noop + | PaintCmd::Circle { .. } + | PaintCmd::LineSegment { .. } + | PaintCmd::Rect { .. } => Self::default(), + PaintCmd::Path { points, .. } => Self::from_slice(points), + PaintCmd::Text { galley, .. } => Self::from_galley(galley), + PaintCmd::Triangles(triangles) => Self::from_triangles(triangles), + } + } + + pub fn from_galley(galley: &font::Galley) -> Self { + Self::from_slice(galley.text.as_bytes()) + Self::from_slice(&galley.lines) + } + + pub fn from_triangles(triangles: &Triangles) -> Self { + Self::from_slice(&triangles.indices) + Self::from_slice(&triangles.vertices) + } + + pub fn from_slice(slice: &[T]) -> Self { + use std::mem::size_of; + let element_size = size_of::(); + 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 { + format!("{:6} {:12}", 0, what) + } else if self.num_allocs() == 1 { + format!( + "{:6} {:12} {} 1 allocation", + self.num_elements, + what, + self.megabytes() + ) + } else if self.element_size != ElementSize::Heterogenous { + format!( + "{:6} {:12} {} {:3} allocations", + self.num_elements(), + what, + self.megabytes(), + self.num_allocs() + ) + } else { + format!( + "{:6} {:12} {} {:3} allocations", + "", + what, + self.megabytes(), + self.num_allocs() + ) + } + } +} + +#[derive(Clone, Copy, Default)] +pub struct PaintStats { + primitives: AllocInfo, + cmd_text: AllocInfo, + cmd_path: AllocInfo, + cmd_mesh: AllocInfo, + + /// Number of separate clip rectangles + jobs: AllocInfo, + vertices: AllocInfo, + indices: AllocInfo, +} + +impl PaintStats { + pub fn from_paint_commands(paint_commands: &[(Rect, PaintCmd)]) -> Self { + let mut stats = Self::default(); + stats.cmd_path.element_size = ElementSize::Heterogenous; // nicer display later + + stats.primitives = AllocInfo::from_slice(paint_commands); + for (_, cmd) in paint_commands { + match cmd { + PaintCmd::Noop + | PaintCmd::Circle { .. } + | PaintCmd::LineSegment { .. } + | PaintCmd::Rect { .. } => Default::default(), + PaintCmd::Path { points, .. } => { + stats.cmd_path += AllocInfo::from_slice(points); + } + PaintCmd::Text { galley, .. } => { + stats.cmd_text += AllocInfo::from_galley(galley); + } + PaintCmd::Triangles(triangles) => { + stats.cmd_mesh += AllocInfo::from_triangles(triangles); + } + } + } + stats + } + + pub fn with_paint_jobs(mut self, paint_jobs: &[crate::paint::PaintJob]) -> Self { + self.jobs += AllocInfo::from_slice(paint_jobs); + for (_, indices) in paint_jobs { + self.vertices += AllocInfo::from_slice(&indices.vertices); + self.indices += AllocInfo::from_slice(&indices.indices); + } + self + } + + // pub fn total(&self) -> AllocInfo { + // self.primitives + // + self.cmd_text + // + self.cmd_path + // + self.cmd_mesh + // + self.jobs + // + self.vertices + // + self.indices + // } +} + +impl PaintStats { + pub fn ui(&self, ui: &mut crate::Ui) { + ui.label( + "Egui generates intermediate level primitives like circles and text. \ + These are later tessellated into triangles.", + ); + ui.advance_cursor(10.0); + + ui.style_mut().body_text_style = TextStyle::Monospace; + ui.label("Intermediate:"); + ui.label(self.primitives.format("primitives")) + .on_hover_text("Boxes, circles, etc"); + ui.label(self.cmd_text.format("text")); + ui.label(self.cmd_path.format("paths")); + ui.label(self.cmd_mesh.format("meshes")); + ui.advance_cursor(10.0); + + ui.label("Tessellated:"); + ui.label(self.jobs.format("jobs")) + .on_hover_text("Number of separate clip rectangles"); + ui.label(self.vertices.format("vertices")); + ui.label(self.indices.format("indices")) + .on_hover_text("Three 32-bit indices per triangles"); + ui.advance_cursor(10.0); + + // ui.label("Total:"); + // ui.label(self.total().format("")); + } +} + +fn megabytes(size: usize) -> String { + format!("{:.2} MB", size as f64 / 1e6) +} diff --git a/egui/src/paint/tessellator.rs b/egui/src/paint/tessellator.rs index c940d827..d47f2a19 100644 --- a/egui/src/paint/tessellator.rs +++ b/egui/src/paint/tessellator.rs @@ -87,6 +87,12 @@ impl Triangles { } } + pub fn bytes_used(&self) -> usize { + std::mem::size_of::() + + self.vertices.len() * std::mem::size_of::() + + self.indices.len() * std::mem::size_of::() + } + /// Are all indices within the bounds of the contained vertices? pub fn is_valid(&self) -> bool { let n = self.vertices.len() as u32;