floem_vello_renderer/
lib.rs

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