Skip to main content

sequoia_draw/
cache.rs

1use std::cell::RefCell;
2use std::collections::HashMap;
3use std::rc::Rc;
4
5use sequoia_core::{Point, Rect, Vec2};
6
7use crate::path::{FillRule, Path};
8use crate::raster::{self, FillStyle, RasterOptions};
9use crate::stroke::{self, StrokeStyle};
10use crate::{Span, union_rect};
11
12#[derive(Clone, Debug, PartialEq)]
13pub struct Shape {
14    pub spans: Rc<[Span]>,
15
16    pub bounds: Rect,
17}
18
19impl Shape {
20    pub fn new(spans: Vec<Span>) -> Self {
21        let mut bounds = Rect::ZERO;
22        for span in &spans {
23            bounds = union_rect(bounds, span.rect);
24        }
25        Self {
26            spans: spans.into(),
27            bounds,
28        }
29    }
30
31    pub fn empty() -> Self {
32        Self {
33            spans: Rc::from(Vec::new()),
34            bounds: Rect::ZERO,
35        }
36    }
37
38    pub fn len(&self) -> usize {
39        self.spans.len()
40    }
41
42    pub fn is_empty(&self) -> bool {
43        self.spans.is_empty()
44    }
45
46    pub fn iter(&self) -> std::slice::Iter<'_, Span> {
47        self.spans.iter()
48    }
49}
50
51impl<'a> IntoIterator for &'a Shape {
52    type Item = &'a Span;
53    type IntoIter = std::slice::Iter<'a, Span>;
54
55    fn into_iter(self) -> Self::IntoIter {
56        self.spans.iter()
57    }
58}
59
60#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
61pub struct ShapeStats {
62    pub hits: u64,
63
64    pub misses: u64,
65
66    pub evictions: u64,
67}
68
69impl ShapeStats {
70    pub fn hit_rate(&self) -> f32 {
71        let total = self.hits + self.misses;
72        if total == 0 {
73            0.0
74        } else {
75            self.hits as f32 / total as f32
76        }
77    }
78}
79
80#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
81struct Key {
82    kind: u8,
83    fingerprint: u64,
84    style: u64,
85    opts: u64,
86    offset: u64,
87}
88
89#[derive(Debug)]
90struct Entry {
91    shape: Rc<Shape>,
92    used: u64,
93}
94
95pub const DEFAULT_CAPACITY: usize = 256;
96
97#[derive(Debug)]
98pub struct ShapeCache {
99    inner: RefCell<Inner>,
100}
101
102#[derive(Debug)]
103struct Inner {
104    entries: HashMap<Key, Entry>,
105    capacity: usize,
106    tick: u64,
107    stats: ShapeStats,
108}
109
110impl Default for ShapeCache {
111    fn default() -> Self {
112        Self::new(DEFAULT_CAPACITY)
113    }
114}
115
116impl ShapeCache {
117    pub fn new(capacity: usize) -> Self {
118        Self {
119            inner: RefCell::new(Inner {
120                entries: HashMap::new(),
121                capacity,
122                tick: 0,
123                stats: ShapeStats::default(),
124            }),
125        }
126    }
127
128    pub fn capacity(&self) -> usize {
129        self.inner.borrow().capacity
130    }
131
132    pub fn len(&self) -> usize {
133        self.inner.borrow().entries.len()
134    }
135
136    pub fn is_empty(&self) -> bool {
137        self.inner.borrow().entries.is_empty()
138    }
139
140    pub fn stats(&self) -> ShapeStats {
141        self.inner.borrow().stats
142    }
143
144    pub fn reset_stats(&self) {
145        self.inner.borrow_mut().stats = ShapeStats::default();
146    }
147
148    pub fn clear(&self) {
149        self.inner.borrow_mut().entries.clear();
150    }
151
152    pub fn fill(&self, path: &Path, rule: FillRule, opts: RasterOptions) -> Rc<Shape> {
153        self.fill_styled(path, FillStyle::new(rule, opts))
154    }
155
156    pub fn fill_styled(&self, path: &Path, style: FillStyle) -> Rc<Shape> {
157        let key = Key {
158            kind: 0,
159            fingerprint: path.fingerprint(),
160            style: style.rule as u64,
161            opts: opts_key(style.opts),
162            offset: 0,
163        };
164        self.get_or_build(key, |out| {
165            raster::fill(path, style.rule, style.opts, out);
166        })
167    }
168
169    pub fn fill_offset(
170        &self,
171        path: &Path,
172        rule: FillRule,
173        opts: RasterOptions,
174        offset: Vec2,
175    ) -> Rc<Shape> {
176        let key = Key {
177            kind: 0,
178            fingerprint: path.fingerprint(),
179            style: rule as u64,
180            opts: opts_key(opts),
181            offset: offset_key(offset),
182        };
183        self.get_or_build(key, |out| {
184            let translated = path.translated(offset.x, offset.y);
185            raster::fill(&translated, rule, opts, out);
186        })
187    }
188
189    pub fn stroke(&self, path: &Path, style: &StrokeStyle, opts: RasterOptions) -> Rc<Shape> {
190        let key = Key {
191            kind: 1,
192            fingerprint: path.fingerprint(),
193            style: style.fingerprint(),
194            opts: opts_key(opts),
195            offset: 0,
196        };
197        self.get_or_build(key, |out| {
198            stroke::stroke_path(path, style, opts, out);
199        })
200    }
201
202    pub fn stroke_offset(
203        &self,
204        path: &Path,
205        style: &StrokeStyle,
206        opts: RasterOptions,
207        offset: Vec2,
208    ) -> Rc<Shape> {
209        let key = Key {
210            kind: 1,
211            fingerprint: path.fingerprint(),
212            style: style.fingerprint(),
213            opts: opts_key(opts),
214            offset: offset_key(offset),
215        };
216        self.get_or_build(key, |out| {
217            let translated = path.translated(offset.x, offset.y);
218            stroke::stroke_path(&translated, style, opts, out);
219        })
220    }
221
222    pub fn fill_contours(
223        &self,
224        contours: &[crate::Contour],
225        rule: FillRule,
226        opts: RasterOptions,
227    ) -> Rc<Shape> {
228        let key = Key {
229            kind: 2,
230            fingerprint: contours_fingerprint(contours),
231            style: rule as u64,
232            opts: opts_key(opts),
233            offset: 0,
234        };
235        self.get_or_build(key, |out| {
236            raster::fill_contours(contours, rule, opts, out);
237        })
238    }
239
240    pub fn disk(&self, center: Point, radius: f32) -> Rc<Shape> {
241        if !(radius > 0.0) || !radius.is_finite() {
242            return Rc::new(Shape::empty());
243        }
244        Rc::new(Shape::new(vec![Span::rounded(
245            Rect::from_xywh(
246                center.x - radius,
247                center.y - radius,
248                radius * 2.0,
249                radius * 2.0,
250            ),
251            radius,
252        )]))
253    }
254
255    pub fn rounded_rect(&self, rect: Rect, radius: f32) -> Rc<Shape> {
256        let radius = radius
257            .clamp(0.0, rect.size.width.min(rect.size.height) * 0.5)
258            .max(0.0);
259        Rc::new(Shape::new(vec![Span::rounded(rect, radius)]))
260    }
261
262    pub fn rect(&self, rect: Rect) -> Rc<Shape> {
263        Rc::new(Shape::new(vec![Span::rect(rect)]))
264    }
265
266    fn get_or_build(&self, key: Key, build: impl FnOnce(&mut Vec<Span>)) -> Rc<Shape> {
267        let mut inner = self.inner.borrow_mut();
268        inner.tick += 1;
269        let tick = inner.tick;
270        if let Some(entry) = inner.entries.get_mut(&key) {
271            entry.used = tick;
272            let shape = entry.shape.clone();
273            inner.stats.hits += 1;
274            return shape;
275        }
276        inner.stats.misses += 1;
277        if inner.capacity == 0 {
278            drop(inner);
279            let mut spans = Vec::new();
280            build(&mut spans);
281            return Rc::new(Shape::new(spans));
282        }
283
284        let mut spans = Vec::new();
285        build(&mut spans);
286        let shape = Rc::new(Shape::new(spans));
287        if inner.entries.len() >= inner.capacity
288            && let Some(oldest) = inner
289                .entries
290                .iter()
291                .min_by_key(|(_, entry)| entry.used)
292                .map(|(key, _)| *key)
293        {
294            inner.entries.remove(&oldest);
295            inner.stats.evictions += 1;
296        }
297        inner.entries.insert(
298            key,
299            Entry {
300                shape: shape.clone(),
301                used: tick,
302            },
303        );
304        shape
305    }
306}
307
308fn offset_key(offset: Vec2) -> u64 {
309    let mut hash: u64 = 0xcbf2_9ce4_8422_2325;
310    for v in [offset.x, offset.y] {
311        let v = if v == 0.0 { 0.0 } else { v };
312        hash ^= u64::from(v.to_bits());
313        hash = hash.wrapping_mul(0x0000_0100_0000_01b3);
314    }
315    hash
316}
317
318fn opts_key(opts: RasterOptions) -> u64 {
319    let mut hash: u64 = 0xcbf2_9ce4_8422_2325;
320    for v in [opts.tolerance, opts.row_height, opts.merge_tol] {
321        hash ^= u64::from(v.to_bits());
322        hash = hash.wrapping_mul(0x0000_0100_0000_01b3);
323    }
324    hash ^= u64::from(opts.aa_samples);
325    hash = hash.wrapping_mul(0x0000_0100_0000_01b3);
326    hash ^= u64::from(opts.analytic);
327    hash.wrapping_mul(0x0000_0100_0000_01b3)
328}
329
330fn contours_fingerprint(contours: &[crate::Contour]) -> u64 {
331    let mut hash: u64 = 0xcbf2_9ce4_8422_2325;
332    let mut eat = |v: u64| {
333        hash ^= v;
334        hash = hash.wrapping_mul(0x0000_0100_0000_01b3);
335    };
336    for c in contours {
337        eat(c.points.len() as u64);
338        eat(u64::from(c.closed));
339        for p in &c.points {
340            eat(u64::from(p.x.to_bits()));
341            eat(u64::from(p.y.to_bits()));
342        }
343    }
344    hash
345}