From 5c85d3db98ce1a365d17ae46d8240af64189b23b Mon Sep 17 00:00:00 2001 From: "Dylan M. Taylor" Date: Tue, 3 Nov 2026 11:33:29 +0000 Subject: [PATCH] render: look up a table's style once, keep the skip list as a set, count each dash --- crates/render/src/placed.rs | 4 +++- crates/render/src/renderer.rs | 28 +++++++++++++++++++++++++++- crates/render/src/stroke.rs | 3 ++- crates/render/src/tests_wild.rs | 4 ++++ 4 files changed, 36 insertions(+), 3 deletions(-) diff --git a/crates/render/src/placed.rs b/crates/render/src/placed.rs index 4dc9e1082..2af8152be 100644 --- a/crates/render/src/placed.rs +++ b/crates/render/src/placed.rs @@ -131,9 +131,11 @@ fn place(out: &mut Placed, rctx: &Ctx, fields: &SlideFields, s: &Shape, parent: } ShapeKind::Table(t) => { // Cell text, laid out exactly as the table draws it. + // The table's style is looked up once, not once per cell. + let def = table::resolve_style(rctx, t); for (r, c, rect) in table::cell_rects(t, &table::row_heights(rctx, t, fields)) { let Some(cell) = t.cell(r, c).filter(|cell| !cell.text.is_empty()) else { continue }; - let body = table::cell_body(rctx, t, r, c, cell); + let body = table::cell_body_with_def(rctx, def, t, r, c, cell); let tmp = Shape::table_cell(body.clone()); place_text(out, rctx, fields, s.id, &tmp, &body, rect, m); } diff --git a/crates/render/src/renderer.rs b/crates/render/src/renderer.rs index c25f6bab6..f144b218a 100644 --- a/crates/render/src/renderer.rs +++ b/crates/render/src/renderer.rs @@ -33,6 +33,10 @@ pub struct Renderer { /// Visits spent on outer-shadow silhouette passes, kept apart so they cannot use up the budget /// of the normal drawing that follows. shadow_work: usize, + /// `RenderOpts::skip` as a set, with the slice (address, length) it was built from: the + /// slice can be as long as the slide, and is tested for every shape drawn. Forgotten when a + /// frame starts, so another frame's slice at the same address is never mistaken for it. + skip: Option<(usize, usize, std::collections::HashSet)>, } impl Default for Renderer { @@ -48,6 +52,7 @@ impl Default for Renderer { in_shadow_pass: false, render_work: 0, shadow_work: 0, + skip: None, } } } @@ -163,6 +168,7 @@ impl Renderer { pub(crate) fn slide(&mut self, pres: &Presentation, slide: &Slide, index: usize, opts: &RenderOpts) -> Image { self.render_work = 0; self.shadow_work = 0; + self.skip = None; self.in_shadow_pass = false; let (mut ctx, w, h, view) = Self::canvas(pres, opts, needs_filters(pres, slide)); let f = Frame { pres, opts, fields: Self::fields(pres, index, opts) }; @@ -178,6 +184,7 @@ impl Renderer { pub(crate) fn slide_part(&mut self, pres: &Presentation, slide: &Slide, index: usize, part: SlidePart, opts: &RenderOpts) -> Image { self.render_work = 0; self.shadow_work = 0; + self.skip = None; self.in_shadow_pass = false; let (mut ctx, w, h, view) = Self::canvas(pres, opts, needs_filters(pres, slide)); let f = Frame { pres, opts, fields: Self::fields(pres, index, opts) }; @@ -212,6 +219,7 @@ impl Renderer { let mix = if mix.is_finite() { mix.clamp(0.0, 1.0) } else { 1.0 }; self.render_work = 0; self.shadow_work = 0; + self.skip = None; self.in_shadow_pass = false; let (mut ctx, w, h, view) = Self::canvas(pres, opts, needs_filters(pres, old.0) || needs_filters(pres, new.0)); let (Some(co), Some(cn)) = (Ctx::for_slide(pres, old.0), Ctx::for_slide(pres, new.0)) else { return self.finish(ctx, w, h) }; @@ -260,6 +268,7 @@ impl Renderer { pub(crate) fn layout(&mut self, pres: &Presentation, mi: usize, li: Option, opts: &RenderOpts) -> Image { self.render_work = 0; self.shadow_work = 0; + self.skip = None; self.in_shadow_pass = false; let (w, h) = output_size(pres, opts); let mut ctx = RenderContext::new_with(w, h, vello_cpu::RenderSettings { num_threads: 0, ..Default::default() }); @@ -385,8 +394,25 @@ impl Renderer { geo } + /// Whether `id` is in `skip`, looked up in a set built once per frame and slice. + fn skipped(&mut self, skip: &[ShapeId], id: ShapeId) -> bool { + if skip.is_empty() { + return false; + } + let key = (skip.as_ptr() as usize, skip.len()); + match &self.skip { + Some((p, n, set)) if (*p, *n) == key => set.contains(&id), + _ => { + let set: std::collections::HashSet = skip.iter().copied().collect(); + let hit = set.contains(&id); + self.skip = Some((key.0, key.1, set)); + hit + } + } + } + fn shape_at(&mut self, ctx: &mut RenderContext, f: &Frame, rctx: &Ctx, s: &Shape, parent: Affine, depth: usize) { - if depth > 32 || (s.hidden && !f.opts.show_hidden) || f.opts.skip.contains(&s.id) { + if depth > 32 || (s.hidden && !f.opts.show_hidden) || self.skipped(f.opts.skip, s.id) { return; } let work = if self.in_shadow_pass { &mut self.shadow_work } else { &mut self.render_work }; diff --git a/crates/render/src/stroke.rs b/crates/render/src/stroke.rs index 8310cf0c9..fc1403f6b 100644 --- a/crates/render/src/stroke.rs +++ b/crates/render/src/stroke.rs @@ -191,7 +191,8 @@ pub(crate) fn limit_dash_count(path: &BezPath, pat: Vec) -> Vec { return pat; } let len: f64 = path.segments().map(|s| s.arclen(1e-2)).sum(); - let dashes = len / period; + // Every dash of the pattern counts, not each repeat of it: a custom pattern can hold many. + let dashes = len / period * pat.len().div_ceil(2) as f64; if !len.is_finite() { return vec![]; } diff --git a/crates/render/src/tests_wild.rs b/crates/render/src/tests_wild.rs index ae78f64b1..f4cfbbe40 100644 --- a/crates/render/src/tests_wild.rs +++ b/crates/render/src/tests_wild.rs @@ -798,4 +798,8 @@ fn a_tiny_dash_on_a_huge_shape_draws_without_running_out_of_memory() { p.line_to((4.0e6, 0.0)); let pat = limit_dash_count(&p, vec![1.0, 1.0]); assert!((4.0e6 / (pat[0] + pat[1]) - MAX_DASHES).abs() < 1.0, "{pat:?}"); + // A custom pattern of 1,000 dashes counts each of them: the limit is on dashes drawn. + let pat = limit_dash_count(&p, vec![1.0; 2000]); + let period: f64 = pat.iter().sum(); + assert!((4.0e6 / period * 1000.0 - MAX_DASHES).abs() < 1.0, "{}", period); }