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