194 lines
7.2 KiB
Rust
194 lines
7.2 KiB
Rust
//! Count the draw calls the Makepad PDF renderer would issue for every page
|
|
//! in the corpus, and what a batched strategy would issue instead.
|
|
//!
|
|
//! This replays the *exact* state machine in `pdf-makepad/src/renderer.rs`:
|
|
//! - `render()` opens with `begin()`.
|
|
//! - `finish_path()` calls `DrawVector::end()` (a real draw call) whenever
|
|
//! `has_path` is set, and is invoked on Save, Restore, PushClip, PopClip
|
|
//! and Clip*.
|
|
//! - Stroke / Fill* end the accumulation and `begin()` a fresh one, but do
|
|
//! NOT call `end()` themselves.
|
|
//! - Text and images go through DrawText / DrawImage, which are separate
|
|
//! draw objects and so break the vector batch in submission order.
|
|
|
|
use nigig_pdf_graphics::content::parse_content_stream;
|
|
use nigig_pdf_graphics::recording::{RecordingDevice, RenderCommand};
|
|
use std::path::{Path, PathBuf};
|
|
|
|
#[derive(Default, Debug, Clone)]
|
|
struct Census {
|
|
commands: usize,
|
|
vector_end_calls: usize,
|
|
text_draws: usize,
|
|
image_draws: usize,
|
|
saves: usize,
|
|
restores: usize,
|
|
clips: usize,
|
|
paint_ops: usize,
|
|
// Counterfactual: flush only when a clip changes, since colour and CTM
|
|
// are baked into vertices on the CPU and need no draw-call boundary.
|
|
batched_vector_ends: usize,
|
|
// Text runs that are contiguous (no intervening vector paint), which a
|
|
// single DrawText batch could absorb.
|
|
text_batches: usize,
|
|
}
|
|
|
|
fn census(commands: &[RenderCommand]) -> Census {
|
|
let mut c = Census::default();
|
|
c.commands = commands.len();
|
|
|
|
// Mirrors PdfRenderer::render: begin() then has_path = true.
|
|
let mut has_path = true;
|
|
let mut batched_open = true;
|
|
let mut last_was_text = false;
|
|
|
|
let finish_path = |has_path: &mut bool, c: &mut Census| {
|
|
if *has_path {
|
|
c.vector_end_calls += 1;
|
|
*has_path = false;
|
|
}
|
|
};
|
|
|
|
for cmd in commands {
|
|
match cmd {
|
|
RenderCommand::Save => {
|
|
finish_path(&mut has_path, &mut c);
|
|
c.saves += 1;
|
|
}
|
|
RenderCommand::Restore => {
|
|
finish_path(&mut has_path, &mut c);
|
|
c.restores += 1;
|
|
}
|
|
RenderCommand::PushClip | RenderCommand::PopClip => {
|
|
finish_path(&mut has_path, &mut c);
|
|
c.clips += 1;
|
|
if batched_open { c.batched_vector_ends += 1; batched_open = false; }
|
|
}
|
|
RenderCommand::ClipEvenOdd | RenderCommand::ClipWinding => {
|
|
finish_path(&mut has_path, &mut c);
|
|
c.clips += 1;
|
|
if batched_open { c.batched_vector_ends += 1; batched_open = false; }
|
|
}
|
|
RenderCommand::MoveTo(..)
|
|
| RenderCommand::LineTo(..)
|
|
| RenderCommand::CurveTo(..)
|
|
| RenderCommand::Rectangle(..) => {
|
|
has_path = true;
|
|
}
|
|
RenderCommand::Stroke
|
|
| RenderCommand::FillEvenOdd
|
|
| RenderCommand::FillWinding
|
|
| RenderCommand::FillStrokeEvenOdd
|
|
| RenderCommand::FillStrokeWinding => {
|
|
// tessellates into the accumulator; begins a fresh one
|
|
c.paint_ops += 1;
|
|
has_path = true;
|
|
batched_open = true;
|
|
last_was_text = false;
|
|
}
|
|
RenderCommand::ShowText(..) | RenderCommand::ShowTextWithMetrics { .. } => {
|
|
c.text_draws += 1;
|
|
if !last_was_text { c.text_batches += 1; }
|
|
last_was_text = true;
|
|
}
|
|
RenderCommand::DrawImage { .. } | RenderCommand::DrawInlineImage { .. } => {
|
|
c.image_draws += 1;
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
// final finish_path() at the end of render()
|
|
finish_path(&mut has_path, &mut c);
|
|
if batched_open { c.batched_vector_ends += 1; }
|
|
c
|
|
}
|
|
|
|
fn collect_pdfs(dir: &Path, out: &mut Vec<PathBuf>) {
|
|
let Ok(rd) = std::fs::read_dir(dir) else {
|
|
return;
|
|
};
|
|
for e in rd.flatten() {
|
|
let p = e.path();
|
|
if p.is_dir() {
|
|
collect_pdfs(&p, out);
|
|
} else if p.extension().and_then(|s| s.to_str()) == Some("pdf") {
|
|
out.push(p);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn main() {
|
|
let root = std::env::args()
|
|
.nth(1)
|
|
.unwrap_or_else(|| "tests/corpus".to_string());
|
|
let mut pdfs = Vec::new();
|
|
collect_pdfs(Path::new(&root), &mut pdfs);
|
|
pdfs.sort();
|
|
|
|
let mut total = Census::default();
|
|
let mut pages = 0usize;
|
|
let mut worst: Vec<(usize, String)> = Vec::new();
|
|
|
|
for path in &pdfs {
|
|
let Ok(bytes) = std::fs::read(path) else {
|
|
continue;
|
|
};
|
|
let Ok(mut doc) = nigig_pdf_document::PdfDocument::parse(&bytes) else {
|
|
continue;
|
|
};
|
|
for i in 0..doc.page_count() {
|
|
let Ok(page) = doc.page(i) else { continue };
|
|
let content = page.content_data.clone();
|
|
let Ok(ops) = parse_content_stream(&content) else {
|
|
continue;
|
|
};
|
|
let cmds = RecordingDevice::from_ops(&ops);
|
|
let c = census(&cmds);
|
|
if c.commands == 0 {
|
|
continue;
|
|
}
|
|
pages += 1;
|
|
total.commands += c.commands;
|
|
total.vector_end_calls += c.vector_end_calls;
|
|
total.text_draws += c.text_draws;
|
|
total.image_draws += c.image_draws;
|
|
total.saves += c.saves;
|
|
total.restores += c.restores;
|
|
total.clips += c.clips;
|
|
total.paint_ops += c.paint_ops;
|
|
total.batched_vector_ends += c.batched_vector_ends;
|
|
total.text_batches += c.text_batches;
|
|
|
|
let dc = c.vector_end_calls + c.text_draws + c.image_draws;
|
|
worst.push((dc, format!("{}#{}", path.display(), i)));
|
|
}
|
|
}
|
|
|
|
worst.sort_by(|a, b| b.0.cmp(&a.0));
|
|
|
|
let dc = total.vector_end_calls + total.text_draws + total.image_draws;
|
|
println!("pages measured : {pages}");
|
|
println!("render commands : {}", total.commands);
|
|
println!("--- draw calls, as the renderer stands ---");
|
|
println!("DrawVector::end() calls : {}", total.vector_end_calls);
|
|
println!("DrawText::draw_abs() : {}", total.text_draws);
|
|
println!("DrawImage::draw_abs() : {}", total.image_draws);
|
|
println!("TOTAL draw calls : {dc}");
|
|
println!(" per page (mean) : {:.1}", dc as f64 / pages as f64);
|
|
println!("--- what forced the breaks ---");
|
|
println!("Save : {}", total.saves);
|
|
println!("Restore : {}", total.restores);
|
|
println!("clip ops : {}", total.clips);
|
|
println!("fill/stroke paint ops : {}", total.paint_ops);
|
|
let batched = total.batched_vector_ends + total.text_batches + total.image_draws;
|
|
println!("--- counterfactual: flush only on clip change ---");
|
|
println!("DrawVector::end() calls : {}", total.batched_vector_ends);
|
|
println!("DrawText batches : {}", total.text_batches);
|
|
println!("TOTAL draw calls : {batched}");
|
|
println!(" per page (mean) : {:.1}", batched as f64 / pages as f64);
|
|
println!(" reduction : {:.1}x", dc as f64 / batched as f64);
|
|
println!("--- worst pages ---");
|
|
for (n, name) in worst.iter().take(10) {
|
|
println!("{n:>7} {name}");
|
|
}
|
|
}
|