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 #[allow(dead_code)]
39 font_embolden: f32,
40 svg_cache: HashMap<Vec<u8>, (bool, Scene)>,
43 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 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 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 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 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 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 }
421
422 fn clear_clip(&mut self) {
423 }
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, width: self.surface.config.width,
440 height: self.surface.config.height,
441 antialiasing_method: vello::AaConfig::Msaa16,
442 },
443 )
444 .unwrap();
445
446 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 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}