Skip to main content

cranpose_render_common/
raster_cache.rs

1use cranpose_core::NodeId;
2use cranpose_ui_graphics::Rect;
3
4const SCALE_BUCKET_STEPS: f32 = 256.0;
5
6#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
7pub struct ScaleBucket(u32);
8
9impl ScaleBucket {
10    pub fn from_scale(scale: f32) -> Self {
11        let normalized = if scale.is_finite() && scale > 0.0 {
12            scale
13        } else {
14            1.0
15        };
16        Self((normalized * SCALE_BUCKET_STEPS).round().max(1.0) as u32)
17    }
18
19    pub fn raw(self) -> u32 {
20        self.0
21    }
22}
23
24#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
25pub struct LayerRasterCacheHashes {
26    pub target_content: u64,
27    pub effect: u64,
28}
29
30#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
31enum LayerRasterCacheKind {
32    FullSurface,
33    SourceContent,
34    BackdropEffect,
35    SceneRange,
36    PrefixSnapshot,
37}
38
39impl LayerRasterCacheKind {
40    fn identity_kind(self) -> u8 {
41        match self {
42            Self::FullSurface => 0,
43            Self::SourceContent => 1,
44            Self::BackdropEffect => 2,
45            Self::SceneRange => 3,
46            Self::PrefixSnapshot => 4,
47        }
48    }
49}
50
51#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
52pub struct LayerRasterCacheIdentity {
53    stable_id: NodeId,
54    kind: u8,
55}
56
57#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
58pub struct LayerRasterCacheKey {
59    kind: LayerRasterCacheKind,
60    stable_id: Option<NodeId>,
61    content_hash: u64,
62    effect_hash: u64,
63    local_bounds_bits: [u32; 4],
64    pixel_size: [u32; 2],
65    scale_bucket: ScaleBucket,
66}
67
68fn local_bounds_bits(local_bounds: Rect) -> [u32; 4] {
69    [
70        local_bounds.x.to_bits(),
71        local_bounds.y.to_bits(),
72        local_bounds.width.to_bits(),
73        local_bounds.height.to_bits(),
74    ]
75}
76
77impl LayerRasterCacheKey {
78    pub fn new(
79        stable_id: Option<NodeId>,
80        content_hash: u64,
81        effect_hash: u64,
82        local_bounds: Rect,
83        pixel_size: (u32, u32),
84        scale_bucket: ScaleBucket,
85    ) -> Self {
86        Self {
87            kind: LayerRasterCacheKind::FullSurface,
88            stable_id,
89            content_hash,
90            effect_hash,
91            local_bounds_bits: local_bounds_bits(local_bounds),
92            pixel_size: [pixel_size.0, pixel_size.1],
93            scale_bucket,
94        }
95    }
96
97    pub fn source_content(
98        stable_id: Option<NodeId>,
99        content_hash: u64,
100        local_bounds: Rect,
101        pixel_size: (u32, u32),
102        scale_bucket: ScaleBucket,
103    ) -> Self {
104        Self {
105            kind: LayerRasterCacheKind::SourceContent,
106            stable_id,
107            content_hash,
108            effect_hash: 0,
109            local_bounds_bits: local_bounds_bits(local_bounds),
110            pixel_size: [pixel_size.0, pixel_size.1],
111            scale_bucket,
112        }
113    }
114
115    pub fn backdrop_effect(
116        stable_id: Option<NodeId>,
117        input_hash: u64,
118        effect_hash: u64,
119        local_bounds: Rect,
120        pixel_size: (u32, u32),
121        scale_bucket: ScaleBucket,
122    ) -> Self {
123        Self {
124            kind: LayerRasterCacheKind::BackdropEffect,
125            stable_id,
126            content_hash: input_hash,
127            effect_hash,
128            local_bounds_bits: local_bounds_bits(local_bounds),
129            pixel_size: [pixel_size.0, pixel_size.1],
130            scale_bucket,
131        }
132    }
133
134    pub fn scene_range(
135        content_hash: u64,
136        local_bounds: Rect,
137        pixel_size: (u32, u32),
138        scale_bucket: ScaleBucket,
139    ) -> Self {
140        Self {
141            kind: LayerRasterCacheKind::SceneRange,
142            stable_id: None,
143            content_hash,
144            effect_hash: 0,
145            local_bounds_bits: local_bounds_bits(local_bounds),
146            pixel_size: [pixel_size.0, pixel_size.1],
147            scale_bucket,
148        }
149    }
150
151    /// A snapshot of the scene's rendered prefix: the bytes the target held
152    /// after drawing ops `[0, prefix_len)` over the pass's clear color. A
153    /// replay of captured bytes is identical to direct rendering by
154    /// construction — no flattening, so none of the chained-rounding
155    /// divergence flatten entries carry.
156    pub fn prefix_snapshot(
157        content_hash: u64,
158        prefix_len: u64,
159        local_bounds: Rect,
160        pixel_size: (u32, u32),
161        scale_bucket: ScaleBucket,
162    ) -> Self {
163        Self {
164            kind: LayerRasterCacheKind::PrefixSnapshot,
165            stable_id: None,
166            content_hash,
167            effect_hash: prefix_len,
168            local_bounds_bits: local_bounds_bits(local_bounds),
169            pixel_size: [pixel_size.0, pixel_size.1],
170            scale_bucket,
171        }
172    }
173
174    pub fn stable_id(self) -> Option<NodeId> {
175        self.stable_id
176    }
177
178    pub fn is_scene_range(self) -> bool {
179        matches!(
180            self.kind,
181            LayerRasterCacheKind::SceneRange | LayerRasterCacheKind::PrefixSnapshot
182        )
183    }
184
185    pub fn is_source_content(self) -> bool {
186        self.kind == LayerRasterCacheKind::SourceContent
187    }
188
189    pub fn identity(self) -> Option<LayerRasterCacheIdentity> {
190        Some(LayerRasterCacheIdentity {
191            stable_id: self.stable_id?,
192            kind: self.kind.identity_kind(),
193        })
194    }
195
196    pub fn pixel_size(self) -> (u32, u32) {
197        (self.pixel_size[0], self.pixel_size[1])
198    }
199
200    pub fn scale_bucket(self) -> ScaleBucket {
201        self.scale_bucket
202    }
203}
204
205#[cfg(test)]
206mod tests {
207    use super::*;
208
209    #[test]
210    fn scale_bucket_normalizes_invalid_values() {
211        assert_eq!(
212            ScaleBucket::from_scale(0.0).raw(),
213            ScaleBucket::from_scale(1.0).raw()
214        );
215        assert_eq!(
216            ScaleBucket::from_scale(-3.0).raw(),
217            ScaleBucket::from_scale(1.0).raw()
218        );
219        assert_eq!(
220            ScaleBucket::from_scale(f32::NAN).raw(),
221            ScaleBucket::from_scale(1.0).raw()
222        );
223    }
224
225    #[test]
226    fn scale_bucket_quantizes_small_fractional_changes() {
227        let a = ScaleBucket::from_scale(1.0);
228        let b = ScaleBucket::from_scale(1.001);
229        let c = ScaleBucket::from_scale(1.01);
230        assert_eq!(a, b);
231        assert_ne!(a, c);
232    }
233
234    #[test]
235    fn layer_raster_cache_key_captures_bounds_and_pixel_size() {
236        let rect = Rect {
237            x: 1.0,
238            y: 2.0,
239            width: 30.0,
240            height: 40.0,
241        };
242        let base = LayerRasterCacheKey::new(
243            Some(7),
244            11,
245            13,
246            rect,
247            (30, 40),
248            ScaleBucket::from_scale(1.0),
249        );
250        let moved = LayerRasterCacheKey::new(
251            Some(7),
252            11,
253            13,
254            Rect { x: 2.0, ..rect },
255            (30, 40),
256            ScaleBucket::from_scale(1.0),
257        );
258        let resized = LayerRasterCacheKey::new(
259            Some(7),
260            11,
261            13,
262            rect,
263            (60, 80),
264            ScaleBucket::from_scale(2.0),
265        );
266
267        assert_ne!(base, moved);
268        assert_ne!(base, resized);
269        assert_eq!(base.stable_id(), Some(7));
270        assert_eq!(base.pixel_size(), (30, 40));
271    }
272
273    #[test]
274    fn source_content_keys_do_not_collide_with_full_surface_keys() {
275        let rect = Rect {
276            x: 1.0,
277            y: 2.0,
278            width: 30.0,
279            height: 40.0,
280        };
281        let scale = ScaleBucket::from_scale(1.0);
282        let source = LayerRasterCacheKey::source_content(Some(7), 11, rect, (30, 40), scale);
283        let full = LayerRasterCacheKey::new(Some(7), 11, 0, rect, (30, 40), scale);
284
285        assert_ne!(source, full);
286        assert_ne!(source.identity(), full.identity());
287    }
288
289    #[test]
290    fn backdrop_effect_keys_do_not_collide_with_layer_surface_keys() {
291        let rect = Rect {
292            x: 1.0,
293            y: 2.0,
294            width: 30.0,
295            height: 40.0,
296        };
297        let scale = ScaleBucket::from_scale(1.0);
298        let backdrop = LayerRasterCacheKey::backdrop_effect(Some(7), 11, 13, rect, (30, 40), scale);
299        let source = LayerRasterCacheKey::source_content(Some(7), 11, rect, (30, 40), scale);
300        let full = LayerRasterCacheKey::new(Some(7), 11, 13, rect, (30, 40), scale);
301
302        assert_ne!(backdrop, source);
303        assert_ne!(backdrop, full);
304        assert_ne!(backdrop.identity(), source.identity());
305        assert_ne!(backdrop.identity(), full.identity());
306    }
307
308    #[test]
309    fn prefix_snapshot_keys_share_the_scene_range_partition_but_never_a_key() {
310        let rect = Rect {
311            x: 0.0,
312            y: 0.0,
313            width: 320.0,
314            height: 240.0,
315        };
316        let scale = ScaleBucket::from_scale(1.0);
317        let prefix = LayerRasterCacheKey::prefix_snapshot(11, 7, rect, (320, 240), scale);
318        let range = LayerRasterCacheKey::scene_range(11, rect, (320, 240), scale);
319        let longer = LayerRasterCacheKey::prefix_snapshot(11, 8, rect, (320, 240), scale);
320
321        assert!(prefix.is_scene_range());
322        assert_ne!(prefix, range);
323        assert_ne!(prefix, longer);
324        assert_eq!(prefix.identity(), None);
325        assert_eq!(prefix.pixel_size(), (320, 240));
326    }
327
328    #[test]
329    fn scene_range_keys_do_not_collide_with_layer_surface_keys() {
330        let rect = Rect {
331            x: 0.0,
332            y: 0.0,
333            width: 320.0,
334            height: 240.0,
335        };
336        let scale = ScaleBucket::from_scale(1.0);
337        let range = LayerRasterCacheKey::scene_range(11, rect, (320, 240), scale);
338        let source = LayerRasterCacheKey::source_content(None, 11, rect, (320, 240), scale);
339        let full = LayerRasterCacheKey::new(None, 11, 0, rect, (320, 240), scale);
340
341        assert_ne!(range, source);
342        assert_ne!(range, full);
343        assert_eq!(range.identity(), None);
344    }
345}