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
24pub 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 render_state: RenderState,
40 window_handle: Arc<dyn BlitzWindowHandle>,
41
42 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 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 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 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 self.scene.reset();
150 }
151}
152
153pub struct VelloImageRenderer {
154 size: Extent3d,
155 scale: f64,
156 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 let mut context = RenderContext::new();
176
177 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 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 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 drop(data);
307 self.gpu_buffer.unmap();
308
309 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}