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/// Hashes of a layer's content and of its render effect, which key its raster cache entries.
29#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
30pub struct LayerRasterCacheHashes {
31    /// Hash of what the layer draws into its target, as `LayerNode::target_content_hash`
32    /// returns it.
33    pub target_content: u64,
34    /// Hash of the layer's render effect.
35    pub effect: u64,
36}
37
38/// Number of distinct [`LayerRasterCacheKey`] kinds; see
39/// [`LayerRasterCacheKey::kind_slot`] and [`LAYER_RASTER_CACHE_KIND_LABELS`].
40pub const LAYER_RASTER_CACHE_KIND_COUNT: usize = 5;
41
42/// Short labels per kind slot, in [`LayerRasterCacheKey::kind_slot`] order.
43pub 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/// Identity of a node's cached raster of one kind. It stays the same while the content changes.
68#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
69pub struct LayerRasterCacheIdentity {
70    stable_id: NodeId,
71    kind: u8,
72}
73
74/// Key of one cached layer raster. It covers the kind of entry, the owning node, the content and
75/// effect hashes, the layer bounds, the pixel size, the raster scale and the sub-pixel phase.
76#[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    /// Key for the rasterized content of a layer. `content_hash` hashes the content, `local_bounds`
108    /// is the layer's bounds, `pixel_size` is the raster size in pixels, and `device_phase` is the
109    /// layer's sub-pixel position, kept in sixteenth-pixel steps.
110    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    /// Key for the output of a backdrop effect. `input_hash` hashes the backdrop that the effect
131    /// reads and `effect_hash` hashes the effect.
132    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    /// A layer's render effect applied over its retained surface: the output
152    /// is a pure function of the surface's content, the effect and the
153    /// layer's pixel rect within the surface.
154    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    /// Key for a cached range of the scene, identified by the hash of its content and owned by no
195    /// node.
196    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    /// A snapshot of the scene's rendered prefix: the bytes the target held
215    /// after drawing ops `[0, prefix_len)` over the pass's clear color. A
216    /// replay of captured bytes is identical to direct rendering by
217    /// construction — no flattening, so none of the chained-rounding
218    /// divergence flatten entries carry.
219    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    /// Index of this key's kind in `0..LAYER_RASTER_CACHE_KIND_COUNT`, for
239    /// per-kind accounting.
240    pub fn kind_slot(self) -> usize {
241        self.kind.identity_kind() as usize
242    }
243
244    /// The node that owns the entry, or `None` when the entry belongs to no single node.
245    pub fn stable_id(self) -> Option<NodeId> {
246        self.stable_id
247    }
248
249    /// Whether the key is for a scene range or a prefix snapshot.
250    pub fn is_scene_range(self) -> bool {
251        matches!(
252            self.kind,
253            LayerRasterCacheKind::SceneRange | LayerRasterCacheKind::PrefixSnapshot
254        )
255    }
256
257    /// Whether the key is for the rasterized content of a layer.
258    pub fn is_source_content(self) -> bool {
259        self.kind == LayerRasterCacheKind::SourceContent
260    }
261
262    /// The identity of the entry, or `None` when it has no owning node.
263    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    /// Raster size in pixels as width and height.
271    pub fn pixel_size(self) -> (u32, u32) {
272        (self.pixel_size[0], self.pixel_size[1])
273    }
274
275    /// The bit pattern of the entry's local bounds: the place a keyless entry
276    /// occupies, stable while its content changes.
277    pub fn local_bounds_bits(self) -> [u32; 4] {
278        self.local_bounds_bits
279    }
280
281    /// The device scale the raster is drawn at.
282    pub fn raster_scale(self) -> RasterScale {
283        self.raster_scale
284    }
285
286    /// Whether `other` rasterizes other content than this key: another
287    /// kind, node, content, effect or bounds. Keys that differ only in the
288    /// scale or the sub-pixel phase they are drawn at hold the same content.
289    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;