Skip to main content

sequoia_draw/
cache.rs

1use std::cell::RefCell;
2use std::collections::{HashMap, HashSet};
3use std::rc::Rc;
4
5use sequoia_core::{Point, Rect, Vec2};
6
7use crate::flatten::Contour;
8use crate::path::{FillRule, Path};
9use crate::raster::{self, FillStyle, RasterOptions};
10use crate::stroke::{self, StrokeStyle};
11use crate::{Span, union_rect};
12
13#[derive(Clone, Debug, PartialEq)]
14pub struct Shape {
15    pub spans: Rc<[Span]>,
16
17    pub bounds: Rect,
18}
19
20impl Shape {
21    pub fn new(spans: Vec<Span>) -> Self {
22        let mut bounds = Rect::ZERO;
23        for span in &spans {
24            bounds = union_rect(bounds, span.rect);
25        }
26        Self {
27            spans: spans.into(),
28            bounds,
29        }
30    }
31
32    pub fn empty() -> Self {
33        Self {
34            spans: Rc::from(Vec::new()),
35            bounds: Rect::ZERO,
36        }
37    }
38
39    pub fn len(&self) -> usize {
40        self.spans.len()
41    }
42
43    pub fn is_empty(&self) -> bool {
44        self.spans.is_empty()
45    }
46
47    pub fn iter(&self) -> std::slice::Iter<'_, Span> {
48        self.spans.iter()
49    }
50}
51
52impl<'a> IntoIterator for &'a Shape {
53    type Item = &'a Span;
54    type IntoIter = std::slice::Iter<'a, Span>;
55
56    fn into_iter(self) -> Self::IntoIter {
57        self.spans.iter()
58    }
59}
60
61#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
62pub struct ShapeStats {
63    pub hits: u64,
64
65    pub misses: u64,
66
67    pub evictions: u64,
68}
69
70impl ShapeStats {
71    pub fn hit_rate(&self) -> f32 {
72        let total = self.hits + self.misses;
73        if total == 0 {
74            0.0
75        } else {
76            self.hits as f32 / total as f32
77        }
78    }
79}
80
81#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
82struct Key {
83    kind: u8,
84    fingerprint: u64,
85    style: u64,
86    opts: u64,
87    offset: u64,
88}
89
90#[derive(Debug)]
91struct Entry {
92    shape: Rc<Shape>,
93    used: u64,
94}
95
96/// Owned description of a cache miss, captured so it can be tessellated after
97/// the paint pass that asked for it has returned.
98#[derive(Debug)]
99enum Request {
100    Fill {
101        path: Path,
102        rule: FillRule,
103        opts: RasterOptions,
104        offset: Vec2,
105    },
106    Stroke {
107        path: Path,
108        style: StrokeStyle,
109        opts: RasterOptions,
110        offset: Vec2,
111    },
112    Contours {
113        contours: Vec<Contour>,
114        rule: FillRule,
115        opts: RasterOptions,
116    },
117}
118
119impl Request {
120    fn opts(&self) -> RasterOptions {
121        match self {
122            Request::Fill { opts, .. }
123            | Request::Stroke { opts, .. }
124            | Request::Contours { opts, .. } => *opts,
125        }
126    }
127
128    /// Rough work estimate: source vertices, not raster cells. Used only to
129    /// decide whether a batch is worth spawning threads for.
130    fn cost(&self) -> usize {
131        match self {
132            Request::Fill { path, .. } | Request::Stroke { path, .. } => path.segs().len(),
133            Request::Contours { contours, .. } => contours.iter().map(|c| c.points.len()).sum(),
134        }
135    }
136
137    fn build(&self, opts: RasterOptions, out: &mut Vec<Span>) {
138        match self {
139            Request::Fill {
140                path, rule, offset, ..
141            } => {
142                if *offset == Vec2::ZERO {
143                    raster::fill(path, *rule, opts, out);
144                } else {
145                    raster::fill(&path.translated(offset.x, offset.y), *rule, opts, out);
146                }
147            }
148            Request::Stroke {
149                path,
150                style,
151                offset,
152                ..
153            } => {
154                if *offset == Vec2::ZERO {
155                    stroke::stroke_path(path, style, opts, out);
156                } else {
157                    stroke::stroke_path(&path.translated(offset.x, offset.y), style, opts, out);
158                }
159            }
160            Request::Contours { contours, rule, .. } => {
161                raster::fill_contours(contours, *rule, opts, out);
162            }
163        }
164    }
165}
166
167/// Below this many shapes a batch stays on the calling thread: thread startup
168/// costs more than a few small rasters.
169const BATCH_MIN_SHAPES: usize = 4;
170
171/// Below this many source vertices a batch stays on the calling thread, even
172/// when it has enough shapes.
173const BATCH_MIN_COST: usize = 256;
174
175const BATCH_MAX_WORKERS: usize = 8;
176
177pub const DEFAULT_CAPACITY: usize = 256;
178
179/// Upper bound for the default cache. A scene whose *live* working set is
180/// bigger than [`DEFAULT_CAPACITY`] grows up to this size instead of
181/// re-tessellating the same outlines every frame (see [`ShapeCache::growing`]).
182pub(crate) const MAX_CAPACITY: usize = 2048;
183
184#[derive(Debug)]
185pub struct ShapeCache {
186    inner: RefCell<Inner>,
187}
188
189#[derive(Debug)]
190struct Inner {
191    entries: HashMap<Key, Entry>,
192    capacity: usize,
193    max_capacity: usize,
194    tick: u64,
195    stats: ShapeStats,
196    prefetching: bool,
197    pending: Vec<(Key, Request)>,
198    pending_keys: HashSet<Key>,
199}
200
201impl Default for ShapeCache {
202    fn default() -> Self {
203        Self::growing(DEFAULT_CAPACITY, MAX_CAPACITY)
204    }
205}
206
207impl ShapeCache {
208    pub fn new(capacity: usize) -> Self {
209        Self::growing(capacity, capacity)
210    }
211
212    /// A cache that may grow from `capacity` up to `max_capacity` once all of
213    /// its entries are live, i.e. every one was touched within the last
214    /// `capacity` lookups. Evicting an on-screen shape forces the next frame to
215    /// re-tessellate it, so growing once is the cheaper response to a working
216    /// set that outgrew the cache.
217    pub fn growing(capacity: usize, max_capacity: usize) -> Self {
218        let max_capacity = max_capacity.max(capacity);
219        Self {
220            inner: RefCell::new(Inner {
221                entries: HashMap::new(),
222                capacity,
223                max_capacity,
224                tick: 0,
225                stats: ShapeStats::default(),
226                prefetching: false,
227                pending: Vec::new(),
228                pending_keys: HashSet::new(),
229            }),
230        }
231    }
232
233    pub fn capacity(&self) -> usize {
234        self.inner.borrow().capacity
235    }
236
237    pub fn max_capacity(&self) -> usize {
238        self.inner.borrow().max_capacity
239    }
240
241    pub fn len(&self) -> usize {
242        self.inner.borrow().entries.len()
243    }
244
245    pub fn is_empty(&self) -> bool {
246        self.inner.borrow().entries.is_empty()
247    }
248
249    pub fn stats(&self) -> ShapeStats {
250        self.inner.borrow().stats
251    }
252
253    pub fn reset_stats(&self) {
254        self.inner.borrow_mut().stats = ShapeStats::default();
255    }
256
257    pub fn clear(&self) {
258        self.inner.borrow_mut().entries.clear();
259    }
260
261    pub fn fill(&self, path: &Path, rule: FillRule, opts: RasterOptions) -> Rc<Shape> {
262        self.fill_styled(path, FillStyle::new(rule, opts))
263    }
264
265    pub fn fill_styled(&self, path: &Path, style: FillStyle) -> Rc<Shape> {
266        let key = Key {
267            kind: 0,
268            fingerprint: path.fingerprint(),
269            style: style.rule as u64,
270            opts: opts_key(style.opts),
271            offset: 0,
272        };
273        self.get_or_build(
274            key,
275            |out| {
276                raster::fill(path, style.rule, style.opts, out);
277            },
278            || Request::Fill {
279                path: path.clone(),
280                rule: style.rule,
281                opts: style.opts,
282                offset: Vec2::ZERO,
283            },
284        )
285    }
286
287    pub fn fill_offset(
288        &self,
289        path: &Path,
290        rule: FillRule,
291        opts: RasterOptions,
292        offset: Vec2,
293    ) -> Rc<Shape> {
294        let key = Key {
295            kind: 0,
296            fingerprint: path.fingerprint(),
297            style: rule as u64,
298            opts: opts_key(opts),
299            offset: offset_key(offset),
300        };
301        self.get_or_build(
302            key,
303            |out| {
304                let translated = path.translated(offset.x, offset.y);
305                raster::fill(&translated, rule, opts, out);
306            },
307            || Request::Fill {
308                path: path.clone(),
309                rule,
310                opts,
311                offset,
312            },
313        )
314    }
315
316    pub fn stroke(&self, path: &Path, style: &StrokeStyle, opts: RasterOptions) -> Rc<Shape> {
317        let key = Key {
318            kind: 1,
319            fingerprint: path.fingerprint(),
320            style: style.fingerprint(),
321            opts: opts_key(opts),
322            offset: 0,
323        };
324        self.get_or_build(
325            key,
326            |out| {
327                stroke::stroke_path(path, style, opts, out);
328            },
329            || Request::Stroke {
330                path: path.clone(),
331                style: style.clone(),
332                opts,
333                offset: Vec2::ZERO,
334            },
335        )
336    }
337
338    pub fn stroke_offset(
339        &self,
340        path: &Path,
341        style: &StrokeStyle,
342        opts: RasterOptions,
343        offset: Vec2,
344    ) -> Rc<Shape> {
345        let key = Key {
346            kind: 1,
347            fingerprint: path.fingerprint(),
348            style: style.fingerprint(),
349            opts: opts_key(opts),
350            offset: offset_key(offset),
351        };
352        self.get_or_build(
353            key,
354            |out| {
355                let translated = path.translated(offset.x, offset.y);
356                stroke::stroke_path(&translated, style, opts, out);
357            },
358            || Request::Stroke {
359                path: path.clone(),
360                style: style.clone(),
361                opts,
362                offset,
363            },
364        )
365    }
366
367    pub fn fill_contours(
368        &self,
369        contours: &[crate::Contour],
370        rule: FillRule,
371        opts: RasterOptions,
372    ) -> Rc<Shape> {
373        let key = Key {
374            kind: 2,
375            fingerprint: contours_fingerprint(contours),
376            style: rule as u64,
377            opts: opts_key(opts),
378            offset: 0,
379        };
380        self.get_or_build(
381            key,
382            |out| {
383                raster::fill_contours(contours, rule, opts, out);
384            },
385            || Request::Contours {
386                contours: contours.to_vec(),
387                rule,
388                opts,
389            },
390        )
391    }
392
393    pub fn disk(&self, center: Point, radius: f32) -> Rc<Shape> {
394        if !(radius > 0.0) || !radius.is_finite() {
395            return Rc::new(Shape::empty());
396        }
397        Rc::new(Shape::new(vec![Span::rounded(
398            Rect::from_xywh(
399                center.x - radius,
400                center.y - radius,
401                radius * 2.0,
402                radius * 2.0,
403            ),
404            radius,
405        )]))
406    }
407
408    pub fn rounded_rect(&self, rect: Rect, radius: f32) -> Rc<Shape> {
409        let radius = radius
410            .clamp(0.0, rect.size.width.min(rect.size.height) * 0.5)
411            .max(0.0);
412        Rc::new(Shape::new(vec![Span::rounded(rect, radius)]))
413    }
414
415    pub fn rect(&self, rect: Rect) -> Rc<Shape> {
416        Rc::new(Shape::new(vec![Span::rect(rect)]))
417    }
418
419    /// Starts a prefetch pass.
420    ///
421    /// While the pass is active, cache misses are recorded instead of
422    /// tessellated, and each miss returns an empty placeholder [`Shape`]. The
423    /// caller is expected to paint again after
424    /// [`ShapeCache::tessellate_prefetch`], so the placeholders never reach the
425    /// screen. Do not retain placeholder shapes across the pass.
426    pub fn begin_prefetch(&self) {
427        let mut inner = self.inner.borrow_mut();
428        inner.prefetching = true;
429        inner.pending.clear();
430        inner.pending_keys.clear();
431    }
432
433    /// Ends a prefetch pass and returns how many distinct shapes missed the
434    /// cache. Nothing has been tessellated yet.
435    pub fn end_prefetch(&self) -> usize {
436        let mut inner = self.inner.borrow_mut();
437        inner.prefetching = false;
438        inner.pending.len()
439    }
440
441    /// Tessellates the shapes recorded by the last prefetch pass and inserts
442    /// them into the cache, returning how many were built.
443    ///
444    /// Big batches are split across worker threads; each worker builds its
445    /// shapes with `parallel(false)` because the batch already keeps the cores
446    /// busy. Small batches stay on the calling thread, and a lone shape keeps
447    /// its own band-parallel rasterizer. Results are inserted in request order
448    /// so a batch produces the same spans every time.
449    pub fn tessellate_prefetch(&self) -> usize {
450        let pending = {
451            let mut inner = self.inner.borrow_mut();
452            inner.prefetching = false;
453            inner.pending_keys.clear();
454            std::mem::take(&mut inner.pending)
455        };
456        if pending.is_empty() {
457            return 0;
458        }
459
460        let cost: usize = pending.iter().map(|(_, req)| req.cost()).sum();
461        let workers = if pending.len() >= BATCH_MIN_SHAPES && cost >= BATCH_MIN_COST {
462            std::thread::available_parallelism()
463                .map(|n| n.get())
464                .unwrap_or(1)
465                .min(BATCH_MAX_WORKERS)
466                .min(pending.len())
467        } else {
468            1
469        };
470
471        let built: Vec<(Key, Vec<Span>)> = if workers <= 1 {
472            pending
473                .iter()
474                .map(|(key, req)| (*key, build_request(req, req.opts())))
475                .collect()
476        } else {
477            let chunk = pending.len().div_ceil(workers);
478            let mut parts: Vec<Vec<(Key, Vec<Span>)>> = Vec::with_capacity(workers);
479            std::thread::scope(|scope| {
480                let mut handles = Vec::with_capacity(workers);
481                for chunk in pending.chunks(chunk) {
482                    handles.push(scope.spawn(move || {
483                        chunk
484                            .iter()
485                            .map(|(key, req)| {
486                                (*key, build_request(req, req.opts().parallel(false)))
487                            })
488                            .collect::<Vec<_>>()
489                    }));
490                }
491                // Join in submission order so the cache sees requests in the
492                // order they were asked for.
493                for handle in handles {
494                    if let Ok(part) = handle.join() {
495                        parts.push(part);
496                    }
497                }
498            });
499            parts.into_iter().flatten().collect()
500        };
501
502        let count = built.len();
503        let mut inner = self.inner.borrow_mut();
504        for (key, spans) in built {
505            inner.tick += 1;
506            let used = inner.tick;
507            inner.insert_used(key, Rc::new(Shape::new(spans)), used);
508        }
509        count
510    }
511
512    fn get_or_build(
513        &self,
514        key: Key,
515        build: impl FnOnce(&mut Vec<Span>),
516        request: impl FnOnce() -> Request,
517    ) -> Rc<Shape> {
518        let mut inner = self.inner.borrow_mut();
519        inner.tick += 1;
520        let tick = inner.tick;
521        if let Some(entry) = inner.entries.get_mut(&key) {
522            entry.used = tick;
523            let shape = entry.shape.clone();
524            inner.stats.hits += 1;
525            return shape;
526        }
527        if inner.prefetching {
528            if inner.pending_keys.insert(key) {
529                inner.stats.misses += 1;
530                inner.pending.push((key, request()));
531            } else {
532                // A repeat lookup in the same pass would have been a hit if
533                // the first one had been built, so do not report it as a miss.
534                inner.stats.hits += 1;
535            }
536            return Rc::new(Shape::empty());
537        }
538        inner.stats.misses += 1;
539        if inner.capacity == 0 {
540            drop(inner);
541            let mut spans = Vec::new();
542            build(&mut spans);
543            return Rc::new(Shape::new(spans));
544        }
545
546        let mut spans = Vec::new();
547        build(&mut spans);
548        let shape = Rc::new(Shape::new(spans));
549        inner.insert_used(key, shape.clone(), tick);
550        shape
551    }
552}
553
554impl Inner {
555    fn insert_used(&mut self, key: Key, shape: Rc<Shape>, used: u64) {
556        if self.capacity == 0 {
557            return;
558        }
559        if self.entries.len() >= self.capacity
560            && let Some((oldest, oldest_used)) = self
561                .entries
562                .iter()
563                .min_by_key(|(_, entry)| entry.used)
564                .map(|(key, entry)| (*key, entry.used))
565        {
566            // Everything is still in use, so the working set outgrew the
567            // cache: grow instead of evicting a shape that is on screen.
568            let hot = used.saturating_sub(oldest_used) <= self.capacity as u64;
569            if hot && self.capacity < self.max_capacity {
570                self.capacity = self.capacity.saturating_mul(2).min(self.max_capacity);
571            } else {
572                self.entries.remove(&oldest);
573                self.stats.evictions += 1;
574            }
575        }
576        self.entries.insert(key, Entry { shape, used });
577    }
578}
579
580fn build_request(req: &Request, opts: RasterOptions) -> Vec<Span> {
581    let mut spans = Vec::new();
582    req.build(opts, &mut spans);
583    spans
584}
585
586fn offset_key(offset: Vec2) -> u64 {
587    let mut hash: u64 = 0xcbf2_9ce4_8422_2325;
588    for v in [offset.x, offset.y] {
589        let v = if v == 0.0 { 0.0 } else { v };
590        hash ^= u64::from(v.to_bits());
591        hash = hash.wrapping_mul(0x0000_0100_0000_01b3);
592    }
593    hash
594}
595
596fn opts_key(opts: RasterOptions) -> u64 {
597    let mut hash: u64 = 0xcbf2_9ce4_8422_2325;
598    for v in [opts.tolerance, opts.row_height, opts.merge_tol] {
599        hash ^= u64::from(v.to_bits());
600        hash = hash.wrapping_mul(0x0000_0100_0000_01b3);
601    }
602    hash ^= u64::from(opts.aa_samples);
603    hash = hash.wrapping_mul(0x0000_0100_0000_01b3);
604    hash ^= u64::from(opts.analytic);
605    hash = hash.wrapping_mul(0x0000_0100_0000_01b3);
606    hash ^= u64::from(opts.parallel);
607    hash.wrapping_mul(0x0000_0100_0000_01b3)
608}
609
610fn contours_fingerprint(contours: &[crate::Contour]) -> u64 {
611    let mut hash: u64 = 0xcbf2_9ce4_8422_2325;
612    let mut eat = |v: u64| {
613        hash ^= v;
614        hash = hash.wrapping_mul(0x0000_0100_0000_01b3);
615    };
616    for c in contours {
617        eat(c.points.len() as u64);
618        eat(u64::from(c.closed));
619        for p in &c.points {
620            eat(u64::from(p.x.to_bits()));
621            eat(u64::from(p.y.to_bits()));
622        }
623    }
624    hash
625}