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, ImageAlphaType, ImageData, 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::FontData>,
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        self.scene.stroke(
217            stroke,
218            self.transform.then_scale(self.window_scale),
219            brush,
220            None,
221            shape,
222        );
223    }
224
225    fn fill<'b>(&mut self, path: &impl Shape, brush: impl Into<BrushRef<'b>>, blur_radius: f64) {
226        let brush: BrushRef<'b> = brush.into();
227
228        // For solid colors with specific shapes, use optimized methods
229        if blur_radius > 0.0 {
230            if let BrushRef::Solid(color) = brush {
231                if let Some(rounded) = path.as_rounded_rect() {
232                    if rounded.radii().top_left == rounded.radii().top_right
233                        && rounded.radii().top_left == rounded.radii().bottom_left
234                        && rounded.radii().top_left == rounded.radii().bottom_right
235                    {
236                        let rect_radius = rounded.radii().top_left;
237                        let rect = rounded.rect();
238                        self.scene.draw_blurred_rounded_rect(
239                            self.transform.then_scale(self.window_scale),
240                            rect,
241                            color,
242                            rect_radius,
243                            blur_radius,
244                        );
245                        return;
246                    }
247                } else if let Some(rect) = path.as_rect() {
248                    self.scene.draw_blurred_rounded_rect(
249                        self.transform.then_scale(self.window_scale),
250                        rect,
251                        color,
252                        0.,
253                        blur_radius,
254                    );
255                    return;
256                }
257            }
258        }
259
260        self.scene.fill(
261            vello::peniko::Fill::NonZero,
262            self.transform.then_scale(self.window_scale),
263            brush,
264            None,
265            path,
266        );
267    }
268
269    fn push_layer(
270        &mut self,
271        blend: impl Into<peniko::BlendMode>,
272        alpha: f32,
273        transform: Affine,
274        clip: &impl Shape,
275    ) {
276        self.scene.push_layer(
277            blend,
278            alpha,
279            self.transform.then_scale(self.window_scale) * transform,
280            clip,
281        );
282    }
283
284    fn pop_layer(&mut self) {
285        self.scene.pop_layer();
286    }
287
288    fn draw_text_with_layout<'b>(
289        &mut self,
290        layout: impl Iterator<Item = LayoutRun<'b>>,
291        pos: impl Into<Point>,
292    ) {
293        let pos: Point = pos.into();
294        let transform = self
295            .transform
296            .pre_translate((pos.x, pos.y).into())
297            .then_scale(self.window_scale);
298
299        for line in layout {
300            let mut current_run: Option<GlyphRun> = None;
301
302            for glyph in line.glyphs {
303                let color = glyph.color_opt.map_or(palette::css::BLACK, |c| {
304                    Color::from_rgba8(c.r(), c.g(), c.b(), c.a())
305                });
306                let font_size = glyph.font_size;
307                let font_id = glyph.font_id;
308                let metadata = glyph.metadata;
309
310                if current_run.as_ref().is_none_or(|run| {
311                    run.color != color
312                        || run.font_size != font_size
313                        || run.font_id != font_id
314                        || run.metadata != metadata
315                }) {
316                    if let Some(run) = current_run.take() {
317                        self.draw_glyph_run(
318                            run,
319                            transform.pre_translate((0., line.line_y.into()).into()),
320                        );
321                    }
322                    current_run = Some(GlyphRun {
323                        color,
324                        font_size,
325                        font_id,
326                        metadata,
327                        glyphs: Vec::new(),
328                    });
329                }
330
331                if let Some(run) = &mut current_run {
332                    run.glyphs.push(glyph);
333                }
334            }
335
336            if let Some(run) = current_run.take() {
337                self.draw_glyph_run(
338                    run,
339                    transform.pre_translate((0., line.line_y.into()).into()),
340                );
341            }
342        }
343    }
344
345    fn draw_img(&mut self, img: Img<'_>, rect: Rect) {
346        let rect_width = rect.width().max(1.);
347        let rect_height = rect.height().max(1.);
348
349        let scale_x = rect_width / img.img.image.width as f64;
350        let scale_y = rect_height / img.img.image.height as f64;
351
352        let translate_x = rect.min_x();
353        let translate_y = rect.min_y();
354
355        self.scene.draw_image(
356            &img.img,
357            self.transform
358                .pre_scale_non_uniform(scale_x, scale_y)
359                .then_translate((translate_x, translate_y).into())
360                .then_scale(self.window_scale),
361        );
362    }
363
364    fn draw_svg<'b>(
365        &mut self,
366        svg: floem_renderer::Svg<'b>,
367        rect: Rect,
368        brush: Option<impl Into<BrushRef<'b>>>,
369    ) {
370        let rect_width = rect.width().max(1.);
371        let rect_height = rect.height().max(1.);
372
373        let svg_size = svg.tree.size();
374
375        let scale_x = rect_width / f64::from(svg_size.width());
376        let scale_y = rect_height / f64::from(svg_size.height());
377
378        let translate_x = rect.min_x();
379        let translate_y = rect.min_y();
380
381        let new = brush.map_or_else(
382            || vello_svg::render_tree(svg.tree),
383            |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
388                alpha_mask_scene(
389                    size,
390                    |scene| {
391                        scene.append(&vello_svg::render_tree(svg.tree), None);
392                    },
393                    move |scene| {
394                        scene.fill(Fill::NonZero, Affine::IDENTITY, brush, None, &fill_rect);
395                    },
396                )
397            },
398        );
399
400        // Apply transformations to fit the SVG within the provided rectangle
401        self.scene.append(
402            &new,
403            Some(
404                self.transform
405                    .pre_scale_non_uniform(scale_x, scale_y)
406                    .pre_translate((translate_x, translate_y).into())
407                    .then_scale(self.window_scale),
408            ),
409        );
410    }
411
412    fn set_transform(&mut self, transform: Affine) {
413        self.transform = transform;
414    }
415
416    fn set_z_index(&mut self, _z_index: i32) {}
417
418    fn clip(&mut self, _shape: &impl Shape) {
419        // if shape.bounding_box().is_zero_area() {
420        //     return;
421        // }
422        // self.scene.pop_layer();
423        // self.scene.push_layer(
424        //     vello::peniko::BlendMode::default(),
425        //     1.,
426        //     self.transform.then_scale(self.window_scale),
427        //     shape,
428        // );
429    }
430
431    fn clear_clip(&mut self) {
432        // self.scene.pop_layer();
433    }
434
435    fn finish(&mut self) -> Option<vello::peniko::ImageBrush> {
436        if self.capture {
437            self.render_capture_image()
438        } else {
439            if let Ok(surface_texture) = self.surface.surface.get_current_texture() {
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 =
457                    self.device
458                        .create_command_encoder(&wgpu::CommandEncoderDescriptor {
459                            label: Some("Surface Blit"),
460                        });
461
462                self.surface.blitter.copy(
463                    &self.device,
464                    &mut encoder,
465                    &self.surface.target_view,
466                    &surface_texture
467                        .texture
468                        .create_view(&wgpu::TextureViewDescriptor::default()),
469                );
470                self.queue.submit([encoder.finish()]);
471                // Queue the texture to be presented on the surface
472                surface_texture.present();
473            }
474            None
475        }
476    }
477
478    fn debug_info(&self) -> String {
479        use std::fmt::Write;
480
481        let mut out = String::new();
482        writeln!(out, "name: Vello").ok();
483        writeln!(out, "info: {:#?}", self.adapter.get_info()).ok();
484
485        out
486    }
487}
488
489impl VelloRenderer {
490    fn render_capture_image(&mut self) -> Option<peniko::ImageBrush> {
491        let width_align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT - 1;
492        let width = (self.surface.config.width + width_align) & !width_align;
493        let height = self.surface.config.height;
494        let texture_desc = wgpu::TextureDescriptor {
495            size: wgpu::Extent3d {
496                width: self.surface.config.width,
497                height,
498                depth_or_array_layers: 1,
499            },
500            mip_level_count: 1,
501            sample_count: 1,
502            dimension: wgpu::TextureDimension::D2,
503            format: wgpu::TextureFormat::Rgba8Unorm,
504            usage: wgpu::TextureUsages::RENDER_ATTACHMENT
505                | wgpu::TextureUsages::COPY_SRC
506                | wgpu::TextureUsages::STORAGE_BINDING,
507            label: Some("render_texture"),
508            view_formats: &[wgpu::TextureFormat::Rgba8Unorm],
509        };
510        let texture = self.device.create_texture(&texture_desc);
511        let view = texture.create_view(&wgpu::TextureViewDescriptor {
512            label: Some("Floem Inspector Preview"),
513            format: Some(TextureFormat::Rgba8Unorm),
514            dimension: Some(wgpu::TextureViewDimension::D2),
515            aspect: TextureAspect::default(),
516            base_mip_level: 0,
517            mip_level_count: None,
518            base_array_layer: 0,
519            array_layer_count: None,
520            ..Default::default()
521        });
522
523        self.renderer
524            .render_to_texture(
525                &self.device,
526                &self.queue,
527                &self.scene,
528                &view,
529                &vello::RenderParams {
530                    base_color: palette::css::BLACK, // Background color
531                    width: self.surface.config.width * self.window_scale as u32,
532                    height: self.surface.config.height * self.window_scale as u32,
533                    antialiasing_method: AaConfig::Area,
534                },
535            )
536            .unwrap();
537
538        let bytes_per_pixel = 4;
539        let buffer = self.device.create_buffer(&wgpu::BufferDescriptor {
540            label: None,
541            size: (u64::from(width * height) * bytes_per_pixel),
542            usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST,
543            mapped_at_creation: false,
544        });
545        let bytes_per_row = width * bytes_per_pixel as u32;
546        assert!(bytes_per_row.is_multiple_of(wgpu::COPY_BYTES_PER_ROW_ALIGNMENT));
547
548        let mut encoder = self
549            .device
550            .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
551        encoder.copy_texture_to_buffer(
552            texture.as_image_copy(),
553            wgpu::TexelCopyBufferInfo {
554                buffer: &buffer,
555                layout: wgpu::TexelCopyBufferLayout {
556                    offset: 0,
557                    bytes_per_row: Some(bytes_per_row),
558                    rows_per_image: None,
559                },
560            },
561            texture_desc.size,
562        );
563        let command_buffer = encoder.finish();
564        self.queue.submit(Some(command_buffer));
565        self.device.poll(wgpu::PollType::Wait).ok()?;
566
567        let slice = buffer.slice(..);
568        let (tx, rx) = sync_channel(1);
569        slice.map_async(wgpu::MapMode::Read, move |r| tx.send(r).unwrap());
570
571        loop {
572            if let Ok(r) = rx.try_recv() {
573                break r.ok()?;
574            }
575            if matches!(
576                self.device.poll(wgpu::PollType::Wait).ok()?,
577                wgpu::PollStatus::WaitSucceeded
578            ) {
579                rx.recv().ok()?.ok()?;
580                break;
581            }
582        }
583
584        let mut cropped_buffer = Vec::new();
585        let buffer: Vec<u8> = slice.get_mapped_range().to_owned();
586
587        let mut cursor = 0;
588        let row_size = self.surface.config.width as usize * bytes_per_pixel as usize;
589        for _ in 0..height {
590            cropped_buffer.extend_from_slice(&buffer[cursor..(cursor + row_size)]);
591            cursor += bytes_per_row as usize;
592        }
593
594        Some(vello::peniko::ImageBrush::new(ImageData {
595            data: Blob::new(Arc::new(cropped_buffer)),
596            format: vello::peniko::ImageFormat::Rgba8,
597            alpha_type: ImageAlphaType::AlphaPremultiplied,
598            width: self.surface.config.width,
599            height,
600        }))
601    }
602}
603
604fn common_alpha_mask_scene(
605    size: Size,
606    alpha_mask: impl FnOnce(&mut Scene),
607    item: impl FnOnce(&mut Scene),
608    compose_mode: Compose,
609) -> Scene {
610    let mut scene = Scene::new();
611    scene.push_layer(
612        Mix::Normal,
613        1.0,
614        Affine::IDENTITY,
615        &Rect::from_origin_size((0., 0.), size),
616    );
617
618    alpha_mask(&mut scene);
619
620    scene.push_layer(
621        vello::peniko::BlendMode {
622            mix: Mix::Normal,
623            compose: compose_mode,
624        },
625        1.,
626        Affine::IDENTITY,
627        &Rect::from_origin_size((0., 0.), size),
628    );
629
630    item(&mut scene);
631
632    scene.pop_layer();
633    scene.pop_layer();
634    scene
635}
636
637fn alpha_mask_scene(
638    size: Size,
639    alpha_mask: impl FnOnce(&mut Scene),
640    item: impl FnOnce(&mut Scene),
641) -> Scene {
642    common_alpha_mask_scene(size, alpha_mask, item, Compose::SrcIn)
643}
644#[allow(unused)]
645fn invert_alpha_mask_scene(
646    size: Size,
647    alpha_mask: impl FnOnce(&mut Scene),
648    item: impl FnOnce(&mut Scene),
649) -> Scene {
650    common_alpha_mask_scene(size, alpha_mask, item, Compose::SrcOut)
651}
652
653struct GlyphRun<'a> {
654    color: Color,
655    font_size: f32,
656    font_id: ID,
657    metadata: usize,
658    glyphs: Vec<&'a LayoutGlyph>,
659}
660
661impl VelloRenderer {
662    fn get_font(&mut self, font_id: ID) -> vello::peniko::FontData {
663        self.font_cache.get(&font_id).cloned().unwrap_or_else(|| {
664            let mut font_system = FONT_SYSTEM.lock();
665            let font = font_system.get_font(font_id).unwrap();
666            let face = font_system.db().face(font_id).unwrap();
667            let font_data = font.data();
668            let font_index = face.index;
669            drop(font_system);
670            let font =
671                vello::peniko::FontData::new(Blob::new(Arc::new(font_data.to_vec())), font_index);
672            self.font_cache.insert(font_id, font.clone());
673            font
674        })
675    }
676
677    fn draw_glyph_run(&mut self, run: GlyphRun, transform: Affine) {
678        let font = self.get_font(run.font_id);
679        self.scene
680            .draw_glyphs(&font)
681            .font_size(run.font_size)
682            .brush(run.color)
683            .hint(false)
684            .transform(transform)
685            .draw(
686                Fill::NonZero,
687                run.glyphs.into_iter().map(|glyph| vello::Glyph {
688                    id: glyph.glyph_id.into(),
689                    x: glyph.x,
690                    y: glyph.y,
691                }),
692            );
693    }
694}