1use ab_glyph::Font;
2use std::collections::HashMap;
3use core::num::NonZeroU8;
4use crate::sequence::GridSequence;
5use crate::atlas::populate_atlas;
6
7#[inline]
8fn compute_output_size<const W: usize, const H: usize>(font_width: u32, font_height: u32) -> (u32, u32) {
9 let output_width = W as u32 * font_width;
10 let output_height = H as u32 * font_height;
11 (output_width, output_height)
12}
13
14pub struct WgpuRenderer<const W: usize, const H: usize> {
15 sequence: GridSequence<W, H>,
16 lut: HashMap<char, u32>,
17 device: wgpu::Device,
18 queue: wgpu::Queue,
19 idx_grid: wgpu::Buffer,
20 output_img: wgpu::Texture,
21 color_grid: wgpu::Buffer,
22 pipeline: wgpu::ComputePipeline,
23 bind_group: wgpu::BindGroup,
24 output_width: u32,
25 output_height: u32
26}
27
28fn round_up_aligned(n: u32) -> u32 {
29 use wgpu::COPY_BYTES_PER_ROW_ALIGNMENT as ALIGN;
30
31 (ALIGN * (n / ALIGN)) + ALIGN
32}
33
34impl<const W: usize, const H: usize> WgpuRenderer<W, H> {
35 pub async fn new<F: Font>(font: F, sequence: GridSequence<W, H>) -> Self {
36 let populated_atlas = populate_atlas(font, &sequence);
37
38 let (output_width, output_height) = compute_output_size::<W, H>(
39 populated_atlas.font_width,
40 populated_atlas.font_height
41 );
42
43 let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
44 backends: wgpu::Backends::all(),
45 flags: wgpu::InstanceFlags::VALIDATION,
46 backend_options: wgpu::BackendOptions::default()
47 });
48
49 let adapter = instance.request_adapter(&wgpu::RequestAdapterOptions::default()).await.unwrap();
50 let max_buf_size = round_up_aligned(output_width * 4) as u64 * output_height as u64;
52
53 let (device, queue) = adapter.request_device(&wgpu::DeviceDescriptor {
54 required_features: wgpu::Features::SHADER_INT64,
55 required_limits: wgpu::Limits {
56 max_buffer_size: max_buf_size,
57 max_texture_dimension_2d: output_width.max(output_height),
58 ..wgpu::Limits::default()
59 },
60 memory_hints: wgpu::MemoryHints::Performance,
61 label: Some("device"),
62 trace: wgpu::Trace::Off
63 }).await.unwrap();
64
65 let idx_grid = device.create_buffer(&wgpu::BufferDescriptor {
66 label: Some("idx_grid"),
67 size: (H * W * 4) as u64,
68 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::STORAGE,
69 mapped_at_creation: false
70 });
71
72 let atlas = device.create_buffer(&wgpu::BufferDescriptor {
73 label: Some("atlas"),
74 size: populated_atlas.buffer.len() as u64,
75 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::STORAGE,
76 mapped_at_creation: false
77 });
78
79 queue.write_buffer(&atlas, 0, &populated_atlas.buffer);
81
82 let output_img_size = wgpu::Extent3d {
83 width: output_width,
84 height: output_height,
85 depth_or_array_layers: 1
86 };
87
88 let output_img = device.create_texture(&wgpu::TextureDescriptor {
89 label: Some("output_img"),
90 size: output_img_size,
91 mip_level_count: 1,
92 sample_count: 1,
93 dimension: wgpu::TextureDimension::D2,
94 format: wgpu::TextureFormat::Rgba8Uint,
95 usage: wgpu::TextureUsages::STORAGE_BINDING | wgpu::TextureUsages::COPY_SRC,
96 view_formats: &[]
97 });
98
99 let grid_width_uniform = device.create_buffer(&wgpu::BufferDescriptor {
100 label: Some("grid_width_uniform"),
101 size: 4,
102 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
103 mapped_at_creation: false
104 });
105
106 let grid_height_uniform = device.create_buffer(&wgpu::BufferDescriptor {
107 label: Some("grid_height_uniform"),
108 size: 4,
109 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
110 mapped_at_creation: false
111 });
112
113 queue.write_buffer(&grid_width_uniform, 0, &(W as u32).to_ne_bytes());
114 queue.write_buffer(&grid_height_uniform, 0, &(H as u32).to_ne_bytes());
115
116 let img_width_uniform = device.create_buffer(&wgpu::BufferDescriptor {
117 label: Some("img_width_uniform"),
118 size: 4,
119 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
120 mapped_at_creation: false
121 });
122
123 let img_height_uniform = device.create_buffer(&wgpu::BufferDescriptor {
124 label: Some("img_height_uniform"),
125 size: 4,
126 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
127 mapped_at_creation: false
128 });
129
130 queue.write_buffer(&img_width_uniform, 0, &output_width.to_ne_bytes());
131 queue.write_buffer(&img_height_uniform, 0, &output_height.to_ne_bytes());
132
133 let color_grid = device.create_buffer(&wgpu::BufferDescriptor {
134 label: Some("color_grid"),
135 size: (W * H * 8) as u64,
136 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::STORAGE,
137 mapped_at_creation: false
138 });
139
140 let shader = device.create_shader_module(wgpu::include_wgsl!("shader.wgsl"));
141
142 let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
143 label: Some("bind_group_layout"),
144 entries: &[
145 wgpu::BindGroupLayoutEntry {
147 binding: 0,
148 visibility: wgpu::ShaderStages::COMPUTE,
149 ty: wgpu::BindingType::Buffer {
150 ty: wgpu::BufferBindingType::Storage {
151 read_only: true
152 },
153 has_dynamic_offset: false,
154 min_binding_size: None
155 },
156 count: None
157 },
158 wgpu::BindGroupLayoutEntry {
160 binding: 1,
161 visibility: wgpu::ShaderStages::COMPUTE,
162 ty: wgpu::BindingType::Buffer {
163 ty: wgpu::BufferBindingType::Storage {
164 read_only: true
165 },
166 has_dynamic_offset: false,
167 min_binding_size: None
168 },
169 count: None
170 },
171 wgpu::BindGroupLayoutEntry {
173 binding: 2,
174 visibility: wgpu::ShaderStages::COMPUTE,
175 ty: wgpu::BindingType::StorageTexture {
176 access: wgpu::StorageTextureAccess::WriteOnly,
177 format: wgpu::TextureFormat::Rgba8Uint,
178 view_dimension: wgpu::TextureViewDimension::D2
179 },
180 count: None
181 },
182 wgpu::BindGroupLayoutEntry {
184 binding: 3,
185 visibility: wgpu::ShaderStages::COMPUTE,
186 ty: wgpu::BindingType::Buffer {
187 ty: wgpu::BufferBindingType::Uniform,
188 has_dynamic_offset: false,
189 min_binding_size: None
190 },
191 count: None
192 },
193 wgpu::BindGroupLayoutEntry {
195 binding: 4,
196 visibility: wgpu::ShaderStages::COMPUTE,
197 ty: wgpu::BindingType::Buffer {
198 ty: wgpu::BufferBindingType::Uniform,
199 has_dynamic_offset: false,
200 min_binding_size: None
201 },
202 count: None
203 },
204 wgpu::BindGroupLayoutEntry {
206 binding: 5,
207 visibility: wgpu::ShaderStages::COMPUTE,
208 ty: wgpu::BindingType::Buffer {
209 ty: wgpu::BufferBindingType::Uniform,
210 has_dynamic_offset: false,
211 min_binding_size: None
212 },
213 count: None
214 },
215 wgpu::BindGroupLayoutEntry {
217 binding: 6,
218 visibility: wgpu::ShaderStages::COMPUTE,
219 ty: wgpu::BindingType::Buffer {
220 ty: wgpu::BufferBindingType::Uniform,
221 has_dynamic_offset: false,
222 min_binding_size: None
223 },
224 count: None
225 },
226 wgpu::BindGroupLayoutEntry {
228 binding: 7,
229 visibility: wgpu::ShaderStages::COMPUTE,
230 ty: wgpu::BindingType::Buffer {
231 ty: wgpu::BufferBindingType::Storage {
232 read_only: true
233 },
234 has_dynamic_offset: false,
235 min_binding_size: None
236 },
237 count: None
238 }
239 ]
240 });
241
242 let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
243 label: Some("pipeline_layout"),
244 bind_group_layouts: &[&bind_group_layout],
245 push_constant_ranges: &[]
246 });
247
248 let pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
249 label: Some("pipeline"),
250 layout: Some(&pipeline_layout),
251 module: &shader,
252 entry_point: Some("sample_atlas"),
253 compilation_options: wgpu::PipelineCompilationOptions::default(),
254 cache: None
255 });
256
257 let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
258 label: Some("bind_group"),
259 layout: &bind_group_layout,
260 entries: &[
261 wgpu::BindGroupEntry {
262 binding: 0,
263 resource: idx_grid.as_entire_binding()
264 },
265 wgpu::BindGroupEntry {
266 binding: 1,
267 resource: atlas.as_entire_binding()
268 },
269 wgpu::BindGroupEntry {
270 binding: 2,
271 resource: wgpu::BindingResource::TextureView(
272 &output_img.create_view(&wgpu::TextureViewDescriptor {
273 label: Some("output_img_view"),
274 format: Some(wgpu::TextureFormat::Rgba8Uint),
275 dimension: Some(wgpu::TextureViewDimension::D2),
276 usage: Some(wgpu::TextureUsages::STORAGE_BINDING),
277 aspect: wgpu::TextureAspect::All,
278 base_mip_level: 0,
279 mip_level_count: None,
280 base_array_layer: 0,
281 array_layer_count: None
282 })
283 )
284 },
285 wgpu::BindGroupEntry {
286 binding: 3,
287 resource: grid_width_uniform.as_entire_binding()
288 },
289 wgpu::BindGroupEntry {
290 binding: 4,
291 resource: grid_height_uniform.as_entire_binding()
292 },
293 wgpu::BindGroupEntry {
294 binding: 5,
295 resource: img_width_uniform.as_entire_binding()
296 },
297 wgpu::BindGroupEntry {
298 binding: 6,
299 resource: img_height_uniform.as_entire_binding()
300 },
301 wgpu::BindGroupEntry {
302 binding: 7,
303 resource: color_grid.as_entire_binding()
304 }
305 ]
306 });
307
308 Self {
309 sequence,
310 lut: populated_atlas.lut,
311 device,
312 queue,
313 idx_grid,
314 output_img,
315 pipeline,
316 bind_group,
317 output_width,
318 output_height,
319 color_grid
320 }
321 }
322}
323
324pub struct RenderedFrame {
325 pub img: image::RgbaImage,
326 pub frame_hold: NonZeroU8
327}
328
329impl RenderedFrame {
330 fn deserialize(width: u32, height: u32, data: Vec<u8>, frame_hold: NonZeroU8, bytes_per_row: usize) -> Self {
331 let mut buf = Vec::with_capacity(width as usize * height as usize * 4);
332 let used_row_bytes = width as usize * 4;
333
334 for row in data.chunks(bytes_per_row) {
335 buf.extend_from_slice(&row[..used_row_bytes]);
336 }
337
338 Self {
339 img: image::RgbaImage::from_raw(
340 width,
341 height,
342 buf
343 ).unwrap(),
344 frame_hold
345 }
346 }
347}
348
349#[inline]
350fn int_div_round_up(divisor: u32, dividend: u32) -> u32 {
351 (divisor / dividend) + match divisor % dividend {
352 0 => 0,
353 _ => 1
354 }
355}
356
357impl<const W: usize, const H: usize> Iterator for WgpuRenderer<W, H> {
358 type Item = RenderedFrame;
359
360 fn next(&mut self) -> Option<Self::Item> {
361 let frame = self.sequence.pop()?;
362
363 let mut encoder = self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
364 label: Some("encoder")
365 });
366
367 let frame_hold = frame.frame_hold;
368 self.queue.write_buffer(&self.color_grid, 0, &frame.serialize_colors());
369 self.queue.write_buffer(&self.idx_grid, 0, &frame.serialize(&self.lut));
370
371 let mut compute_pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
372 label: Some("sample_compute_pass"),
373 timestamp_writes: None
374 });
375 compute_pass.set_pipeline(&self.pipeline);
376 compute_pass.set_bind_group(0, &self.bind_group, &[]);
377 compute_pass.dispatch_workgroups(
378 int_div_round_up(self.output_width, 16),
379 int_div_round_up(self.output_height, 16),
380 1
381 );
382 drop(compute_pass);
383
384 let padded_bytes_width = round_up_aligned(self.output_width * 4);
385 let padded_bytes = padded_bytes_width * self.output_height;
386 let map_buf = self.device.create_buffer(&wgpu::BufferDescriptor {
387 label: Some("map_buf"),
388 size: padded_bytes as u64,
389 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
390 mapped_at_creation: false
391 });
392
393 encoder.copy_texture_to_buffer(
394 wgpu::TexelCopyTextureInfo {
395 texture: &self.output_img,
396 mip_level: 0,
397 origin: wgpu::Origin3d::ZERO,
398 aspect: wgpu::TextureAspect::All
399 },
400 wgpu::TexelCopyBufferInfo {
401 buffer: &map_buf,
402 layout: wgpu::TexelCopyBufferLayout {
403 offset: 0,
404 bytes_per_row: Some(padded_bytes_width),
405 rows_per_image: Some(self.output_height)
406 }
407 },
408 wgpu::Extent3d {
409 width: self.output_width,
410 height: self.output_height,
411 depth_or_array_layers: 1
412 }
413 );
414
415 self.queue.submit(std::iter::once(encoder.finish()));
416
417 map_buf.map_async(wgpu::MapMode::Read, .., |r| r.unwrap());
418 self.device.poll(wgpu::PollType::Wait).unwrap();
419
420 let serialized_data = map_buf.get_mapped_range(..).to_vec();
421 Some(RenderedFrame::deserialize(
422 self.output_width,
423 self.output_height,
424 serialized_data,
425 frame_hold,
426 padded_bytes_width as usize
427 ))
428 }
429}
430
431mod private {
432 use super::WgpuRenderer;
433
434 pub trait Sealed {}
435
436 impl<const W: usize, const H: usize> Sealed for WgpuRenderer<W, H> {}
437}
438
439pub trait VideoSrc: Iterator<Item = RenderedFrame> + Send + 'static + private::Sealed {
440 fn framerate(&self) -> NonZeroU8;
441 fn width(&self) -> u32;
442 fn height(&self) -> u32;
443}
444
445impl<const W: usize, const H: usize> VideoSrc for WgpuRenderer<W, H> {
446 #[inline]
447 fn framerate(&self) -> NonZeroU8 {
448 self.sequence.framerate
449 }
450
451 #[inline]
452 fn width(&self) -> u32 {
453 self.output_width
454 }
455
456 #[inline]
457 fn height(&self) -> u32 {
458 self.output_height
459 }
460}