carnelian/render/
rive.rs

1// Copyright 2021 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::color::Color;
6use crate::render::{
7    BlendMode, Context as RenderContext, Fill, FillRule, Gradient, GradientType, Layer, Path,
8    PathBuilder, Raster, Style,
9};
10use crate::Point;
11use anyhow::{Context, Error};
12use euclid::{vec2, Transform2D};
13use fuchsia_trace::duration;
14use rive_rs::math::{self, Mat};
15use rive_rs::shapes::{Command, CommandPath};
16use rive_rs::{self as rive, ImportError, PaintColor, RenderPaint};
17use std::collections::HashMap;
18use std::fs;
19use std::num::NonZeroU64;
20
21pub fn load_rive<P: AsRef<std::path::Path> + std::fmt::Debug>(
22    path: P,
23) -> Result<rive::File, Error> {
24    let buffer = fs::read(&path).with_context(|| format!("Failed to read rive from {:?}", path))?;
25    let mut reader = rive::BinaryReader::new(&buffer);
26    let file = rive::File::import(&mut reader).map_err(|error| {
27        let context = match error {
28            ImportError::UnsupportedVersion => format!("Unsupported version: {:?}", path),
29            ImportError::Malformed => format!("Malformed: {:?}", path),
30        };
31        anyhow::anyhow!(context)
32    })?;
33
34    Ok(file)
35}
36
37#[derive(Clone, Debug)]
38struct CachedRaster {
39    raster: Raster,
40    inverted_transform: Mat,
41    was_used: bool,
42}
43
44#[derive(Debug)]
45pub struct RenderCache {
46    cached_rasters: HashMap<NonZeroU64, CachedRaster>,
47    tag: NonZeroU64,
48    pub layers: Vec<Layer>,
49}
50
51impl RenderCache {
52    pub fn new() -> Self {
53        Self {
54            cached_rasters: HashMap::new(),
55            tag: NonZeroU64::new(1).unwrap(),
56            layers: Vec::new(),
57        }
58    }
59
60    fn reset(&mut self) {
61        duration!(c"gfx", c"render::Rive::RenderCache::reset");
62
63        // Retain rasters used last frame and reset `was_used` field.
64        self.cached_rasters
65            .retain(|_, cached_raster| std::mem::replace(&mut cached_raster.was_used, false));
66    }
67
68    pub fn with_renderer(&mut self, context: &RenderContext, f: impl FnOnce(&mut Renderer<'_>)) {
69        duration!(c"gfx", c"render::Rive::RenderCache::with_renderer");
70
71        self.reset();
72        f(&mut Renderer { context, cache: self, clip: None });
73    }
74}
75
76#[derive(Debug)]
77pub struct Renderer<'c> {
78    context: &'c RenderContext,
79    cache: &'c mut RenderCache,
80    clip: Option<Raster>,
81}
82
83impl<'c> Renderer<'c> {
84    pub fn tag(&mut self) -> NonZeroU64 {
85        let tag = self.cache.tag;
86        self.cache.tag = NonZeroU64::new(tag.get().checked_add(1).unwrap_or(1)).unwrap();
87        tag
88    }
89}
90
91fn to_point(p: math::Vec) -> Point {
92    Point::new(p.x, p.y)
93}
94
95fn to_path(mut path_builder: PathBuilder, commands: &[Command]) -> Path {
96    let mut start_point = None;
97    let mut end_point = None;
98
99    for command in commands {
100        match *command {
101            Command::MoveTo(p) => {
102                if start_point != end_point {
103                    path_builder.line_to(to_point(start_point.unwrap()));
104                }
105                path_builder.move_to(to_point(p));
106                start_point = Some(p);
107                end_point = Some(p);
108            }
109            Command::LineTo(p) => {
110                path_builder.line_to(to_point(p));
111                end_point = Some(p);
112            }
113            Command::CubicTo(c0, c1, p) => {
114                path_builder.cubic_to(to_point(c0), to_point(c1), to_point(p));
115                end_point = Some(p);
116            }
117            Command::Close => {
118                if start_point != end_point {
119                    path_builder.line_to(to_point(start_point.unwrap()));
120                }
121                end_point = start_point;
122            }
123        }
124    }
125
126    if start_point != end_point {
127        path_builder.line_to(to_point(start_point.unwrap()));
128    }
129
130    path_builder.build()
131}
132
133impl rive::Renderer for Renderer<'_> {
134    fn draw(&mut self, path: &CommandPath, transform: Mat, paint: &RenderPaint) {
135        fn transform_translates_by_integers(transform: &Mat) -> bool {
136            fn approx_eq(a: f32, b: f32) -> bool {
137                (a - b).abs() < 0.001
138            }
139
140            approx_eq(transform.scale_x, 1.0)
141                && approx_eq(transform.shear_x, 0.0)
142                && approx_eq(transform.shear_y, 0.0)
143                && approx_eq(transform.scale_y, 1.0)
144                && transform.translate_x.fract().abs() < 0.001
145                && transform.translate_y.fract().abs() < 0.001
146        }
147
148        let raster = match path
149            .user_tag
150            .get()
151            .and_then(|tag| self.cache.cached_rasters.get_mut(&tag))
152            .and_then(|cached_raster| {
153                if cached_raster.was_used {
154                    return None;
155                }
156
157                cached_raster.was_used = true;
158                Some((&mut cached_raster.raster, transform * cached_raster.inverted_transform))
159            }) {
160            Some((raster, transform)) if transform_translates_by_integers(&transform) => {
161                *raster = raster.clone().translate(vec2(
162                    transform.translate_x.round() as i32,
163                    transform.translate_y.round() as i32,
164                ));
165                raster.clone()
166            }
167            _ => {
168                let render_path = to_path(self.context.path_builder().unwrap(), &path.commands);
169
170                let mut raster_builder = self.context.raster_builder().unwrap();
171
172                raster_builder.add_with_transform(
173                    &render_path,
174                    &Transform2D::new(
175                        transform.scale_x,
176                        transform.shear_y,
177                        transform.shear_x,
178                        transform.scale_y,
179                        transform.translate_x,
180                        transform.translate_y,
181                    ),
182                );
183
184                let raster = raster_builder.build();
185
186                if let Some(inverted_transform) = transform.invert() {
187                    let tag = self.tag();
188
189                    path.user_tag.set(Some(tag));
190                    self.cache.cached_rasters.insert(
191                        tag,
192                        CachedRaster { raster: raster.clone(), inverted_transform, was_used: true },
193                    );
194                }
195
196                raster
197            }
198        };
199
200        let blend_mode = match paint.blend_mode {
201            rive::shapes::paint::BlendMode::SrcOver => BlendMode::Over,
202            rive::shapes::paint::BlendMode::Screen => BlendMode::Screen,
203            rive::shapes::paint::BlendMode::Overlay => BlendMode::Overlay,
204            rive::shapes::paint::BlendMode::Darken => BlendMode::Darken,
205            rive::shapes::paint::BlendMode::Lighten => BlendMode::Lighten,
206            rive::shapes::paint::BlendMode::ColorDodge => BlendMode::ColorDodge,
207            rive::shapes::paint::BlendMode::ColorBurn => BlendMode::ColorBurn,
208            rive::shapes::paint::BlendMode::HardLight => BlendMode::HardLight,
209            rive::shapes::paint::BlendMode::SoftLight => BlendMode::SoftLight,
210            rive::shapes::paint::BlendMode::Difference => BlendMode::Difference,
211            rive::shapes::paint::BlendMode::Exclusion => BlendMode::Exclusion,
212            rive::shapes::paint::BlendMode::Multiply => BlendMode::Multiply,
213            rive::shapes::paint::BlendMode::Hue => BlendMode::Hue,
214            rive::shapes::paint::BlendMode::Saturation => BlendMode::Saturation,
215            rive::shapes::paint::BlendMode::Color => BlendMode::Color,
216            rive::shapes::paint::BlendMode::Luminosity => BlendMode::Luminosity,
217        };
218
219        let fill_rule = match paint.fill_rule {
220            rive::shapes::FillRule::NonZero => FillRule::NonZero,
221            rive::shapes::FillRule::EvenOdd => FillRule::EvenOdd,
222        };
223
224        fn to_color(color: &rive::shapes::paint::Color32) -> Color {
225            Color { r: color.red(), g: color.green(), b: color.blue(), a: color.alpha() }
226        }
227
228        let style = match &paint.color {
229            PaintColor::Solid(color) => {
230                Style { fill_rule, fill: Fill::Solid(to_color(color)), blend_mode }
231            }
232            PaintColor::Gradient(gradient) => {
233                let start = transform * gradient.start;
234                let end = transform * gradient.end;
235
236                Style {
237                    fill_rule,
238                    fill: Fill::Gradient(Gradient {
239                        r#type: match gradient.r#type {
240                            rive::GradientType::Linear => GradientType::Linear,
241                            rive::GradientType::Radial => GradientType::Radial,
242                        },
243                        start: Point::new(start.x, start.y),
244                        end: Point::new(end.x, end.y),
245                        stops: gradient
246                            .stops
247                            .iter()
248                            .map(|(color, stop)| (to_color(color), *stop))
249                            .collect::<Vec<_>>(),
250                    }),
251                    blend_mode,
252                }
253            }
254        };
255
256        self.cache.layers.push(Layer {
257            raster,
258            clip: paint.is_clipped.then(|| self.clip.clone()).flatten(),
259            style,
260        });
261    }
262
263    fn clip(&mut self, path: &CommandPath, transform: Mat, _: usize) {
264        fn transform_translates_by_integers(transform: &Mat) -> bool {
265            fn approx_eq(a: f32, b: f32) -> bool {
266                (a - b).abs() < 0.001
267            }
268
269            approx_eq(transform.scale_x, 1.0)
270                && approx_eq(transform.shear_x, 0.0)
271                && approx_eq(transform.shear_y, 0.0)
272                && approx_eq(transform.scale_y, 1.0)
273                && transform.translate_x.fract().abs() < 0.001
274                && transform.translate_y.fract().abs() < 0.001
275        }
276
277        let raster = match path
278            .user_tag
279            .get()
280            .and_then(|tag| self.cache.cached_rasters.get_mut(&tag))
281            .and_then(|cached_raster| {
282                if cached_raster.was_used {
283                    return None;
284                }
285
286                cached_raster.was_used = true;
287                Some((&mut cached_raster.raster, transform * cached_raster.inverted_transform))
288            }) {
289            Some((raster, transform)) if transform_translates_by_integers(&transform) => {
290                *raster = raster.clone().translate(vec2(
291                    transform.translate_x.round() as i32,
292                    transform.translate_y.round() as i32,
293                ));
294                raster.clone()
295            }
296            _ => {
297                let render_path = to_path(self.context.path_builder().unwrap(), &path.commands);
298
299                let mut raster_builder = self.context.raster_builder().unwrap();
300
301                raster_builder.add_with_transform(
302                    &render_path,
303                    &Transform2D::new(
304                        transform.scale_x,
305                        transform.shear_y,
306                        transform.shear_x,
307                        transform.scale_y,
308                        transform.translate_x,
309                        transform.translate_y,
310                    ),
311                );
312
313                let raster = raster_builder.build();
314
315                if let Some(inverted_transform) = transform.invert() {
316                    let tag = self.tag();
317
318                    path.user_tag.set(Some(tag));
319                    self.cache.cached_rasters.insert(
320                        tag,
321                        CachedRaster { raster: raster.clone(), inverted_transform, was_used: true },
322                    );
323                }
324
325                raster
326            }
327        };
328
329        self.clip = Some(raster);
330    }
331}