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}