1use cranpose_core::NodeId;
2use cranpose_ui_graphics::{Point, Rect};
3
4#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
8pub struct RasterScale(u32);
9
10impl RasterScale {
11 pub fn from_scale(scale: f32) -> Self {
14 let normalized = if scale.is_finite() && scale > 0.0 {
15 scale
16 } else {
17 1.0
18 };
19 Self(normalized.to_bits())
20 }
21
22 pub fn raw(self) -> u32 {
24 self.0
25 }
26}
27
28#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
30pub struct LayerRasterCacheHashes {
31 pub target_content: u64,
34 pub effect: u64,
36}
37
38pub const LAYER_RASTER_CACHE_KIND_COUNT: usize = 5;
41
42pub const LAYER_RASTER_CACHE_KIND_LABELS: [&str; LAYER_RASTER_CACHE_KIND_COUNT] =
44 ["src", "backdrop", "range", "prefix", "effect"];
45
46#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
47enum LayerRasterCacheKind {
48 SourceContent,
49 BackdropEffect,
50 SceneRange,
51 PrefixSnapshot,
52 LayerEffect,
53}
54
55impl LayerRasterCacheKind {
56 fn identity_kind(self) -> u8 {
57 match self {
58 Self::SourceContent => 0,
59 Self::BackdropEffect => 1,
60 Self::SceneRange => 2,
61 Self::PrefixSnapshot => 3,
62 Self::LayerEffect => 4,
63 }
64 }
65}
66
67#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
69pub struct LayerRasterCacheIdentity {
70 stable_id: NodeId,
71 kind: u8,
72}
73
74#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
77pub struct LayerRasterCacheKey {
78 kind: LayerRasterCacheKind,
79 stable_id: Option<NodeId>,
80 content_hash: u64,
81 effect_hash: u64,
82 local_bounds_bits: [u32; 4],
83 pixel_size: [u32; 2],
84 raster_scale: RasterScale,
85 device_phase_steps: [u32; 2],
86}
87
88const DEVICE_PHASE_STEPS: f32 = 16.0;
89
90fn device_phase_steps(phase: Point) -> [u32; 2] {
91 let steps = |value: f32| {
92 ((value.rem_euclid(1.0) * DEVICE_PHASE_STEPS).round() as u32) % DEVICE_PHASE_STEPS as u32
93 };
94 [steps(phase.x), steps(phase.y)]
95}
96
97fn local_bounds_bits(local_bounds: Rect) -> [u32; 4] {
98 [
99 local_bounds.x.to_bits(),
100 local_bounds.y.to_bits(),
101 local_bounds.width.to_bits(),
102 local_bounds.height.to_bits(),
103 ]
104}
105
106impl LayerRasterCacheKey {
107 pub fn source_content(
111 stable_id: Option<NodeId>,
112 content_hash: u64,
113 local_bounds: Rect,
114 pixel_size: (u32, u32),
115 raster_scale: RasterScale,
116 device_phase: Point,
117 ) -> Self {
118 Self {
119 kind: LayerRasterCacheKind::SourceContent,
120 stable_id,
121 content_hash,
122 effect_hash: 0,
123 local_bounds_bits: local_bounds_bits(local_bounds),
124 pixel_size: [pixel_size.0, pixel_size.1],
125 raster_scale,
126 device_phase_steps: device_phase_steps(device_phase),
127 }
128 }
129
130 pub fn backdrop_effect(
133 stable_id: Option<NodeId>,
134 input_hash: u64,
135 effect_hash: u64,
136 local_bounds: Rect,
137 pixel_size: (u32, u32),
138 raster_scale: RasterScale,
139 ) -> Self {
140 Self::effect(
141 LayerRasterCacheKind::BackdropEffect,
142 stable_id,
143 input_hash,
144 effect_hash,
145 local_bounds,
146 pixel_size,
147 raster_scale,
148 )
149 }
150
151 pub fn layer_effect(
155 stable_id: Option<NodeId>,
156 input_hash: u64,
157 effect_hash: u64,
158 local_bounds: Rect,
159 pixel_size: (u32, u32),
160 raster_scale: RasterScale,
161 ) -> Self {
162 Self::effect(
163 LayerRasterCacheKind::LayerEffect,
164 stable_id,
165 input_hash,
166 effect_hash,
167 local_bounds,
168 pixel_size,
169 raster_scale,
170 )
171 }
172
173 fn effect(
174 kind: LayerRasterCacheKind,
175 stable_id: Option<NodeId>,
176 input_hash: u64,
177 effect_hash: u64,
178 local_bounds: Rect,
179 pixel_size: (u32, u32),
180 raster_scale: RasterScale,
181 ) -> Self {
182 Self {
183 kind,
184 stable_id,
185 content_hash: input_hash,
186 effect_hash,
187 local_bounds_bits: local_bounds_bits(local_bounds),
188 pixel_size: [pixel_size.0, pixel_size.1],
189 raster_scale,
190 device_phase_steps: [0; 2],
191 }
192 }
193
194 pub fn scene_range(
197 content_hash: u64,
198 local_bounds: Rect,
199 pixel_size: (u32, u32),
200 raster_scale: RasterScale,
201 ) -> Self {
202 Self {
203 kind: LayerRasterCacheKind::SceneRange,
204 stable_id: None,
205 content_hash,
206 effect_hash: 0,
207 local_bounds_bits: local_bounds_bits(local_bounds),
208 pixel_size: [pixel_size.0, pixel_size.1],
209 raster_scale,
210 device_phase_steps: [0; 2],
211 }
212 }
213
214 pub fn prefix_snapshot(
220 content_hash: u64,
221 prefix_len: u64,
222 local_bounds: Rect,
223 pixel_size: (u32, u32),
224 raster_scale: RasterScale,
225 ) -> Self {
226 Self {
227 kind: LayerRasterCacheKind::PrefixSnapshot,
228 stable_id: None,
229 content_hash,
230 effect_hash: prefix_len,
231 local_bounds_bits: local_bounds_bits(local_bounds),
232 pixel_size: [pixel_size.0, pixel_size.1],
233 raster_scale,
234 device_phase_steps: [0; 2],
235 }
236 }
237
238 pub fn kind_slot(self) -> usize {
241 self.kind.identity_kind() as usize
242 }
243
244 pub fn stable_id(self) -> Option<NodeId> {
246 self.stable_id
247 }
248
249 pub fn is_scene_range(self) -> bool {
251 matches!(
252 self.kind,
253 LayerRasterCacheKind::SceneRange | LayerRasterCacheKind::PrefixSnapshot
254 )
255 }
256
257 pub fn is_source_content(self) -> bool {
259 self.kind == LayerRasterCacheKind::SourceContent
260 }
261
262 pub fn identity(self) -> Option<LayerRasterCacheIdentity> {
264 Some(LayerRasterCacheIdentity {
265 stable_id: self.stable_id?,
266 kind: self.kind.identity_kind(),
267 })
268 }
269
270 pub fn pixel_size(self) -> (u32, u32) {
272 (self.pixel_size[0], self.pixel_size[1])
273 }
274
275 pub fn local_bounds_bits(self) -> [u32; 4] {
278 self.local_bounds_bits
279 }
280
281 pub fn raster_scale(self) -> RasterScale {
283 self.raster_scale
284 }
285
286 pub fn draws_other_content(self, other: Self) -> bool {
290 Self {
291 pixel_size: other.pixel_size,
292 raster_scale: other.raster_scale,
293 device_phase_steps: other.device_phase_steps,
294 ..self
295 } != other
296 }
297}
298
299#[cfg(test)]
300#[path = "tests/raster_cache_tests.rs"]
301mod tests;