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}
35
36#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
37pub struct LayerRasterCacheKey {
38    kind: LayerRasterCacheKind,
39    stable_id: Option<NodeId>,
40    content_hash: u64,
41    effect_hash: u64,
42    local_bounds_bits: [u32; 4],
43    pixel_size: [u32; 2],
44    scale_bucket: ScaleBucket,
45}
46
47impl LayerRasterCacheKey {
48    pub fn new(
49        stable_id: Option<NodeId>,
50        content_hash: u64,
51        effect_hash: u64,
52        local_bounds: Rect,
53        pixel_size: (u32, u32),
54        scale_bucket: ScaleBucket,
55    ) -> Self {
56        Self {
57            kind: LayerRasterCacheKind::FullSurface,
58            stable_id,
59            content_hash,
60            effect_hash,
61            local_bounds_bits: [
62                local_bounds.x.to_bits(),
63                local_bounds.y.to_bits(),
64                local_bounds.width.to_bits(),
65                local_bounds.height.to_bits(),
66            ],
67            pixel_size: [pixel_size.0, pixel_size.1],
68            scale_bucket,
69        }
70    }
71
72    pub fn source_content(
73        stable_id: Option<NodeId>,
74        content_hash: u64,
75        local_bounds: Rect,
76        pixel_size: (u32, u32),
77        scale_bucket: ScaleBucket,
78    ) -> Self {
79        Self {
80            kind: LayerRasterCacheKind::SourceContent,
81            stable_id,
82            content_hash,
83            effect_hash: 0,
84            local_bounds_bits: [
85                local_bounds.x.to_bits(),
86                local_bounds.y.to_bits(),
87                local_bounds.width.to_bits(),
88                local_bounds.height.to_bits(),
89            ],
90            pixel_size: [pixel_size.0, pixel_size.1],
91            scale_bucket,
92        }
93    }
94
95    pub fn stable_id(self) -> Option<NodeId> {
96        self.stable_id
97    }
98
99    pub fn pixel_size(self) -> (u32, u32) {
100        (self.pixel_size[0], self.pixel_size[1])
101    }
102
103    pub fn scale_bucket(self) -> ScaleBucket {
104        self.scale_bucket
105    }
106}
107
108#[cfg(test)]
109mod tests {
110    use super::*;
111
112    #[test]
113    fn scale_bucket_normalizes_invalid_values() {
114        assert_eq!(
115            ScaleBucket::from_scale(0.0).raw(),
116            ScaleBucket::from_scale(1.0).raw()
117        );
118        assert_eq!(
119            ScaleBucket::from_scale(-3.0).raw(),
120            ScaleBucket::from_scale(1.0).raw()
121        );
122        assert_eq!(
123            ScaleBucket::from_scale(f32::NAN).raw(),
124            ScaleBucket::from_scale(1.0).raw()
125        );
126    }
127
128    #[test]
129    fn scale_bucket_quantizes_small_fractional_changes() {
130        let a = ScaleBucket::from_scale(1.0);
131        let b = ScaleBucket::from_scale(1.001);
132        let c = ScaleBucket::from_scale(1.01);
133        assert_eq!(a, b);
134        assert_ne!(a, c);
135    }
136
137    #[test]
138    fn layer_raster_cache_key_captures_bounds_and_pixel_size() {
139        let rect = Rect {
140            x: 1.0,
141            y: 2.0,
142            width: 30.0,
143            height: 40.0,
144        };
145        let base = LayerRasterCacheKey::new(
146            Some(7),
147            11,
148            13,
149            rect,
150            (30, 40),
151            ScaleBucket::from_scale(1.0),
152        );
153        let moved = LayerRasterCacheKey::new(
154            Some(7),
155            11,
156            13,
157            Rect { x: 2.0, ..rect },
158            (30, 40),
159            ScaleBucket::from_scale(1.0),
160        );
161        let resized = LayerRasterCacheKey::new(
162            Some(7),
163            11,
164            13,
165            rect,
166            (60, 80),
167            ScaleBucket::from_scale(2.0),
168        );
169
170        assert_ne!(base, moved);
171        assert_ne!(base, resized);
172        assert_eq!(base.stable_id(), Some(7));
173        assert_eq!(base.pixel_size(), (30, 40));
174    }
175
176    #[test]
177    fn source_content_keys_do_not_collide_with_full_surface_keys() {
178        let rect = Rect {
179            x: 1.0,
180            y: 2.0,
181            width: 30.0,
182            height: 40.0,
183        };
184        let scale = ScaleBucket::from_scale(1.0);
185        let source = LayerRasterCacheKey::source_content(Some(7), 11, rect, (30, 40), scale);
186        let full = LayerRasterCacheKey::new(Some(7), 11, 0, rect, (30, 40), scale);
187
188        assert_ne!(source, full);
189    }
190}