Skip to main content

cranpose_ui_graphics/
render_hash.rs

1use std::hash::{Hash, Hasher};
2
3use crate::{
4    Brush, Color, ColorFilter, CornerRadii, DrawPrimitive, FxHasher, ImageBitmap, LayerShape,
5    Point, Rect, RenderEffect, RuntimeShader, ShadowPrimitive, Stroke,
6};
7
8/// A hash of the data that decides how something renders, so a renderer can tell cheaply whether a
9/// cached result is still valid.
10pub trait RenderHash {
11    /// Returns a hash over exactly the values that change what is drawn. Equal values give equal
12    /// hashes.
13    fn render_hash(&self) -> u64;
14}
15
16impl RenderHash for Color {
17    fn render_hash(&self) -> u64 {
18        finish_hash(|state| hash_color(*self, state))
19    }
20}
21
22impl RenderHash for Point {
23    fn render_hash(&self) -> u64 {
24        finish_hash(|state| hash_point(*self, state))
25    }
26}
27
28impl RenderHash for Rect {
29    fn render_hash(&self) -> u64 {
30        finish_hash(|state| hash_rect(*self, state))
31    }
32}
33
34impl RenderHash for CornerRadii {
35    fn render_hash(&self) -> u64 {
36        finish_hash(|state| hash_corner_radii(*self, state))
37    }
38}
39
40impl RenderHash for LayerShape {
41    fn render_hash(&self) -> u64 {
42        finish_hash(|state| hash_layer_shape(*self, state))
43    }
44}
45
46impl RenderHash for Stroke {
47    fn render_hash(&self) -> u64 {
48        finish_hash(|state| hash_stroke(*self, state))
49    }
50}
51
52impl RenderHash for Brush {
53    fn render_hash(&self) -> u64 {
54        finish_hash(|state| hash_brush(self, state))
55    }
56}
57
58impl RenderHash for ColorFilter {
59    fn render_hash(&self) -> u64 {
60        finish_hash(|state| hash_color_filter(*self, state))
61    }
62}
63
64impl RenderHash for ImageBitmap {
65    fn render_hash(&self) -> u64 {
66        finish_hash(|state| self.id().hash(state))
67    }
68}
69
70impl RenderHash for RuntimeShader {
71    fn render_hash(&self) -> u64 {
72        finish_hash(|state| hash_runtime_shader(self, state))
73    }
74}
75
76impl RenderHash for RenderEffect {
77    fn render_hash(&self) -> u64 {
78        finish_hash(|state| hash_render_effect(self, state))
79    }
80}
81
82impl RenderHash for DrawPrimitive {
83    fn render_hash(&self) -> u64 {
84        finish_hash(|state| hash_draw_primitive(self, state))
85    }
86}
87
88impl RenderHash for ShadowPrimitive {
89    fn render_hash(&self) -> u64 {
90        finish_hash(|state| hash_shadow_primitive(self, state))
91    }
92}
93
94fn finish_hash(write: impl FnOnce(&mut FxHasher)) -> u64 {
95    let mut hasher = FxHasher::default();
96    write(&mut hasher);
97    hasher.finish()
98}
99
100fn hash_f32_bits<H: Hasher>(value: f32, state: &mut H) {
101    value.to_bits().hash(state);
102}
103
104fn hash_color<H: Hasher>(color: Color, state: &mut H) {
105    hash_f32_bits(color.0, state);
106    hash_f32_bits(color.1, state);
107    hash_f32_bits(color.2, state);
108    hash_f32_bits(color.3, state);
109}
110
111fn hash_point<H: Hasher>(point: Point, state: &mut H) {
112    hash_f32_bits(point.x, state);
113    hash_f32_bits(point.y, state);
114}
115
116fn hash_rect<H: Hasher>(rect: Rect, state: &mut H) {
117    hash_f32_bits(rect.x, state);
118    hash_f32_bits(rect.y, state);
119    hash_f32_bits(rect.width, state);
120    hash_f32_bits(rect.height, state);
121}
122
123fn hash_corner_radii<H: Hasher>(radii: CornerRadii, state: &mut H) {
124    hash_f32_bits(radii.top_left, state);
125    hash_f32_bits(radii.top_right, state);
126    hash_f32_bits(radii.bottom_right, state);
127    hash_f32_bits(radii.bottom_left, state);
128}
129
130/// Every stroke field feeds the hash: layer/scene-range surface caches key off
131/// content hashes, so a width/cap/join change that did not move the hash would
132/// replay a stale cached surface.
133fn hash_stroke<H: Hasher>(stroke: Stroke, state: &mut H) {
134    hash_f32_bits(stroke.width, state);
135    stroke.cap.hash(state);
136    stroke.join.hash(state);
137}
138
139fn hash_optional_stroke<H: Hasher>(stroke: Option<Stroke>, state: &mut H) {
140    match stroke {
141        Some(stroke) => {
142            1u8.hash(state);
143            hash_stroke(stroke, state);
144        }
145        None => 0u8.hash(state),
146    }
147}
148
149fn hash_layer_shape<H: Hasher>(shape: LayerShape, state: &mut H) {
150    match shape {
151        LayerShape::Rectangle => 0u8.hash(state),
152        LayerShape::Rounded(shape) => {
153            1u8.hash(state);
154            hash_corner_radii(shape.radii(), state);
155        }
156    }
157}
158
159fn hash_brush<H: Hasher>(brush: &Brush, state: &mut H) {
160    match brush {
161        Brush::Solid(color) => {
162            0u8.hash(state);
163            hash_color(*color, state);
164        }
165        Brush::LinearGradient {
166            colors,
167            stops,
168            start,
169            end,
170            tile_mode,
171        } => {
172            1u8.hash(state);
173            hash_color_slice(colors, state);
174            hash_optional_stop_list(stops.as_deref(), state);
175            hash_point(*start, state);
176            hash_point(*end, state);
177            tile_mode.hash(state);
178        }
179        Brush::RadialGradient {
180            colors,
181            stops,
182            center,
183            radius,
184            tile_mode,
185        } => {
186            2u8.hash(state);
187            hash_color_slice(colors, state);
188            hash_optional_stop_list(stops.as_deref(), state);
189            hash_point(*center, state);
190            hash_f32_bits(*radius, state);
191            tile_mode.hash(state);
192        }
193        Brush::SweepGradient {
194            colors,
195            stops,
196            center,
197        } => {
198            3u8.hash(state);
199            hash_color_slice(colors, state);
200            hash_optional_stop_list(stops.as_deref(), state);
201            hash_point(*center, state);
202        }
203    }
204}
205
206fn hash_color_slice<H: Hasher>(colors: &[Color], state: &mut H) {
207    colors.len().hash(state);
208    for color in colors {
209        hash_color(*color, state);
210    }
211}
212
213fn hash_optional_stop_list<H: Hasher>(stops: Option<&[f32]>, state: &mut H) {
214    match stops {
215        Some(stops) => {
216            1u8.hash(state);
217            stops.len().hash(state);
218            for stop in stops {
219                hash_f32_bits(*stop, state);
220            }
221        }
222        None => 0u8.hash(state),
223    }
224}
225
226fn hash_color_filter<H: Hasher>(filter: ColorFilter, state: &mut H) {
227    match filter {
228        ColorFilter::Tint(color) => {
229            0u8.hash(state);
230            hash_color(color, state);
231        }
232        ColorFilter::Modulate(color) => {
233            1u8.hash(state);
234            hash_color(color, state);
235        }
236        ColorFilter::Matrix(matrix) => {
237            2u8.hash(state);
238            for value in matrix {
239                hash_f32_bits(value, state);
240            }
241        }
242    }
243}
244
245fn hash_runtime_shader<H: Hasher>(shader: &RuntimeShader, state: &mut H) {
246    shader.source_hash().hash(state);
247    shader.overrides_hash().hash(state);
248    shader.position_independent().hash(state);
249    hash_f32_bits(shader.input_padding(), state);
250    hash_f32_bits(shader.output_padding(), state);
251    for rect in [shader.output_support(), shader.sample_domain()] {
252        match rect {
253            Some(rect) => {
254                1u8.hash(state);
255                hash_f32_bits(rect.x, state);
256                hash_f32_bits(rect.y, state);
257                hash_f32_bits(rect.width, state);
258                hash_f32_bits(rect.height, state);
259            }
260            None => 0u8.hash(state),
261        }
262    }
263    shader.hash_substrates(state);
264    shader.uniforms().len().hash(state);
265    for uniform in shader.uniforms() {
266        hash_f32_bits(*uniform, state);
267    }
268}
269
270fn hash_render_effect<H: Hasher>(effect: &RenderEffect, state: &mut H) {
271    match effect {
272        RenderEffect::Blur {
273            radius_x,
274            radius_y,
275            edge_treatment,
276        } => {
277            0u8.hash(state);
278            hash_f32_bits(*radius_x, state);
279            hash_f32_bits(*radius_y, state);
280            edge_treatment.hash(state);
281        }
282        RenderEffect::Offset { offset_x, offset_y } => {
283            1u8.hash(state);
284            hash_f32_bits(*offset_x, state);
285            hash_f32_bits(*offset_y, state);
286        }
287        RenderEffect::Shader { shader } => {
288            2u8.hash(state);
289            hash_runtime_shader(shader, state);
290        }
291        RenderEffect::Chain { first, second } => {
292            3u8.hash(state);
293            hash_render_effect(first, state);
294            hash_render_effect(second, state);
295        }
296    }
297}
298
299fn hash_draw_primitive<H: Hasher>(primitive: &DrawPrimitive, state: &mut H) {
300    match primitive {
301        DrawPrimitive::Content => {
302            0u8.hash(state);
303        }
304        DrawPrimitive::Blend {
305            primitive,
306            blend_mode,
307        } => {
308            1u8.hash(state);
309            blend_mode.hash(state);
310            hash_draw_primitive(primitive, state);
311        }
312        DrawPrimitive::Rect {
313            rect,
314            brush,
315            stroke,
316        } => {
317            2u8.hash(state);
318            hash_rect(*rect, state);
319            hash_brush(brush, state);
320            hash_optional_stroke(*stroke, state);
321        }
322        DrawPrimitive::RoundRect {
323            rect,
324            brush,
325            radii,
326            stroke,
327        } => {
328            3u8.hash(state);
329            hash_rect(*rect, state);
330            hash_brush(brush, state);
331            hash_corner_radii(*radii, state);
332            hash_optional_stroke(*stroke, state);
333        }
334        DrawPrimitive::Arc {
335            rect,
336            brush,
337            center,
338            radius,
339            start_angle,
340            sweep_angle,
341            stroke,
342            inner_radius,
343        } => {
344            6u8.hash(state);
345            hash_rect(*rect, state);
346            hash_brush(brush, state);
347            hash_point(*center, state);
348            hash_f32_bits(*radius, state);
349            hash_f32_bits(*start_angle, state);
350            hash_f32_bits(*sweep_angle, state);
351            hash_optional_stroke(*stroke, state);
352            hash_f32_bits(*inner_radius, state);
353        }
354        DrawPrimitive::Line {
355            rect,
356            brush,
357            start,
358            end,
359            stroke,
360        } => {
361            9u8.hash(state);
362            hash_rect(*rect, state);
363            hash_brush(brush, state);
364            hash_point(*start, state);
365            hash_point(*end, state);
366            hash_optional_stroke(Some(*stroke), state);
367        }
368        DrawPrimitive::Trapezoid {
369            rect,
370            brush,
371            trapezoid,
372        } => {
373            10u8.hash(state);
374            hash_rect(*rect, state);
375            hash_brush(brush, state);
376            for value in [trapezoid.left, trapezoid.right]
377                .into_iter()
378                .chain(trapezoid.top)
379                .chain(trapezoid.bottom)
380            {
381                hash_f32_bits(value, state);
382            }
383            [trapezoid.open_left, trapezoid.open_right].hash(state);
384        }
385        DrawPrimitive::Image {
386            rect,
387            image,
388            alpha,
389            color_filter,
390            sampling,
391            src_rect,
392        } => {
393            4u8.hash(state);
394            hash_rect(*rect, state);
395            image.id().hash(state);
396            hash_f32_bits(*alpha, state);
397            sampling.hash(state);
398            match color_filter {
399                Some(filter) => {
400                    1u8.hash(state);
401                    hash_color_filter(*filter, state);
402                }
403                None => 0u8.hash(state),
404            }
405            match src_rect {
406                Some(rect) => {
407                    1u8.hash(state);
408                    hash_rect(*rect, state);
409                }
410                None => 0u8.hash(state),
411            }
412        }
413        DrawPrimitive::Text(text) => {
414            7u8.hash(state);
415            hash_rect(text.rect, state);
416            text.text.hash(state);
417            hash_text_style(&text.style, state);
418            hash_color(text.color, state);
419        }
420        DrawPrimitive::Shadow(shadow) => {
421            5u8.hash(state);
422            hash_shadow_primitive(shadow, state);
423        }
424    }
425}
426
427fn hash_text_style<H: Hasher>(style: &crate::DrawTextStyle, state: &mut H) {
428    style.font_family.hash(state);
429    hash_f32_bits(style.font_size, state);
430    style.font_weight.hash(state);
431    style.font_style.hash(state);
432    hash_f32_bits(style.letter_spacing, state);
433    match style.line_height {
434        Some(line_height) => {
435            1u8.hash(state);
436            hash_f32_bits(line_height, state);
437        }
438        None => 0u8.hash(state),
439    }
440    style.align.hash(state);
441    style.vertical_align.hash(state);
442}
443
444fn hash_shadow_primitive<H: Hasher>(shadow: &ShadowPrimitive, state: &mut H) {
445    match shadow {
446        ShadowPrimitive::Drop {
447            shape,
448            cutout,
449            blur_radius,
450            blend_mode,
451        } => {
452            0u8.hash(state);
453            hash_draw_primitive(shape, state);
454            match cutout {
455                Some(cutout) => {
456                    1u8.hash(state);
457                    hash_draw_primitive(cutout, state);
458                }
459                None => 0u8.hash(state),
460            }
461            hash_f32_bits(*blur_radius, state);
462            blend_mode.hash(state);
463        }
464        ShadowPrimitive::Inner {
465            fill,
466            cutout,
467            blur_radius,
468            blend_mode,
469            clip_rect,
470        } => {
471            1u8.hash(state);
472            hash_draw_primitive(fill, state);
473            hash_draw_primitive(cutout, state);
474            hash_f32_bits(*blur_radius, state);
475            blend_mode.hash(state);
476            hash_rect(*clip_rect, state);
477        }
478    }
479}
480
481#[cfg(test)]
482#[path = "tests/render_hash_tests.rs"]
483mod tests;