[demo] Show detailed memory usage statistics of paint lists

This commit is contained in:
Emil Ernerfeldt 2020-10-17 10:57:25 +02:00
parent 570860ec5f
commit 4fc34cca45
6 changed files with 244 additions and 42 deletions

View file

@ -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<T>(usize, std::marker::PhantomData<T>);
#[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));
}
}

View file

@ -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));

View file

@ -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")

View file

@ -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,
};

224
egui/src/paint/stats.rs Normal file
View file

@ -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<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 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<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 {
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)
}

View file

@ -87,6 +87,12 @@ impl Triangles {
}
}
pub fn bytes_used(&self) -> usize {
std::mem::size_of::<Self>()
+ self.vertices.len() * std::mem::size_of::<Vertex>()
+ self.indices.len() * std::mem::size_of::<u32>()
}
/// Are all indices within the bounds of the contained vertices?
pub fn is_valid(&self) -> bool {
let n = self.vertices.len() as u32;