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 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 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 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, width: self.surface.config.width,
475 height: self.surface.config.height,
476 antialiasing_method: vello::AaConfig::Msaa16,
477 },
478 )
479 .unwrap();
480
481 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 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, 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}