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)]
31pub struct LayerRasterCacheKey {
32    stable_id: Option<NodeId>,
33    content_hash: u64,
34    effect_hash: u64,
35    local_bounds_bits: [u32; 4],
36    pixel_size: [u32; 2],
37    scale_bucket: ScaleBucket,
38}
39
40impl LayerRasterCacheKey {
41    pub fn new(
42        stable_id: Option<NodeId>,
43        content_hash: u64,
44        effect_hash: u64,
45        local_bounds: Rect,
46        pixel_size: (u32, u32),
47        scale_bucket: ScaleBucket,
48    ) -> Self {
49        Self {
50            stable_id,
51            content_hash,
52            effect_hash,
53            local_bounds_bits: [
54                local_bounds.x.to_bits(),
55                local_bounds.y.to_bits(),
56                local_bounds.width.to_bits(),
57                local_bounds.height.to_bits(),
58            ],
59            pixel_size: [pixel_size.0, pixel_size.1],
60            scale_bucket,
61        }
62    }
63
64    pub fn stable_id(self) -> Option<NodeId> {
65        self.stable_id
66    }
67
68    pub fn pixel_size(self) -> (u32, u32) {
69        (self.pixel_size[0], self.pixel_size[1])
70    }
71
72    pub fn scale_bucket(self) -> ScaleBucket {
73        self.scale_bucket
74    }
75}
76
77#[cfg(test)]
78mod tests {
79    use super::*;
80
81    #[test]
82    fn scale_bucket_normalizes_invalid_values() {
83        assert_eq!(
84            ScaleBucket::from_scale(0.0).raw(),
85            ScaleBucket::from_scale(1.0).raw()
86        );
87        assert_eq!(
88            ScaleBucket::from_scale(-3.0).raw(),
89            ScaleBucket::from_scale(1.0).raw()
90        );
91        assert_eq!(
92            ScaleBucket::from_scale(f32::NAN).raw(),
93            ScaleBucket::from_scale(1.0).raw()
94        );
95    }
96
97    #[test]
98    fn scale_bucket_quantizes_small_fractional_changes() {
99        let a = ScaleBucket::from_scale(1.0);
100        let b = ScaleBucket::from_scale(1.001);
101        let c = ScaleBucket::from_scale(1.01);
102        assert_eq!(a, b);
103        assert_ne!(a, c);
104    }
105
106    #[test]
107    fn layer_raster_cache_key_captures_bounds_and_pixel_size() {
108        let rect = Rect {
109            x: 1.0,
110            y: 2.0,
111            width: 30.0,
112            height: 40.0,
113        };
114        let base = LayerRasterCacheKey::new(
115            Some(7),
116            11,
117            13,
118            rect,
119            (30, 40),
120            ScaleBucket::from_scale(1.0),
121        );
122        let moved = LayerRasterCacheKey::new(
123            Some(7),
124            11,
125            13,
126            Rect { x: 2.0, ..rect },
127            (30, 40),
128            ScaleBucket::from_scale(1.0),
129        );
130        let resized = LayerRasterCacheKey::new(
131            Some(7),
132            11,
133            13,
134            rect,
135            (60, 80),
136            ScaleBucket::from_scale(2.0),
137        );
138
139        assert_ne!(base, moved);
140        assert_ne!(base, resized);
141        assert_eq!(base.stable_id(), Some(7));
142        assert_eq!(base.pixel_size(), (30, 40));
143    }
144}