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