blitz_renderer_vello/
renderer.rs

1mod multicolor_rounded_rect;
2mod render;
3
4use crate::Color;
5use crate::renderer::render::generate_vello_scene;
6use blitz_dom::BaseDocument;
7use blitz_traits::{BlitzWindowHandle, Devtools, DocumentRenderer, Viewport};
8use std::num::NonZeroUsize;
9use std::sync::Arc;
10use vello::{
11    AaSupport, RenderParams, Renderer as VelloRenderer, RendererOptions, Scene,
12    util::{RenderContext, RenderSurface, block_on_wgpu},
13};
14use wgpu::{
15    BufferDescriptor, BufferUsages, CommandEncoderDescriptor, Extent3d, ImageCopyBuffer,
16    PresentMode, SurfaceError, TextureDescriptor, TextureFormat, TextureUsages,
17};
18
19#[cfg(target_os = "macos")]
20const DEFAULT_THREADS: Option<NonZeroUsize> = NonZeroUsize::new(1);
21#[cfg(not(target_os = "macos"))]
22const DEFAULT_THREADS: Option<NonZeroUsize> = None;
23
24// Simple struct to hold the state of the renderer
25pub struct ActiveRenderState {
26    renderer: VelloRenderer,
27    surface: RenderSurface<'static>,
28}
29
30#[allow(clippy::large_enum_variant)]
31pub enum RenderState {
32    Active(ActiveRenderState),
33    Suspended,
34}
35
36pub struct BlitzVelloRenderer {
37    // The fields MUST be in this order, so that the surface is dropped before the window
38    // Window is cached even when suspended so that it can be reused when the app is resumed after being suspended
39    render_state: RenderState,
40    window_handle: Arc<dyn BlitzWindowHandle>,
41
42    // Vello
43    render_context: RenderContext,
44    scene: Scene,
45}
46
47impl DocumentRenderer for BlitzVelloRenderer {
48    type Doc = BaseDocument;
49
50    fn new(window: Arc<dyn BlitzWindowHandle>) -> Self {
51        // 1. Set up renderer-specific stuff
52        // We build an independent viewport which can be dynamically set later
53        // The intention here is to split the rendering pipeline away from tao/windowing for rendering to images
54
55        // 2. Set up Vello specific stuff
56        let render_context = RenderContext::new();
57
58        Self {
59            render_context,
60            render_state: RenderState::Suspended,
61            window_handle: window,
62            scene: Scene::new(),
63        }
64    }
65
66    fn is_active(&self) -> bool {
67        matches!(self.render_state, RenderState::Active(_))
68    }
69
70    fn resume(&mut self, viewport: &Viewport) {
71        let surface = pollster::block_on(self.render_context.create_surface(
72            self.window_handle.clone(),
73            viewport.window_size.0,
74            viewport.window_size.1,
75            PresentMode::AutoVsync,
76        ))
77        .expect("Error creating surface");
78
79        let options = RendererOptions {
80            surface_format: Some(surface.config.format),
81            antialiasing_support: AaSupport::all(),
82            use_cpu: false,
83            num_init_threads: DEFAULT_THREADS,
84        };
85
86        let renderer =
87            VelloRenderer::new(&self.render_context.devices[surface.dev_id].device, options)
88                .unwrap();
89
90        self.render_state = RenderState::Active(ActiveRenderState { renderer, surface });
91    }
92
93    fn suspend(&mut self) {
94        self.render_state = RenderState::Suspended;
95    }
96
97    fn set_size(&mut self, physical_width: u32, physical_height: u32) {
98        if let RenderState::Active(state) = &mut self.render_state {
99            self.render_context
100                .resize_surface(&mut state.surface, physical_width, physical_height);
101        };
102    }
103
104    fn render(
105        &mut self,
106        doc: &BaseDocument,
107        scale: f64,
108        width: u32,
109        height: u32,
110        devtools: Devtools,
111    ) {
112        let RenderState::Active(state) = &mut self.render_state else {
113            return;
114        };
115        let surface_texture = match state.surface.surface.get_current_texture() {
116            Ok(surface) => surface,
117            // When resizing too aggresively, the surface can get outdated (another resize) before being rendered into
118            Err(SurfaceError::Outdated) => return,
119            Err(_) => panic!("failed to get surface texture"),
120        };
121
122        let device = &self.render_context.devices[state.surface.dev_id];
123
124        let render_params = RenderParams {
125            base_color: Color::WHITE,
126            width: state.surface.config.width,
127            height: state.surface.config.height,
128            antialiasing_method: vello::AaConfig::Msaa16,
129        };
130
131        // Regenerate the vello scene
132        render::generate_vello_scene(&mut self.scene, doc, scale, width, height, devtools);
133
134        state
135            .renderer
136            .render_to_surface(
137                &device.device,
138                &device.queue,
139                &self.scene,
140                &surface_texture,
141                &render_params,
142            )
143            .expect("failed to render to surface");
144
145        surface_texture.present();
146        device.device.poll(wgpu::Maintain::Wait);
147
148        // Empty the Vello scene (memory optimisation)
149        self.scene.reset();
150    }
151}
152
153pub struct VelloImageRenderer {
154    size: Extent3d,
155    scale: f64,
156    // render_context: vello::util::RenderContext,
157    device: wgpu::Device,
158    queue: wgpu::Queue,
159    renderer: vello::Renderer,
160    scene: vello::Scene,
161    texture: wgpu::Texture,
162    texture_view: wgpu::TextureView,
163    gpu_buffer: wgpu::Buffer,
164}
165
166impl VelloImageRenderer {
167    pub async fn new(width: u32, height: u32, scale: f64) -> Self {
168        let size = Extent3d {
169            width,
170            height,
171            depth_or_array_layers: 1,
172        };
173
174        // Create render context
175        let mut context = RenderContext::new();
176
177        // Setup device
178        let device_id = context
179            .device(None)
180            .await
181            .expect("No compatible device found");
182        let device_handle = context.devices.remove(device_id);
183        let device = device_handle.device;
184        let queue = device_handle.queue;
185
186        // Create renderer
187        let renderer = vello::Renderer::new(
188            &device,
189            RendererOptions {
190                surface_format: None,
191                use_cpu: false,
192                num_init_threads: DEFAULT_THREADS,
193                antialiasing_support: vello::AaSupport::area_only(),
194            },
195        )
196        .expect("Got non-Send/Sync error from creating renderer");
197
198        let texture = device.create_texture(&TextureDescriptor {
199            label: Some("Target texture"),
200            size,
201            mip_level_count: 1,
202            sample_count: 1,
203            dimension: wgpu::TextureDimension::D2,
204            format: TextureFormat::Rgba8Unorm,
205            usage: TextureUsages::STORAGE_BINDING | TextureUsages::COPY_SRC,
206            view_formats: &[],
207        });
208
209        let texture_view = texture.create_view(&wgpu::TextureViewDescriptor::default());
210
211        let padded_byte_width = (width * 4).next_multiple_of(256);
212        let buffer_size = padded_byte_width as u64 * height as u64;
213        let gpu_buffer = device.create_buffer(&BufferDescriptor {
214            label: Some("val"),
215            size: buffer_size,
216            usage: BufferUsages::MAP_READ | BufferUsages::COPY_DST,
217            mapped_at_creation: false,
218        });
219
220        Self {
221            size,
222            scale,
223            device,
224            queue,
225            renderer,
226            texture,
227            texture_view,
228            gpu_buffer,
229            scene: Scene::new(),
230        }
231    }
232
233    pub fn render_document(&mut self, doc: &BaseDocument, cpu_buffer: &mut Vec<u8>) {
234        generate_vello_scene(
235            &mut self.scene,
236            doc,
237            self.scale,
238            self.size.width,
239            self.size.height,
240            Devtools::default(),
241        );
242
243        self.render_internal_scene(cpu_buffer);
244    }
245
246    fn render_internal_scene(&mut self, cpu_buffer: &mut Vec<u8>) {
247        let render_params = vello::RenderParams {
248            base_color: vello::peniko::Color::WHITE,
249            width: self.size.width,
250            height: self.size.height,
251            antialiasing_method: vello::AaConfig::Area,
252        };
253
254        self.renderer
255            .render_to_texture(
256                &self.device,
257                &self.queue,
258                &self.scene,
259                &self.texture_view,
260                &render_params,
261            )
262            .expect("Got non-Send/Sync error from rendering");
263
264        let mut encoder = self
265            .device
266            .create_command_encoder(&CommandEncoderDescriptor {
267                label: Some("Copy out buffer"),
268            });
269        let padded_byte_width = (self.size.width * 4).next_multiple_of(256);
270        encoder.copy_texture_to_buffer(
271            self.texture.as_image_copy(),
272            ImageCopyBuffer {
273                buffer: &self.gpu_buffer,
274                layout: wgpu::ImageDataLayout {
275                    offset: 0,
276                    bytes_per_row: Some(padded_byte_width),
277                    rows_per_image: None,
278                },
279            },
280            self.size,
281        );
282
283        self.queue.submit([encoder.finish()]);
284        let buf_slice = self.gpu_buffer.slice(..);
285
286        let (sender, receiver) = futures_intrusive::channel::shared::oneshot_channel();
287        buf_slice.map_async(wgpu::MapMode::Read, move |v| sender.send(v).unwrap());
288        if let Some(recv_result) = block_on_wgpu(&self.device, receiver.receive()) {
289            recv_result.unwrap();
290        } else {
291            panic!("channel was closed");
292        }
293
294        let data = buf_slice.get_mapped_range();
295
296        cpu_buffer.clear();
297        cpu_buffer.reserve((self.size.width * self.size.height * 4) as usize);
298
299        // Pad result
300        for row in 0..self.size.height {
301            let start = (row * padded_byte_width).try_into().unwrap();
302            cpu_buffer.extend(&data[start..start + (self.size.width * 4) as usize]);
303        }
304
305        // Unmap buffer
306        drop(data);
307        self.gpu_buffer.unmap();
308
309        // Empty the Vello scene (memory optimisation)
310        self.scene.reset();
311    }
312}
313
314pub async fn render_to_buffer(dom: &BaseDocument, viewport: Viewport) -> Vec<u8> {
315    let (width, height) = viewport.window_size;
316
317    let mut buf = Vec::with_capacity((width * height * 4) as usize);
318    let mut renderer = VelloImageRenderer::new(width, height, viewport.scale_f64()).await;
319    renderer.render_document(dom, &mut buf);
320
321    buf
322}