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::Buffer,
21 color_grid: wgpu::Buffer,
22 pipeline: wgpu::ComputePipeline,
23 bind_group: wgpu::BindGroup,
24 output_width: u32,
25 output_height: u32
26}
27
28impl<const W: usize, const H: usize> WgpuRenderer<W, H> {
29 pub async fn new<F: Font>(font: F, sequence: GridSequence<W, H>) -> Self {
30 let populated_atlas = populate_atlas(font, &sequence);
31
32 let (output_width, output_height) = compute_output_size::<W, H>(
33 populated_atlas.font_width,
34 populated_atlas.font_height
35 );
36
37 let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
38 backends: wgpu::Backends::all(),
39 flags: wgpu::InstanceFlags::VALIDATION,
40 backend_options: wgpu::BackendOptions::default()
41 });
42
43 let adapter = instance.request_adapter(&wgpu::RequestAdapterOptions::default()).await.unwrap();
44 let max_buf_size = output_width * output_height * 4;
46
47 let (device, queue) = adapter.request_device(&wgpu::DeviceDescriptor {
48 required_features: wgpu::Features::SHADER_INT64,
49 required_limits: wgpu::Limits {
50 max_buffer_size: max_buf_size as u64,
51 max_storage_buffer_binding_size: max_buf_size,
52 ..wgpu::Limits::default()
53 },
54 memory_hints: wgpu::MemoryHints::Performance,
55 label: Some("device"),
56 trace: wgpu::Trace::Off
57 }).await.unwrap();
58
59 let idx_grid = device.create_buffer(&wgpu::BufferDescriptor {
60 label: Some("idx_grid"),
61 size: (H * W * 4) as u64,
62 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::STORAGE,
63 mapped_at_creation: false
64 });
65
66 let atlas = device.create_buffer(&wgpu::BufferDescriptor {
67 label: Some("atlas"),
68 size: populated_atlas.buffer.len() as u64,
69 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::STORAGE,
70 mapped_at_creation: false
71 });
72
73 queue.write_buffer(&atlas, 0, &populated_atlas.buffer);
75
76 let output_img = device.create_buffer(&wgpu::BufferDescriptor {
77 label: Some("output_buf"),
78 size: output_width as u64 * output_height as u64 * 4,
79 usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC,
80 mapped_at_creation: false
81 });
82
83 let grid_width_uniform = device.create_buffer(&wgpu::BufferDescriptor {
84 label: Some("grid_width_uniform"),
85 size: 4,
86 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
87 mapped_at_creation: false
88 });
89
90 let grid_height_uniform = device.create_buffer(&wgpu::BufferDescriptor {
91 label: Some("grid_height_uniform"),
92 size: 4,
93 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
94 mapped_at_creation: false
95 });
96
97 queue.write_buffer(&grid_width_uniform, 0, &(W as u32).to_ne_bytes());
98 queue.write_buffer(&grid_height_uniform, 0, &(H as u32).to_ne_bytes());
99
100 let img_width_uniform = device.create_buffer(&wgpu::BufferDescriptor {
101 label: Some("img_width_uniform"),
102 size: 4,
103 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
104 mapped_at_creation: false
105 });
106
107 let img_height_uniform = device.create_buffer(&wgpu::BufferDescriptor {
108 label: Some("img_height_uniform"),
109 size: 4,
110 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
111 mapped_at_creation: false
112 });
113
114 queue.write_buffer(&img_width_uniform, 0, &output_width.to_ne_bytes());
115 queue.write_buffer(&img_height_uniform, 0, &output_height.to_ne_bytes());
116
117 let color_grid = device.create_buffer(&wgpu::BufferDescriptor {
118 label: Some("color_grid"),
119 size: (W * H * 8) as u64,
120 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::STORAGE,
121 mapped_at_creation: false
122 });
123
124 let shader = device.create_shader_module(wgpu::include_wgsl!("shader.wgsl"));
125
126 let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
127 label: Some("bind_group_layout"),
128 entries: &[
129 wgpu::BindGroupLayoutEntry {
131 binding: 0,
132 visibility: wgpu::ShaderStages::COMPUTE,
133 ty: wgpu::BindingType::Buffer {
134 ty: wgpu::BufferBindingType::Storage {
135 read_only: true
136 },
137 has_dynamic_offset: false,
138 min_binding_size: None
139 },
140 count: None
141 },
142 wgpu::BindGroupLayoutEntry {
144 binding: 1,
145 visibility: wgpu::ShaderStages::COMPUTE,
146 ty: wgpu::BindingType::Buffer {
147 ty: wgpu::BufferBindingType::Storage {
148 read_only: true
149 },
150 has_dynamic_offset: false,
151 min_binding_size: None
152 },
153 count: None
154 },
155 wgpu::BindGroupLayoutEntry {
157 binding: 2,
158 visibility: wgpu::ShaderStages::COMPUTE,
159 ty: wgpu::BindingType::Buffer {
160 ty: wgpu::BufferBindingType::Storage {
161 read_only: false
162 },
163 has_dynamic_offset: false,
164 min_binding_size: None
165 },
166 count: None
167 },
168 wgpu::BindGroupLayoutEntry {
170 binding: 3,
171 visibility: wgpu::ShaderStages::COMPUTE,
172 ty: wgpu::BindingType::Buffer {
173 ty: wgpu::BufferBindingType::Uniform,
174 has_dynamic_offset: false,
175 min_binding_size: None
176 },
177 count: None
178 },
179 wgpu::BindGroupLayoutEntry {
181 binding: 4,
182 visibility: wgpu::ShaderStages::COMPUTE,
183 ty: wgpu::BindingType::Buffer {
184 ty: wgpu::BufferBindingType::Uniform,
185 has_dynamic_offset: false,
186 min_binding_size: None
187 },
188 count: None
189 },
190 wgpu::BindGroupLayoutEntry {
192 binding: 5,
193 visibility: wgpu::ShaderStages::COMPUTE,
194 ty: wgpu::BindingType::Buffer {
195 ty: wgpu::BufferBindingType::Uniform,
196 has_dynamic_offset: false,
197 min_binding_size: None
198 },
199 count: None
200 },
201 wgpu::BindGroupLayoutEntry {
203 binding: 6,
204 visibility: wgpu::ShaderStages::COMPUTE,
205 ty: wgpu::BindingType::Buffer {
206 ty: wgpu::BufferBindingType::Uniform,
207 has_dynamic_offset: false,
208 min_binding_size: None
209 },
210 count: None
211 },
212 wgpu::BindGroupLayoutEntry {
214 binding: 7,
215 visibility: wgpu::ShaderStages::COMPUTE,
216 ty: wgpu::BindingType::Buffer {
217 ty: wgpu::BufferBindingType::Storage {
218 read_only: true
219 },
220 has_dynamic_offset: false,
221 min_binding_size: None
222 },
223 count: None
224 }
225 ]
226 });
227
228 let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
229 label: Some("pipeline_layout"),
230 bind_group_layouts: &[&bind_group_layout],
231 push_constant_ranges: &[]
232 });
233
234 let pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
235 label: Some("pipeline"),
236 layout: Some(&pipeline_layout),
237 module: &shader,
238 entry_point: Some("sample_atlas"),
239 compilation_options: wgpu::PipelineCompilationOptions::default(),
240 cache: None
241 });
242
243 let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
244 label: Some("bind_group"),
245 layout: &bind_group_layout,
246 entries: &[
247 wgpu::BindGroupEntry {
248 binding: 0,
249 resource: idx_grid.as_entire_binding()
250 },
251 wgpu::BindGroupEntry {
252 binding: 1,
253 resource: atlas.as_entire_binding()
254 },
255 wgpu::BindGroupEntry {
256 binding: 2,
257 resource: output_img.as_entire_binding()
258 },
259 wgpu::BindGroupEntry {
260 binding: 3,
261 resource: grid_width_uniform.as_entire_binding()
262 },
263 wgpu::BindGroupEntry {
264 binding: 4,
265 resource: grid_height_uniform.as_entire_binding()
266 },
267 wgpu::BindGroupEntry {
268 binding: 5,
269 resource: img_width_uniform.as_entire_binding()
270 },
271 wgpu::BindGroupEntry {
272 binding: 6,
273 resource: img_height_uniform.as_entire_binding()
274 },
275 wgpu::BindGroupEntry {
276 binding: 7,
277 resource: color_grid.as_entire_binding()
278 }
279 ]
280 });
281
282 Self {
283 sequence,
284 lut: populated_atlas.lut,
285 device,
286 queue,
287 idx_grid,
288 output_img,
289 pipeline,
290 bind_group,
291 output_width,
292 output_height,
293 color_grid
294 }
295 }
296}
297
298pub struct RenderedFrame {
299 pub img: image::RgbaImage,
300 pub frame_hold: NonZeroU8
301}
302
303impl RenderedFrame {
304 fn deserialize(width: u32, height: u32, data: Vec<u8>, frame_hold: NonZeroU8) -> Self {
305 Self {
306 img: image::RgbaImage::from_raw(width, height, data).unwrap(),
307 frame_hold
308 }
309 }
310}
311
312#[inline]
313fn int_div_round_up(divisor: u32, dividend: u32) -> u32 {
314 (divisor / dividend) + match divisor % dividend {
315 0 => 0,
316 _ => 1
317 }
318}
319
320impl<const W: usize, const H: usize> Iterator for WgpuRenderer<W, H> {
321 type Item = RenderedFrame;
322
323 fn next(&mut self) -> Option<Self::Item> {
324 let frame = self.sequence.pop()?;
325
326 let mut encoder = self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
327 label: Some("encoder")
328 });
329
330 let frame_hold = frame.frame_hold;
331 self.queue.write_buffer(&self.color_grid, 0, &frame.serialize_colors());
332 self.queue.write_buffer(&self.idx_grid, 0, &frame.serialize(&self.lut));
333
334 let mut compute_pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
335 label: Some("sample_compute_pass"),
336 timestamp_writes: None
337 });
338 compute_pass.set_pipeline(&self.pipeline);
339 compute_pass.set_bind_group(0, &self.bind_group, &[]);
340 compute_pass.dispatch_workgroups(
341 int_div_round_up(self.output_width, 16),
342 int_div_round_up(self.output_height, 16),
343 1
344 );
345 drop(compute_pass);
346
347 let pixels = self.output_width * self.output_height;
348 let map_buf = self.device.create_buffer(&wgpu::BufferDescriptor {
349 label: Some("map_buf"),
350 size: pixels as u64 * 4,
351 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
352 mapped_at_creation: false
353 });
354
355 encoder.copy_buffer_to_buffer(
356 &self.output_img, 0,
357 &map_buf, 0,
358 pixels as u64 * 4
359 );
360
361 self.queue.submit(std::iter::once(encoder.finish()));
362
363 map_buf.map_async(wgpu::MapMode::Read, .., |r| r.unwrap());
364 self.device.poll(wgpu::PollType::Wait).unwrap();
365
366 let serialized_data = map_buf.get_mapped_range(..).to_vec();
367 Some(RenderedFrame::deserialize(
368 self.output_width,
369 self.output_height,
370 serialized_data,
371 frame_hold
372 ))
373 }
374}
375
376mod private {
377 use super::WgpuRenderer;
378
379 pub trait Sealed {}
380
381 impl<const W: usize, const H: usize> Sealed for WgpuRenderer<W, H> {}
382}
383
384pub trait VideoSrc: Iterator<Item = RenderedFrame> + Send + 'static + private::Sealed {
385 fn framerate(&self) -> NonZeroU8;
386 fn width(&self) -> u32;
387 fn height(&self) -> u32;
388}
389
390impl<const W: usize, const H: usize> VideoSrc for WgpuRenderer<W, H> {
391 #[inline]
392 fn framerate(&self) -> NonZeroU8 {
393 self.sequence.framerate
394 }
395
396 #[inline]
397 fn width(&self) -> u32 {
398 self.output_width
399 }
400
401 #[inline]
402 fn height(&self) -> u32 {
403 self.output_height
404 }
405}