Skip to main content

floem_vello_renderer/
lib.rs

1use std::collections::HashMap;
2use std::mem;
3use std::num::NonZero;
4use std::sync::Arc;
5use std::sync::mpsc::sync_channel;
6
7use anyhow::Result;
8use floem_renderer::gpu_resources::GpuResources;
9use floem_renderer::text::{Glyph, GlyphRunProps};
10use floem_renderer::{Img, Renderer};
11use peniko::kurbo::Size;
12use peniko::{
13    Blob, BrushRef,
14    color::palette,
15    kurbo::{Affine, Point, Rect, Shape},
16};
17use peniko::{Compose, Fill, ImageAlphaType, ImageData, Mix};
18use vello::kurbo::Stroke;
19use vello::util::RenderSurface;
20use vello::wgpu::Device;
21use vello::{AaConfig, RendererOptions, Scene};
22use wgpu::util::TextureBlitter;
23use wgpu::{Adapter, DeviceType, Queue, TextureAspect, TextureFormat};
24
25pub struct VelloRenderer {
26    device: Device,
27    #[allow(unused)]
28    queue: Queue,
29    surface: RenderSurface<'static>,
30    renderer: vello::Renderer,
31    scene: Scene,
32    alt_scene: Option<Scene>,
33    window_scale: f64,
34    transform: Affine,
35    capture: bool,
36    adapter: Adapter,
37    // TODO: Apply once vello's DrawGlyphs gains embolden support.
38    #[allow(dead_code)]
39    font_embolden: f32,
40    /// Cached vello scenes keyed by SVG content hash.
41    /// The bool tracks the current generation for eviction.
42    svg_cache: HashMap<Vec<u8>, (bool, Scene)>,
43    /// Current cache generation; toggled each frame so stale entries are evicted.
44    cache_generation: bool,
45}
46
47impl VelloRenderer {
48    fn device_transform(&self) -> Affine {
49        self.transform
50    }
51
52    pub fn new(
53        gpu_resources: GpuResources,
54        surface: wgpu::Surface<'static>,
55        width: u32,
56        height: u32,
57        scale: f64,
58        font_embolden: f32,
59    ) -> Result<Self> {
60        let GpuResources {
61            adapter,
62            device,
63            queue,
64            ..
65        } = gpu_resources;
66
67        if adapter.get_info().device_type == DeviceType::Cpu {
68            return Err(anyhow::anyhow!("only cpu adapter found"));
69        }
70
71        let mut required_downlevel_flags = wgpu::DownlevelFlags::empty();
72        required_downlevel_flags.set(wgpu::DownlevelFlags::VERTEX_STORAGE, true);
73
74        if !adapter
75            .get_downlevel_capabilities()
76            .flags
77            .contains(required_downlevel_flags)
78        {
79            return Err(anyhow::anyhow!(
80                "adapter doesn't support required downlevel flags"
81            ));
82        }
83
84        let surface_caps = surface.get_capabilities(&adapter);
85        let texture_format = surface_caps
86            .formats
87            .into_iter()
88            .find(|it| matches!(it, TextureFormat::Rgba8Unorm | TextureFormat::Bgra8Unorm))
89            .ok_or_else(|| anyhow::anyhow!("surface should support Rgba8Unorm or Bgra8Unorm"))?;
90
91        let latency = match adapter.get_info().backend {
92            wgpu::Backend::Vulkan => 2,
93            _ => 1,
94        };
95
96        let config = wgpu::SurfaceConfiguration {
97            usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
98            format: texture_format,
99            width,
100            height,
101            present_mode: wgpu::PresentMode::AutoVsync,
102            alpha_mode: wgpu::CompositeAlphaMode::Auto,
103            view_formats: vec![],
104            desired_maximum_frame_latency: latency,
105        };
106
107        surface.configure(&device, &config);
108
109        let target_texture = device.create_texture(&wgpu::TextureDescriptor {
110            label: None,
111            size: wgpu::Extent3d {
112                width,
113                height,
114                depth_or_array_layers: 1,
115            },
116            mip_level_count: 1,
117            sample_count: 1,
118            dimension: wgpu::TextureDimension::D2,
119            usage: wgpu::TextureUsages::STORAGE_BINDING | wgpu::TextureUsages::TEXTURE_BINDING,
120            format: TextureFormat::Rgba8Unorm,
121            view_formats: &[],
122        });
123
124        let target_view = target_texture.create_view(&wgpu::TextureViewDescriptor::default());
125
126        let render_surface = RenderSurface {
127            surface,
128            config,
129            dev_id: 0,
130            format: texture_format,
131            target_texture,
132            target_view,
133            blitter: TextureBlitter::new(&device, texture_format),
134        };
135
136        let scene = Scene::new();
137        let renderer = vello::Renderer::new(
138            &device,
139            RendererOptions {
140                pipeline_cache: None,
141                use_cpu: false,
142                antialiasing_support: vello::AaSupport::all(),
143                num_init_threads: Some(NonZero::new(1).unwrap()),
144            },
145        )
146        .unwrap();
147
148        Ok(Self {
149            device,
150            queue,
151            surface: render_surface,
152            renderer,
153            scene,
154            alt_scene: None,
155            window_scale: scale,
156            transform: Affine::IDENTITY,
157            capture: false,
158            adapter,
159            font_embolden,
160            svg_cache: HashMap::new(),
161            cache_generation: false,
162        })
163    }
164
165    pub fn resize(&mut self, width: u32, height: u32, scale: f64) {
166        if width != self.surface.config.width || height != self.surface.config.height {
167            let target_texture = self.device.create_texture(&wgpu::TextureDescriptor {
168                label: None,
169                size: wgpu::Extent3d {
170                    width,
171                    height,
172                    depth_or_array_layers: 1,
173                },
174                mip_level_count: 1,
175                sample_count: 1,
176                dimension: wgpu::TextureDimension::D2,
177                usage: wgpu::TextureUsages::STORAGE_BINDING | wgpu::TextureUsages::TEXTURE_BINDING,
178                format: TextureFormat::Rgba8Unorm,
179                view_formats: &[],
180            });
181            let target_view = target_texture.create_view(&wgpu::TextureViewDescriptor::default());
182            self.surface.target_texture = target_texture;
183            self.surface.target_view = target_view;
184            self.surface.config.width = width;
185            self.surface.config.height = height;
186            self.surface
187                .surface
188                .configure(&self.device, &self.surface.config);
189        }
190        self.window_scale = scale;
191    }
192
193    pub const fn set_scale(&mut self, scale: f64) {
194        self.window_scale = scale;
195    }
196
197    pub const fn size(&self) -> Size {
198        Size::new(
199            self.surface.config.width as f64,
200            self.surface.config.height as f64,
201        )
202    }
203}
204
205impl Renderer for VelloRenderer {
206    fn begin(&mut self, capture: bool) {
207        if self.capture == capture {
208            self.scene.reset();
209        } else {
210            self.capture = capture;
211            if self.alt_scene.is_none() {
212                self.alt_scene = Some(Scene::new());
213            }
214            if let Some(scene) = self.alt_scene.as_mut() {
215                scene.reset();
216            }
217            self.scene.reset();
218            mem::swap(&mut self.scene, self.alt_scene.as_mut().unwrap());
219        }
220        self.transform = Affine::IDENTITY;
221
222        // Evict SVG scenes not used in the previous frame, then flip generation.
223        let generation = self.cache_generation;
224        self.svg_cache.retain(|_, (g, _)| *g == generation);
225        self.cache_generation = !generation;
226    }
227
228    fn stroke<'b, 's>(
229        &mut self,
230        shape: &impl Shape,
231        brush: impl Into<BrushRef<'b>>,
232        stroke: &'s Stroke,
233    ) {
234        self.scene
235            .stroke(stroke, self.device_transform(), brush, None, shape);
236    }
237
238    fn fill<'b>(&mut self, path: &impl Shape, brush: impl Into<BrushRef<'b>>, blur_radius: f64) {
239        let brush: BrushRef<'b> = brush.into();
240
241        // For solid colors with specific shapes, use optimized methods
242        if blur_radius > 0.0
243            && let BrushRef::Solid(color) = brush
244        {
245            if let Some(rounded) = path.as_rounded_rect() {
246                if rounded.radii().top_left == rounded.radii().top_right
247                    && rounded.radii().top_left == rounded.radii().bottom_left
248                    && rounded.radii().top_left == rounded.radii().bottom_right
249                {
250                    let rect_radius = rounded.radii().top_left;
251                    let rect = rounded.rect();
252                    self.scene.draw_blurred_rounded_rect(
253                        self.device_transform(),
254                        rect,
255                        color,
256                        rect_radius,
257                        blur_radius,
258                    );
259                    return;
260                }
261            } else if let Some(rect) = path.as_rect() {
262                self.scene.draw_blurred_rounded_rect(
263                    self.device_transform(),
264                    rect,
265                    color,
266                    0.,
267                    blur_radius,
268                );
269                return;
270            }
271        }
272
273        self.scene.fill(
274            vello::peniko::Fill::NonZero,
275            self.device_transform(),
276            brush,
277            None,
278            path,
279        );
280    }
281
282    fn push_layer(
283        &mut self,
284        blend: impl Into<peniko::BlendMode>,
285        alpha: f32,
286        transform: Affine,
287        clip: &impl Shape,
288    ) {
289        self.scene.push_layer(
290            Fill::NonZero,
291            blend,
292            alpha,
293            self.transform * transform,
294            clip,
295        );
296    }
297
298    fn pop_layer(&mut self) {
299        self.scene.pop_layer();
300    }
301
302    fn draw_glyphs<'a>(
303        &mut self,
304        origin: Point,
305        props: &GlyphRunProps<'a>,
306        glyphs: impl Iterator<Item = Glyph> + 'a,
307    ) {
308        // TODO: Vello 0.7's DrawGlyphs API has no embolden support.
309        // Synthetic bold from layout synthesis and `self.font_embolden` are not applied.
310        let transform =
311            self.device_transform() * Affine::translate((origin.x, origin.y)) * props.transform;
312        self.scene
313            .draw_glyphs(&props.font)
314            .brush(props.brush)
315            .brush_alpha(props.brush_alpha)
316            .hint(props.hint)
317            .transform(transform)
318            .glyph_transform(props.glyph_transform)
319            .font_size(props.font_size)
320            .normalized_coords(props.normalized_coords)
321            .draw(
322                props.style,
323                glyphs.map(|glyph| vello::Glyph {
324                    id: glyph.id,
325                    x: glyph.x,
326                    y: glyph.y,
327                }),
328            );
329    }
330
331    fn draw_img(&mut self, img: Img<'_>, rect: Rect) {
332        let rect_width = rect.width().max(1.);
333        let rect_height = rect.height().max(1.);
334
335        let scale_x = rect_width / img.img.image.width as f64;
336        let scale_y = rect_height / img.img.image.height as f64;
337
338        let translate_x = rect.min_x();
339        let translate_y = rect.min_y();
340
341        self.scene.draw_image(
342            &img.img,
343            self.device_transform()
344                .pre_scale_non_uniform(scale_x, scale_y)
345                .pre_translate((translate_x, translate_y).into()),
346        );
347    }
348
349    fn draw_svg<'b>(
350        &mut self,
351        svg: floem_renderer::Svg<'b>,
352        rect: Rect,
353        brush: Option<impl Into<BrushRef<'b>>>,
354    ) {
355        let rect_width = rect.width().max(1.);
356        let rect_height = rect.height().max(1.);
357
358        let svg_size = svg.tree.size();
359
360        let scale_x = rect_width / f64::from(svg_size.width());
361        let scale_y = rect_height / f64::from(svg_size.height());
362
363        let translate_x = rect.min_x();
364        let translate_y = rect.min_y();
365
366        let transform = self
367            .device_transform()
368            .pre_scale_non_uniform(scale_x, scale_y)
369            .pre_translate((translate_x, translate_y).into());
370
371        // Look up (or create) the cached base scene for this SVG.
372        let generation = self.cache_generation;
373        let base = self
374            .svg_cache
375            .entry(svg.hash.to_owned())
376            .and_modify(|(g, _)| *g = generation)
377            .or_insert_with(|| (generation, vello_svg::render_tree(svg.tree)));
378
379        // When a brush is applied (tinted icons), composite through an alpha mask.
380        // The base scene is cached; only the masking composite is rebuilt per frame.
381        let composited;
382        let scene_to_append = match brush {
383            Some(brush) => {
384                let brush = brush.into();
385                let size = Size::new(svg_size.width() as _, svg_size.height() as _);
386                let fill_rect = Rect::from_origin_size(Point::ZERO, size);
387                let base_scene = &base.1;
388                composited = alpha_mask_scene(
389                    size,
390                    |scene| scene.append(base_scene, None),
391                    move |scene| {
392                        scene.fill(Fill::NonZero, Affine::IDENTITY, brush, None, &fill_rect);
393                    },
394                );
395                &composited
396            }
397            None => &base.1,
398        };
399
400        self.scene.append(scene_to_append, Some(transform));
401    }
402
403    fn set_transform(&mut self, transform: Affine) {
404        self.transform = transform;
405    }
406
407    fn set_z_index(&mut self, _z_index: i32) {}
408
409    fn clip(&mut self, _shape: &impl Shape) {
410        // if shape.bounding_box().is_zero_area() {
411        //     return;
412        // }
413        // self.scene.pop_layer();
414        // self.scene.push_layer(
415        //     vello::peniko::BlendMode::default(),
416        //     1.,
417        //     self.transform.then_scale(self.window_scale),
418        //     shape,
419        // );
420    }
421
422    fn clear_clip(&mut self) {
423        // self.scene.pop_layer();
424    }
425
426    fn finish(&mut self) -> Option<vello::peniko::ImageBrush> {
427        if self.capture {
428            self.render_capture_image()
429        } else {
430            if let Ok(surface_texture) = self.surface.surface.get_current_texture() {
431                self.renderer
432                    .render_to_texture(
433                        &self.device,
434                        &self.queue,
435                        &self.scene,
436                        &self.surface.target_view,
437                        &vello::RenderParams {
438                            base_color: palette::css::TRANSPARENT, // Background color
439                            width: self.surface.config.width,
440                            height: self.surface.config.height,
441                            antialiasing_method: vello::AaConfig::Msaa16,
442                        },
443                    )
444                    .unwrap();
445
446                // Perform the copy
447                let mut encoder =
448                    self.device
449                        .create_command_encoder(&wgpu::CommandEncoderDescriptor {
450                            label: Some("Surface Blit"),
451                        });
452                self.surface.blitter.copy(
453                    &self.device,
454                    &mut encoder,
455                    &self.surface.target_view,
456                    &surface_texture
457                        .texture
458                        .create_view(&wgpu::TextureViewDescriptor::default()),
459                );
460                self.queue.submit([encoder.finish()]);
461
462                // Queue the texture to be presented on the surface
463                surface_texture.present();
464            }
465            None
466        }
467    }
468
469    fn debug_info(&self) -> String {
470        use std::fmt::Write;
471
472        let mut out = String::new();
473        writeln!(out, "name: Vello").ok();
474        writeln!(out, "info: {:#?}", self.adapter.get_info()).ok();
475
476        out
477    }
478}
479
480impl VelloRenderer {
481    fn render_capture_image(&mut self) -> Option<peniko::ImageBrush> {
482        let width_align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT - 1;
483        let width = (self.surface.config.width + width_align) & !width_align;
484        let height = self.surface.config.height;
485        let texture_desc = wgpu::TextureDescriptor {
486            size: wgpu::Extent3d {
487                width: self.surface.config.width,
488                height,
489                depth_or_array_layers: 1,
490            },
491            mip_level_count: 1,
492            sample_count: 1,
493            dimension: wgpu::TextureDimension::D2,
494            format: wgpu::TextureFormat::Rgba8Unorm,
495            usage: wgpu::TextureUsages::RENDER_ATTACHMENT
496                | wgpu::TextureUsages::COPY_SRC
497                | wgpu::TextureUsages::STORAGE_BINDING,
498            label: Some("render_texture"),
499            view_formats: &[wgpu::TextureFormat::Rgba8Unorm],
500        };
501        let texture = self.device.create_texture(&texture_desc);
502        let view = texture.create_view(&wgpu::TextureViewDescriptor {
503            label: Some("Floem Inspector Preview"),
504            format: Some(TextureFormat::Rgba8Unorm),
505            dimension: Some(wgpu::TextureViewDimension::D2),
506            aspect: TextureAspect::default(),
507            base_mip_level: 0,
508            mip_level_count: None,
509            base_array_layer: 0,
510            array_layer_count: None,
511            ..Default::default()
512        });
513
514        self.renderer
515            .render_to_texture(
516                &self.device,
517                &self.queue,
518                &self.scene,
519                &view,
520                &vello::RenderParams {
521                    base_color: palette::css::TRANSPARENT,
522                    width: self.surface.config.width,
523                    height: self.surface.config.height,
524                    antialiasing_method: AaConfig::Area,
525                },
526            )
527            .unwrap();
528
529        let bytes_per_pixel = 4;
530        let buffer = self.device.create_buffer(&wgpu::BufferDescriptor {
531            label: None,
532            size: (u64::from(width * height) * bytes_per_pixel),
533            usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST,
534            mapped_at_creation: false,
535        });
536        let bytes_per_row = width * bytes_per_pixel as u32;
537        assert!(bytes_per_row.is_multiple_of(wgpu::COPY_BYTES_PER_ROW_ALIGNMENT));
538
539        let mut encoder = self
540            .device
541            .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
542        encoder.copy_texture_to_buffer(
543            texture.as_image_copy(),
544            wgpu::TexelCopyBufferInfo {
545                buffer: &buffer,
546                layout: wgpu::TexelCopyBufferLayout {
547                    offset: 0,
548                    bytes_per_row: Some(bytes_per_row),
549                    rows_per_image: None,
550                },
551            },
552            texture_desc.size,
553        );
554        let command_buffer = encoder.finish();
555        self.queue.submit(Some(command_buffer));
556        self.device.poll(wgpu::PollType::wait_indefinitely()).ok()?;
557
558        let slice = buffer.slice(..);
559        let (tx, rx) = sync_channel(1);
560        slice.map_async(wgpu::MapMode::Read, move |r| tx.send(r).unwrap());
561
562        self.device.poll(wgpu::PollType::wait_indefinitely()).ok()?;
563        rx.recv().ok()?.ok()?;
564
565        let buffer: Vec<u8> = slice.get_mapped_range().to_owned();
566
567        let row_size = self.surface.config.width as usize * bytes_per_pixel as usize;
568        let mut cropped_buffer = Vec::with_capacity(row_size * height as usize);
569        let mut cursor = 0;
570        for _ in 0..height {
571            cropped_buffer.extend_from_slice(&buffer[cursor..(cursor + row_size)]);
572            cursor += bytes_per_row as usize;
573        }
574
575        Some(vello::peniko::ImageBrush::new(ImageData {
576            data: Blob::new(Arc::new(cropped_buffer)),
577            format: vello::peniko::ImageFormat::Rgba8,
578            alpha_type: ImageAlphaType::AlphaPremultiplied,
579            width: self.surface.config.width,
580            height,
581        }))
582    }
583}
584
585fn common_alpha_mask_scene(
586    size: Size,
587    alpha_mask: impl FnOnce(&mut Scene),
588    item: impl FnOnce(&mut Scene),
589    compose_mode: Compose,
590) -> Scene {
591    let mut scene = Scene::new();
592    scene.push_layer(
593        Fill::NonZero,
594        Mix::Normal,
595        1.0,
596        Affine::IDENTITY,
597        &Rect::from_origin_size((0., 0.), size),
598    );
599
600    alpha_mask(&mut scene);
601
602    scene.push_layer(
603        Fill::NonZero,
604        vello::peniko::BlendMode {
605            mix: Mix::Normal,
606            compose: compose_mode,
607        },
608        1.,
609        Affine::IDENTITY,
610        &Rect::from_origin_size((0., 0.), size),
611    );
612
613    item(&mut scene);
614
615    scene.pop_layer();
616    scene.pop_layer();
617    scene
618}
619
620fn alpha_mask_scene(
621    size: Size,
622    alpha_mask: impl FnOnce(&mut Scene),
623    item: impl FnOnce(&mut Scene),
624) -> Scene {
625    common_alpha_mask_scene(size, alpha_mask, item, Compose::SrcIn)
626}
627#[allow(unused)]
628fn invert_alpha_mask_scene(
629    size: Size,
630    alpha_mask: impl FnOnce(&mut Scene),
631    item: impl FnOnce(&mut Scene),
632) -> Scene {
633    common_alpha_mask_scene(size, alpha_mask, item, Compose::SrcOut)
634}