1use cranpose_core::NodeId;
2use cranpose_ui_graphics::{Point, 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
30pub const LAYER_RASTER_CACHE_KIND_COUNT: usize = 5;
33
34pub const LAYER_RASTER_CACHE_KIND_LABELS: [&str; LAYER_RASTER_CACHE_KIND_COUNT] =
36 ["src", "backdrop", "range", "prefix", "effect"];
37
38#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
39enum LayerRasterCacheKind {
40 SourceContent,
41 BackdropEffect,
42 SceneRange,
43 PrefixSnapshot,
44 LayerEffect,
45}
46
47impl LayerRasterCacheKind {
48 fn identity_kind(self) -> u8 {
49 match self {
50 Self::SourceContent => 0,
51 Self::BackdropEffect => 1,
52 Self::SceneRange => 2,
53 Self::PrefixSnapshot => 3,
54 Self::LayerEffect => 4,
55 }
56 }
57}
58
59#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
60pub struct LayerRasterCacheIdentity {
61 stable_id: NodeId,
62 kind: u8,
63}
64
65#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
66pub struct LayerRasterCacheKey {
67 kind: LayerRasterCacheKind,
68 stable_id: Option<NodeId>,
69 content_hash: u64,
70 effect_hash: u64,
71 local_bounds_bits: [u32; 4],
72 pixel_size: [u32; 2],
73 scale_bucket: ScaleBucket,
74 device_phase_steps: [u32; 2],
75}
76
77const DEVICE_PHASE_STEPS: f32 = 16.0;
78
79fn device_phase_steps(phase: Point) -> [u32; 2] {
80 let steps = |value: f32| {
81 ((value.rem_euclid(1.0) * DEVICE_PHASE_STEPS).round() as u32) % DEVICE_PHASE_STEPS as u32
82 };
83 [steps(phase.x), steps(phase.y)]
84}
85
86fn local_bounds_bits(local_bounds: Rect) -> [u32; 4] {
87 [
88 local_bounds.x.to_bits(),
89 local_bounds.y.to_bits(),
90 local_bounds.width.to_bits(),
91 local_bounds.height.to_bits(),
92 ]
93}
94
95impl LayerRasterCacheKey {
96 pub fn source_content(
97 stable_id: Option<NodeId>,
98 content_hash: u64,
99 local_bounds: Rect,
100 pixel_size: (u32, u32),
101 scale_bucket: ScaleBucket,
102 device_phase: Point,
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 device_phase_steps: device_phase_steps(device_phase),
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::effect(
125 LayerRasterCacheKind::BackdropEffect,
126 stable_id,
127 input_hash,
128 effect_hash,
129 local_bounds,
130 pixel_size,
131 scale_bucket,
132 )
133 }
134
135 pub fn layer_effect(
139 stable_id: Option<NodeId>,
140 input_hash: u64,
141 effect_hash: u64,
142 local_bounds: Rect,
143 pixel_size: (u32, u32),
144 scale_bucket: ScaleBucket,
145 ) -> Self {
146 Self::effect(
147 LayerRasterCacheKind::LayerEffect,
148 stable_id,
149 input_hash,
150 effect_hash,
151 local_bounds,
152 pixel_size,
153 scale_bucket,
154 )
155 }
156
157 fn effect(
158 kind: LayerRasterCacheKind,
159 stable_id: Option<NodeId>,
160 input_hash: u64,
161 effect_hash: u64,
162 local_bounds: Rect,
163 pixel_size: (u32, u32),
164 scale_bucket: ScaleBucket,
165 ) -> Self {
166 Self {
167 kind,
168 stable_id,
169 content_hash: input_hash,
170 effect_hash,
171 local_bounds_bits: local_bounds_bits(local_bounds),
172 pixel_size: [pixel_size.0, pixel_size.1],
173 scale_bucket,
174 device_phase_steps: [0; 2],
175 }
176 }
177
178 pub fn scene_range(
179 content_hash: u64,
180 local_bounds: Rect,
181 pixel_size: (u32, u32),
182 scale_bucket: ScaleBucket,
183 ) -> Self {
184 Self {
185 kind: LayerRasterCacheKind::SceneRange,
186 stable_id: None,
187 content_hash,
188 effect_hash: 0,
189 local_bounds_bits: local_bounds_bits(local_bounds),
190 pixel_size: [pixel_size.0, pixel_size.1],
191 scale_bucket,
192 device_phase_steps: [0; 2],
193 }
194 }
195
196 pub fn prefix_snapshot(
202 content_hash: u64,
203 prefix_len: u64,
204 local_bounds: Rect,
205 pixel_size: (u32, u32),
206 scale_bucket: ScaleBucket,
207 ) -> Self {
208 Self {
209 kind: LayerRasterCacheKind::PrefixSnapshot,
210 stable_id: None,
211 content_hash,
212 effect_hash: prefix_len,
213 local_bounds_bits: local_bounds_bits(local_bounds),
214 pixel_size: [pixel_size.0, pixel_size.1],
215 scale_bucket,
216 device_phase_steps: [0; 2],
217 }
218 }
219
220 pub fn kind_slot(self) -> usize {
223 self.kind.identity_kind() as usize
224 }
225
226 pub fn stable_id(self) -> Option<NodeId> {
227 self.stable_id
228 }
229
230 pub fn is_scene_range(self) -> bool {
231 matches!(
232 self.kind,
233 LayerRasterCacheKind::SceneRange | LayerRasterCacheKind::PrefixSnapshot
234 )
235 }
236
237 pub fn is_source_content(self) -> bool {
238 self.kind == LayerRasterCacheKind::SourceContent
239 }
240
241 pub fn identity(self) -> Option<LayerRasterCacheIdentity> {
242 Some(LayerRasterCacheIdentity {
243 stable_id: self.stable_id?,
244 kind: self.kind.identity_kind(),
245 })
246 }
247
248 pub fn pixel_size(self) -> (u32, u32) {
249 (self.pixel_size[0], self.pixel_size[1])
250 }
251
252 pub fn local_bounds_bits(self) -> [u32; 4] {
255 self.local_bounds_bits
256 }
257
258 pub fn scale_bucket(self) -> ScaleBucket {
259 self.scale_bucket
260 }
261}
262
263#[cfg(test)]
264#[path = "tests/raster_cache_tests.rs"]
265mod tests;