Skip to main content

cranpose_render_common/
raster_cache.rs

1use cranpose_core::NodeId;
2use cranpose_ui_graphics::{Point, Rect};
3
4/// The device scale a layer raster is drawn at, compared exactly: a raster
5/// drawn at one scale places its edges and glyphs where no other scale does,
6/// so it serves only that scale.
7#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
8pub struct RasterScale(u32);
9
10impl RasterScale {
11    /// The raster scale of `scale`; a scale no raster can be drawn at (zero,
12    /// negative or not finite) is the unit scale.
13    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    /// The scale's bit pattern.
23    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
34/// Number of distinct [`LayerRasterCacheKey`] kinds; see
35/// [`LayerRasterCacheKey::kind_slot`] and [`LAYER_RASTER_CACHE_KIND_LABELS`].
36pub const LAYER_RASTER_CACHE_KIND_COUNT: usize = 5;
37
38/// Short labels per kind slot, in [`LayerRasterCacheKey::kind_slot`] order.
39pub 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    /// A layer's render effect applied over its retained surface: the output
140    /// is a pure function of the surface's content, the effect and the
141    /// layer's pixel rect within the surface.
142    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    /// A snapshot of the scene's rendered prefix: the bytes the target held
201    /// after drawing ops `[0, prefix_len)` over the pass's clear color. A
202    /// replay of captured bytes is identical to direct rendering by
203    /// construction — no flattening, so none of the chained-rounding
204    /// divergence flatten entries carry.
205    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    /// Index of this key's kind in `0..LAYER_RASTER_CACHE_KIND_COUNT`, for
225    /// per-kind accounting.
226    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    /// The bit pattern of the entry's local bounds: the place a keyless entry
257    /// occupies, stable while its content changes.
258    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;