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}
37
38impl LayerRasterCacheKind {
39    fn identity_kind(self) -> u8 {
40        match self {
41            Self::FullSurface => 0,
42            Self::SourceContent => 1,
43            Self::BackdropEffect => 2,
44            Self::SceneRange => 3,
45        }
46    }
47}
48
49#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
50pub struct LayerRasterCacheIdentity {
51    stable_id: NodeId,
52    kind: u8,
53}
54
55#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
56pub struct LayerRasterCacheKey {
57    kind: LayerRasterCacheKind,
58    stable_id: Option<NodeId>,
59    content_hash: u64,
60    effect_hash: u64,
61    local_bounds_bits: [u32; 4],
62    pixel_size: [u32; 2],
63    scale_bucket: ScaleBucket,
64}
65
66impl LayerRasterCacheKey {
67    pub fn new(
68        stable_id: Option<NodeId>,
69        content_hash: u64,
70        effect_hash: u64,
71        local_bounds: Rect,
72        pixel_size: (u32, u32),
73        scale_bucket: ScaleBucket,
74    ) -> Self {
75        Self {
76            kind: LayerRasterCacheKind::FullSurface,
77            stable_id,
78            content_hash,
79            effect_hash,
80            local_bounds_bits: [
81                local_bounds.x.to_bits(),
82                local_bounds.y.to_bits(),
83                local_bounds.width.to_bits(),
84                local_bounds.height.to_bits(),
85            ],
86            pixel_size: [pixel_size.0, pixel_size.1],
87            scale_bucket,
88        }
89    }
90
91    pub fn source_content(
92        stable_id: Option<NodeId>,
93        content_hash: u64,
94        local_bounds: Rect,
95        pixel_size: (u32, u32),
96        scale_bucket: ScaleBucket,
97    ) -> Self {
98        Self {
99            kind: LayerRasterCacheKind::SourceContent,
100            stable_id,
101            content_hash,
102            effect_hash: 0,
103            local_bounds_bits: [
104                local_bounds.x.to_bits(),
105                local_bounds.y.to_bits(),
106                local_bounds.width.to_bits(),
107                local_bounds.height.to_bits(),
108            ],
109            pixel_size: [pixel_size.0, pixel_size.1],
110            scale_bucket,
111        }
112    }
113
114    pub fn backdrop_effect(
115        stable_id: Option<NodeId>,
116        input_hash: u64,
117        effect_hash: u64,
118        local_bounds: Rect,
119        pixel_size: (u32, u32),
120        scale_bucket: ScaleBucket,
121    ) -> Self {
122        Self {
123            kind: LayerRasterCacheKind::BackdropEffect,
124            stable_id,
125            content_hash: input_hash,
126            effect_hash,
127            local_bounds_bits: [
128                local_bounds.x.to_bits(),
129                local_bounds.y.to_bits(),
130                local_bounds.width.to_bits(),
131                local_bounds.height.to_bits(),
132            ],
133            pixel_size: [pixel_size.0, pixel_size.1],
134            scale_bucket,
135        }
136    }
137
138    pub fn scene_range(
139        content_hash: u64,
140        local_bounds: Rect,
141        pixel_size: (u32, u32),
142        scale_bucket: ScaleBucket,
143    ) -> Self {
144        Self {
145            kind: LayerRasterCacheKind::SceneRange,
146            stable_id: None,
147            content_hash,
148            effect_hash: 0,
149            local_bounds_bits: [
150                local_bounds.x.to_bits(),
151                local_bounds.y.to_bits(),
152                local_bounds.width.to_bits(),
153                local_bounds.height.to_bits(),
154            ],
155            pixel_size: [pixel_size.0, pixel_size.1],
156            scale_bucket,
157        }
158    }
159
160    pub fn stable_id(self) -> Option<NodeId> {
161        self.stable_id
162    }
163
164    pub fn is_scene_range(self) -> bool {
165        self.kind == LayerRasterCacheKind::SceneRange
166    }
167
168    pub fn identity(self) -> Option<LayerRasterCacheIdentity> {
169        Some(LayerRasterCacheIdentity {
170            stable_id: self.stable_id?,
171            kind: self.kind.identity_kind(),
172        })
173    }
174
175    pub fn pixel_size(self) -> (u32, u32) {
176        (self.pixel_size[0], self.pixel_size[1])
177    }
178
179    pub fn scale_bucket(self) -> ScaleBucket {
180        self.scale_bucket
181    }
182}
183
184#[cfg(test)]
185mod tests {
186    use super::*;
187
188    #[test]
189    fn scale_bucket_normalizes_invalid_values() {
190        assert_eq!(
191            ScaleBucket::from_scale(0.0).raw(),
192            ScaleBucket::from_scale(1.0).raw()
193        );
194        assert_eq!(
195            ScaleBucket::from_scale(-3.0).raw(),
196            ScaleBucket::from_scale(1.0).raw()
197        );
198        assert_eq!(
199            ScaleBucket::from_scale(f32::NAN).raw(),
200            ScaleBucket::from_scale(1.0).raw()
201        );
202    }
203
204    #[test]
205    fn scale_bucket_quantizes_small_fractional_changes() {
206        let a = ScaleBucket::from_scale(1.0);
207        let b = ScaleBucket::from_scale(1.001);
208        let c = ScaleBucket::from_scale(1.01);
209        assert_eq!(a, b);
210        assert_ne!(a, c);
211    }
212
213    #[test]
214    fn layer_raster_cache_key_captures_bounds_and_pixel_size() {
215        let rect = Rect {
216            x: 1.0,
217            y: 2.0,
218            width: 30.0,
219            height: 40.0,
220        };
221        let base = LayerRasterCacheKey::new(
222            Some(7),
223            11,
224            13,
225            rect,
226            (30, 40),
227            ScaleBucket::from_scale(1.0),
228        );
229        let moved = LayerRasterCacheKey::new(
230            Some(7),
231            11,
232            13,
233            Rect { x: 2.0, ..rect },
234            (30, 40),
235            ScaleBucket::from_scale(1.0),
236        );
237        let resized = LayerRasterCacheKey::new(
238            Some(7),
239            11,
240            13,
241            rect,
242            (60, 80),
243            ScaleBucket::from_scale(2.0),
244        );
245
246        assert_ne!(base, moved);
247        assert_ne!(base, resized);
248        assert_eq!(base.stable_id(), Some(7));
249        assert_eq!(base.pixel_size(), (30, 40));
250    }
251
252    #[test]
253    fn source_content_keys_do_not_collide_with_full_surface_keys() {
254        let rect = Rect {
255            x: 1.0,
256            y: 2.0,
257            width: 30.0,
258            height: 40.0,
259        };
260        let scale = ScaleBucket::from_scale(1.0);
261        let source = LayerRasterCacheKey::source_content(Some(7), 11, rect, (30, 40), scale);
262        let full = LayerRasterCacheKey::new(Some(7), 11, 0, rect, (30, 40), scale);
263
264        assert_ne!(source, full);
265        assert_ne!(source.identity(), full.identity());
266    }
267
268    #[test]
269    fn backdrop_effect_keys_do_not_collide_with_layer_surface_keys() {
270        let rect = Rect {
271            x: 1.0,
272            y: 2.0,
273            width: 30.0,
274            height: 40.0,
275        };
276        let scale = ScaleBucket::from_scale(1.0);
277        let backdrop = LayerRasterCacheKey::backdrop_effect(Some(7), 11, 13, rect, (30, 40), scale);
278        let source = LayerRasterCacheKey::source_content(Some(7), 11, rect, (30, 40), scale);
279        let full = LayerRasterCacheKey::new(Some(7), 11, 13, rect, (30, 40), scale);
280
281        assert_ne!(backdrop, source);
282        assert_ne!(backdrop, full);
283        assert_ne!(backdrop.identity(), source.identity());
284        assert_ne!(backdrop.identity(), full.identity());
285    }
286
287    #[test]
288    fn scene_range_keys_do_not_collide_with_layer_surface_keys() {
289        let rect = Rect {
290            x: 0.0,
291            y: 0.0,
292            width: 320.0,
293            height: 240.0,
294        };
295        let scale = ScaleBucket::from_scale(1.0);
296        let range = LayerRasterCacheKey::scene_range(11, rect, (320, 240), scale);
297        let source = LayerRasterCacheKey::source_content(None, 11, rect, (320, 240), scale);
298        let full = LayerRasterCacheKey::new(None, 11, 0, rect, (320, 240), scale);
299
300        assert_ne!(range, source);
301        assert_ne!(range, full);
302        assert_eq!(range.identity(), None);
303    }
304}