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