use std::sync::Arc;
use stet_graphics::display_list::{DisplayElement, DisplayList};
fn rss() -> u64 {
let s = std::fs::read_to_string("/proc/self/statm").unwrap_or_default();
let pages: u64 = s
.split_whitespace()
.nth(1)
.unwrap_or("0")
.parse()
.unwrap_or(0);
pages * 4096
}
fn mb(bytes: u64) -> String {
format!("{:>9.1} MB", bytes as f64 / (1024.0 * 1024.0))
}
#[derive(Default, Clone)]
struct Tally {
scale: f64,
elements: usize,
images: usize,
image_bytes: u64,
image_pixels: u64,
fills: usize,
strokes: usize,
text: usize,
groups: usize,
clips: usize,
shadings: usize,
other: usize,
image_ptrs: std::collections::HashSet<usize>,
unique_image_bytes: std::collections::HashMap<usize, u64>,
patch_shadings: Vec<(usize, u64, u64)>,
}
impl Tally {
fn walk(&mut self, list: &DisplayList) {
for el in list.elements() {
self.elements += 1;
match el {
DisplayElement::Image {
sample_data,
params,
} => {
self.images += 1;
self.image_bytes += sample_data.len() as u64;
self.image_pixels += u64::from(params.width) * u64::from(params.height);
self.image_ptrs.insert(Arc::as_ptr(sample_data) as usize);
self.unique_image_bytes
.entry(Arc::as_ptr(sample_data) as usize)
.or_insert(sample_data.len() as u64);
}
DisplayElement::Fill { .. } => self.fills += 1,
DisplayElement::Stroke { .. } => self.strokes += 1,
DisplayElement::Text { .. } => self.text += 1,
DisplayElement::Clip { .. } => self.clips += 1,
DisplayElement::Group { elements, .. } => {
self.groups += 1;
self.walk(elements);
}
DisplayElement::SoftMasked { mask, content, .. } => {
self.groups += 1;
self.walk(content);
self.walk(mask);
}
DisplayElement::OcgGroup { elements, .. } => {
self.groups += 1;
self.walk(elements);
}
DisplayElement::PatchShading { params } => {
self.shadings += 1;
self.tally_patches(params);
}
DisplayElement::AxialShading { .. }
| DisplayElement::RadialShading { .. }
| DisplayElement::MeshShading { .. } => self.shadings += 1,
_ => self.other += 1,
}
}
}
fn tally_patches(&mut self, params: &stet_graphics::device::PatchShadingParams) {
let scale = self.scale;
let mut triangles = 0u64;
for patch in ¶ms.patches {
if patch.points.len() < 12 {
continue;
}
let (mut x0, mut y0) = (f64::INFINITY, f64::INFINITY);
let (mut x1, mut y1) = (f64::NEG_INFINITY, f64::NEG_INFINITY);
for &(px, py) in &patch.points {
let (dx, dy) = params.ctm.transform_point(px, py);
x0 = x0.min(dx);
y0 = y0.min(dy);
x1 = x1.max(dx);
y1 = y1.max(dy);
}
let extent = (x1 - x0).max(y1 - y0).abs() * scale;
let n = (extent / 2.0).ceil().clamp(8.0, 64.0) as u64;
triangles += 2 * n * n;
}
let bytes = triangles * size_of::<stet_graphics::device::ShadingTriangle>() as u64;
self.patch_shadings
.push((params.patches.len(), triangles, bytes));
}
fn report(&self) {
println!(" display list elements : {}", self.elements);
println!(
" images : {:>6} data {} ({:.1} Mpx)",
self.images,
mb(self.image_bytes),
self.image_pixels as f64 / 1e6
);
println!(" fills : {:>6}", self.fills);
println!(" strokes : {:>6}", self.strokes);
println!(" text runs : {:>6}", self.text);
println!(" clips : {:>6}", self.clips);
println!(" groups : {:>6}", self.groups);
println!(" shadings : {:>6}", self.shadings);
println!(" other : {:>6}", self.other);
let unique: u64 = self.unique_image_bytes.values().sum();
println!(
" distinct image bufs : {:>6} data {} (sharing saves {})",
self.image_ptrs.len(),
mb(unique),
mb(self.image_bytes.saturating_sub(unique))
);
if self.patch_shadings.is_empty() {
return;
}
let total: u64 = self.patch_shadings.iter().map(|r| r.2).sum();
let tris: u64 = self.patch_shadings.iter().map(|r| r.1).sum();
let patches: usize = self.patch_shadings.iter().map(|r| r.0).sum();
let threads = rayon::current_num_threads().max(1) as u64;
println!();
println!(
" patch shadings : {} elements, {} patches, {} triangles",
self.patch_shadings.len(),
patches,
tris
);
println!(
" triangulation : {} per build ({} bytes/triangle)",
mb(total),
size_of::<stet_graphics::device::ShadingTriangle>()
);
println!(
" x{} concurrent bands : {} <-- rebuilt per band, unculled",
threads,
mb(total.saturating_mul(threads))
);
let mut rows = self.patch_shadings.clone();
rows.sort_by_key(|r| std::cmp::Reverse(r.2));
for (p, t, b) in rows.iter().take(5) {
println!(" {p:>6} patches {t:>10} tris {}", mb(*b));
}
}
}
fn main() {
let mut args = std::env::args().skip(1);
let path = args
.next()
.expect("usage: profile_images FILE [PAGE] [DPI]");
let page: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(0);
let dpi: f64 = args.next().and_then(|s| s.parse().ok()).unwrap_or(72.0);
let base = rss();
println!("{:<26} {}", "start", mb(base));
let data = std::fs::read(&path).expect("read input");
println!(
"{:<26} {} (file is {})",
"after read",
mb(rss()),
mb(data.len() as u64)
);
let doc = stet_pdf_reader::PdfDocument::from_bytes(&data).expect("parse");
let after_parse = rss();
println!("{:<26} {}", "after from_bytes", mb(after_parse));
let t0 = std::time::Instant::now();
let list = doc.render_page(page, dpi).expect("build display list");
let after_list = rss();
println!(
"{:<26} {} (+{}, {:.1}s)",
"after render_page",
mb(after_list),
mb(after_list.saturating_sub(after_parse)),
t0.elapsed().as_secs_f64()
);
let mut tally = Tally {
scale: dpi / 72.0,
..Default::default()
};
tally.walk(&list);
tally.report();
let t1 = std::time::Instant::now();
let scale = dpi / 72.0;
let (page_w, page_h) = doc.page_size(page).expect("page size");
let w = (page_w * scale).round().max(1.0) as u32;
let h = (page_h * scale).round().max(1.0) as u32;
let rgba = stet_render::render_to_rgba(&list, w, h, dpi, Some(doc.icc_cache()), false);
let after_raster = rss();
println!(
"{:<26} {} (+{}, {:.1}s)",
"after rasterize",
mb(after_raster),
mb(after_raster.saturating_sub(after_list)),
t1.elapsed().as_secs_f64()
);
println!(" canvas {}x{} = {}", w, h, mb((w as u64) * (h as u64) * 4));
std::hint::black_box(&rgba);
}