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            let surface_texture = match self.surface.surface.get_current_texture() {
431                Ok(surface_texture) => surface_texture,
432                Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => {
433                    self.surface
434                        .surface
435                        .configure(&self.device, &self.surface.config);
436                    return None;
437                }
438                _ => return None,
439            };
440            self.renderer
441                .render_to_texture(
442                    &self.device,
443                    &self.queue,
444                    &self.scene,
445                    &self.surface.target_view,
446                    &vello::RenderParams {
447                        base_color: palette::css::TRANSPARENT, // Background color
448                        width: self.surface.config.width,
449                        height: self.surface.config.height,
450                        antialiasing_method: vello::AaConfig::Msaa16,
451                    },
452                )
453                .unwrap();
454
455            // Perform the copy
456            let mut encoder = self
457                .device
458                .create_command_encoder(&wgpu::CommandEncoderDescriptor {
459                    label: Some("Surface Blit"),
460                });
461            self.surface.blitter.copy(
462                &self.device,
463                &mut encoder,
464                &self.surface.target_view,
465                &surface_texture
466                    .texture
467                    .create_view(&wgpu::TextureViewDescriptor::default()),
468            );
469            self.queue.submit([encoder.finish()]);
470
471            // Queue the texture to be presented on the surface
472            surface_texture.present();
473            None
474        }
475    }
476
477    fn debug_info(&self) -> String {
478        use std::fmt::Write;
479
480        let mut out = String::new();
481        writeln!(out, "name: Vello").ok();
482        writeln!(out, "info: {:#?}", self.adapter.get_info()).ok();
483
484        out
485    }
486}
487
488impl VelloRenderer {
489    fn render_capture_image(&mut self) -> Option<peniko::ImageBrush> {
490        let width_align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT - 1;
491        let width = (self.surface.config.width + width_align) & !width_align;
492        let height = self.surface.config.height;
493        let texture_desc = wgpu::TextureDescriptor {
494            size: wgpu::Extent3d {
495                width: self.surface.config.width,
496                height,
497                depth_or_array_layers: 1,
498            },
499            mip_level_count: 1,
500            sample_count: 1,
501            dimension: wgpu::TextureDimension::D2,
502            format: wgpu::TextureFormat::Rgba8Unorm,
503            usage: wgpu::TextureUsages::RENDER_ATTACHMENT
504                | wgpu::TextureUsages::COPY_SRC
505                | wgpu::TextureUsages::STORAGE_BINDING,
506            label: Some("render_texture"),
507            view_formats: &[wgpu::TextureFormat::Rgba8Unorm],
508        };
509        let texture = self.device.create_texture(&texture_desc);
510        let view = texture.create_view(&wgpu::TextureViewDescriptor {
511            label: Some("Floem Inspector Preview"),
512            format: Some(TextureFormat::Rgba8Unorm),
513            dimension: Some(wgpu::TextureViewDimension::D2),
514            aspect: TextureAspect::default(),
515            base_mip_level: 0,
516            mip_level_count: None,
517            base_array_layer: 0,
518            array_layer_count: None,
519            ..Default::default()
520        });
521
522        self.renderer
523            .render_to_texture(
524                &self.device,
525                &self.queue,
526                &self.scene,
527                &view,
528                &vello::RenderParams {
529                    base_color: palette::css::TRANSPARENT,
530                    width: self.surface.config.width,
531                    height: self.surface.config.height,
532                    antialiasing_method: AaConfig::Area,
533                },
534            )
535            .unwrap();
536
537        let bytes_per_pixel = 4;
538        let buffer = self.device.create_buffer(&wgpu::BufferDescriptor {
539            label: None,
540            size: (u64::from(width * height) * bytes_per_pixel),
541            usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST,
542            mapped_at_creation: false,
543        });
544        let bytes_per_row = width * bytes_per_pixel as u32;
545        assert!(bytes_per_row.is_multiple_of(wgpu::COPY_BYTES_PER_ROW_ALIGNMENT));
546
547        let mut encoder = self
548            .device
549            .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
550        encoder.copy_texture_to_buffer(
551            texture.as_image_copy(),
552            wgpu::TexelCopyBufferInfo {
553                buffer: &buffer,
554                layout: wgpu::TexelCopyBufferLayout {
555                    offset: 0,
556                    bytes_per_row: Some(bytes_per_row),
557                    rows_per_image: None,
558                },
559            },
560            texture_desc.size,
561        );
562        let command_buffer = encoder.finish();
563        self.queue.submit(Some(command_buffer));
564        self.device.poll(wgpu::PollType::wait_indefinitely()).ok()?;
565
566        let slice = buffer.slice(..);
567        let (tx, rx) = sync_channel(1);
568        slice.map_async(wgpu::MapMode::Read, move |r| tx.send(r).unwrap());
569
570        self.device.poll(wgpu::PollType::wait_indefinitely()).ok()?;
571        rx.recv().ok()?.ok()?;
572
573        let buffer: Vec<u8> = slice.get_mapped_range().to_owned();
574
575        let row_size = self.surface.config.width as usize * bytes_per_pixel as usize;
576        let mut cropped_buffer = Vec::with_capacity(row_size * height as usize);
577        let mut cursor = 0;
578        for _ in 0..height {
579            cropped_buffer.extend_from_slice(&buffer[cursor..(cursor + row_size)]);
580            cursor += bytes_per_row as usize;
581        }
582
583        Some(vello::peniko::ImageBrush::new(ImageData {
584            data: Blob::new(Arc::new(cropped_buffer)),
585            format: vello::peniko::ImageFormat::Rgba8,
586            alpha_type: ImageAlphaType::AlphaPremultiplied,
587            width: self.surface.config.width,
588            height,
589        }))
590    }
591}
592
593fn common_alpha_mask_scene(
594    size: Size,
595    alpha_mask: impl FnOnce(&mut Scene),
596    item: impl FnOnce(&mut Scene),
597    compose_mode: Compose,
598) -> Scene {
599    let mut scene = Scene::new();
600    scene.push_layer(
601        Fill::NonZero,
602        Mix::Normal,
603        1.0,
604        Affine::IDENTITY,
605        &Rect::from_origin_size((0., 0.), size),
606    );
607
608    alpha_mask(&mut scene);
609
610    scene.push_layer(
611        Fill::NonZero,
612        vello::peniko::BlendMode {
613            mix: Mix::Normal,
614            compose: compose_mode,
615        },
616        1.,
617        Affine::IDENTITY,
618        &Rect::from_origin_size((0., 0.), size),
619    );
620
621    item(&mut scene);
622
623    scene.pop_layer();
624    scene.pop_layer();
625    scene
626}
627
628fn 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::SrcIn)
634}
635#[allow(unused)]
636fn invert_alpha_mask_scene(
637    size: Size,
638    alpha_mask: impl FnOnce(&mut Scene),
639    item: impl FnOnce(&mut Scene),
640) -> Scene {
641    common_alpha_mask_scene(size, alpha_mask, item, Compose::SrcOut)
642}