Skip to main content

cranpose_render_common/
raster_cache.rs

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
30/// Number of distinct [`LayerRasterCacheKey`] kinds; see
31/// [`LayerRasterCacheKey::kind_slot`] and [`LAYER_RASTER_CACHE_KIND_LABELS`].
32pub const LAYER_RASTER_CACHE_KIND_COUNT: usize = 5;
33
34/// Short labels per kind slot, in [`LayerRasterCacheKey::kind_slot`] order.
35pub 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    /// A layer's render effect applied over its retained surface: the output
136    /// is a pure function of the surface's content, the effect and the
137    /// layer's pixel rect within the surface.
138    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    /// A snapshot of the scene's rendered prefix: the bytes the target held
197    /// after drawing ops `[0, prefix_len)` over the pass's clear color. A
198    /// replay of captured bytes is identical to direct rendering by
199    /// construction — no flattening, so none of the chained-rounding
200    /// divergence flatten entries carry.
201    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    /// Index of this key's kind in `0..LAYER_RASTER_CACHE_KIND_COUNT`, for
221    /// per-kind accounting.
222    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    /// The bit pattern of the entry's local bounds: the place a keyless entry
253    /// occupies, stable while its content changes.
254    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;