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
68impl LayerRasterCacheKey {
69 pub fn new(
70 stable_id: Option<NodeId>,
71 content_hash: u64,
72 effect_hash: u64,
73 local_bounds: Rect,
74 pixel_size: (u32, u32),
75 scale_bucket: ScaleBucket,
76 ) -> Self {
77 Self {
78 kind: LayerRasterCacheKind::FullSurface,
79 stable_id,
80 content_hash,
81 effect_hash,
82 local_bounds_bits: [
83 local_bounds.x.to_bits(),
84 local_bounds.y.to_bits(),
85 local_bounds.width.to_bits(),
86 local_bounds.height.to_bits(),
87 ],
88 pixel_size: [pixel_size.0, pixel_size.1],
89 scale_bucket,
90 }
91 }
92
93 pub fn source_content(
94 stable_id: Option<NodeId>,
95 content_hash: u64,
96 local_bounds: Rect,
97 pixel_size: (u32, u32),
98 scale_bucket: ScaleBucket,
99 ) -> Self {
100 Self {
101 kind: LayerRasterCacheKind::SourceContent,
102 stable_id,
103 content_hash,
104 effect_hash: 0,
105 local_bounds_bits: [
106 local_bounds.x.to_bits(),
107 local_bounds.y.to_bits(),
108 local_bounds.width.to_bits(),
109 local_bounds.height.to_bits(),
110 ],
111 pixel_size: [pixel_size.0, pixel_size.1],
112 scale_bucket,
113 }
114 }
115
116 pub fn backdrop_effect(
117 stable_id: Option<NodeId>,
118 input_hash: u64,
119 effect_hash: u64,
120 local_bounds: Rect,
121 pixel_size: (u32, u32),
122 scale_bucket: ScaleBucket,
123 ) -> Self {
124 Self {
125 kind: LayerRasterCacheKind::BackdropEffect,
126 stable_id,
127 content_hash: input_hash,
128 effect_hash,
129 local_bounds_bits: [
130 local_bounds.x.to_bits(),
131 local_bounds.y.to_bits(),
132 local_bounds.width.to_bits(),
133 local_bounds.height.to_bits(),
134 ],
135 pixel_size: [pixel_size.0, pixel_size.1],
136 scale_bucket,
137 }
138 }
139
140 pub fn scene_range(
141 content_hash: u64,
142 local_bounds: Rect,
143 pixel_size: (u32, u32),
144 scale_bucket: ScaleBucket,
145 ) -> Self {
146 Self {
147 kind: LayerRasterCacheKind::SceneRange,
148 stable_id: None,
149 content_hash,
150 effect_hash: 0,
151 local_bounds_bits: [
152 local_bounds.x.to_bits(),
153 local_bounds.y.to_bits(),
154 local_bounds.width.to_bits(),
155 local_bounds.height.to_bits(),
156 ],
157 pixel_size: [pixel_size.0, pixel_size.1],
158 scale_bucket,
159 }
160 }
161
162 pub fn prefix_snapshot(
168 content_hash: u64,
169 prefix_len: u64,
170 local_bounds: Rect,
171 pixel_size: (u32, u32),
172 scale_bucket: ScaleBucket,
173 ) -> Self {
174 Self {
175 kind: LayerRasterCacheKind::PrefixSnapshot,
176 stable_id: None,
177 content_hash,
178 effect_hash: prefix_len,
179 local_bounds_bits: [
180 local_bounds.x.to_bits(),
181 local_bounds.y.to_bits(),
182 local_bounds.width.to_bits(),
183 local_bounds.height.to_bits(),
184 ],
185 pixel_size: [pixel_size.0, pixel_size.1],
186 scale_bucket,
187 }
188 }
189
190 pub fn stable_id(self) -> Option<NodeId> {
191 self.stable_id
192 }
193
194 pub fn is_scene_range(self) -> bool {
195 matches!(
196 self.kind,
197 LayerRasterCacheKind::SceneRange | LayerRasterCacheKind::PrefixSnapshot
198 )
199 }
200
201 pub fn is_source_content(self) -> bool {
202 self.kind == LayerRasterCacheKind::SourceContent
203 }
204
205 pub fn identity(self) -> Option<LayerRasterCacheIdentity> {
206 Some(LayerRasterCacheIdentity {
207 stable_id: self.stable_id?,
208 kind: self.kind.identity_kind(),
209 })
210 }
211
212 pub fn pixel_size(self) -> (u32, u32) {
213 (self.pixel_size[0], self.pixel_size[1])
214 }
215
216 pub fn scale_bucket(self) -> ScaleBucket {
217 self.scale_bucket
218 }
219}
220
221#[cfg(test)]
222mod tests {
223 use super::*;
224
225 #[test]
226 fn scale_bucket_normalizes_invalid_values() {
227 assert_eq!(
228 ScaleBucket::from_scale(0.0).raw(),
229 ScaleBucket::from_scale(1.0).raw()
230 );
231 assert_eq!(
232 ScaleBucket::from_scale(-3.0).raw(),
233 ScaleBucket::from_scale(1.0).raw()
234 );
235 assert_eq!(
236 ScaleBucket::from_scale(f32::NAN).raw(),
237 ScaleBucket::from_scale(1.0).raw()
238 );
239 }
240
241 #[test]
242 fn scale_bucket_quantizes_small_fractional_changes() {
243 let a = ScaleBucket::from_scale(1.0);
244 let b = ScaleBucket::from_scale(1.001);
245 let c = ScaleBucket::from_scale(1.01);
246 assert_eq!(a, b);
247 assert_ne!(a, c);
248 }
249
250 #[test]
251 fn layer_raster_cache_key_captures_bounds_and_pixel_size() {
252 let rect = Rect {
253 x: 1.0,
254 y: 2.0,
255 width: 30.0,
256 height: 40.0,
257 };
258 let base = LayerRasterCacheKey::new(
259 Some(7),
260 11,
261 13,
262 rect,
263 (30, 40),
264 ScaleBucket::from_scale(1.0),
265 );
266 let moved = LayerRasterCacheKey::new(
267 Some(7),
268 11,
269 13,
270 Rect { x: 2.0, ..rect },
271 (30, 40),
272 ScaleBucket::from_scale(1.0),
273 );
274 let resized = LayerRasterCacheKey::new(
275 Some(7),
276 11,
277 13,
278 rect,
279 (60, 80),
280 ScaleBucket::from_scale(2.0),
281 );
282
283 assert_ne!(base, moved);
284 assert_ne!(base, resized);
285 assert_eq!(base.stable_id(), Some(7));
286 assert_eq!(base.pixel_size(), (30, 40));
287 }
288
289 #[test]
290 fn source_content_keys_do_not_collide_with_full_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 source = LayerRasterCacheKey::source_content(Some(7), 11, rect, (30, 40), scale);
299 let full = LayerRasterCacheKey::new(Some(7), 11, 0, rect, (30, 40), scale);
300
301 assert_ne!(source, full);
302 assert_ne!(source.identity(), full.identity());
303 }
304
305 #[test]
306 fn backdrop_effect_keys_do_not_collide_with_layer_surface_keys() {
307 let rect = Rect {
308 x: 1.0,
309 y: 2.0,
310 width: 30.0,
311 height: 40.0,
312 };
313 let scale = ScaleBucket::from_scale(1.0);
314 let backdrop = LayerRasterCacheKey::backdrop_effect(Some(7), 11, 13, rect, (30, 40), scale);
315 let source = LayerRasterCacheKey::source_content(Some(7), 11, rect, (30, 40), scale);
316 let full = LayerRasterCacheKey::new(Some(7), 11, 13, rect, (30, 40), scale);
317
318 assert_ne!(backdrop, source);
319 assert_ne!(backdrop, full);
320 assert_ne!(backdrop.identity(), source.identity());
321 assert_ne!(backdrop.identity(), full.identity());
322 }
323
324 #[test]
325 fn prefix_snapshot_keys_share_the_scene_range_partition_but_never_a_key() {
326 let rect = Rect {
327 x: 0.0,
328 y: 0.0,
329 width: 320.0,
330 height: 240.0,
331 };
332 let scale = ScaleBucket::from_scale(1.0);
333 let prefix = LayerRasterCacheKey::prefix_snapshot(11, 7, rect, (320, 240), scale);
334 let range = LayerRasterCacheKey::scene_range(11, rect, (320, 240), scale);
335 let longer = LayerRasterCacheKey::prefix_snapshot(11, 8, rect, (320, 240), scale);
336
337 assert!(prefix.is_scene_range());
338 assert_ne!(prefix, range);
339 assert_ne!(prefix, longer);
340 assert_eq!(prefix.identity(), None);
341 assert_eq!(prefix.pixel_size(), (320, 240));
342 }
343
344 #[test]
345 fn scene_range_keys_do_not_collide_with_layer_surface_keys() {
346 let rect = Rect {
347 x: 0.0,
348 y: 0.0,
349 width: 320.0,
350 height: 240.0,
351 };
352 let scale = ScaleBucket::from_scale(1.0);
353 let range = LayerRasterCacheKey::scene_range(11, rect, (320, 240), scale);
354 let source = LayerRasterCacheKey::source_content(None, 11, rect, (320, 240), scale);
355 let full = LayerRasterCacheKey::new(None, 11, 0, rect, (320, 240), scale);
356
357 assert_ne!(range, source);
358 assert_ne!(range, full);
359 assert_eq!(range.identity(), None);
360 }
361}