dvd_render/
render.rs

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		// I expect that the size of the atlas should be bounded by the size of the output
45		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		// TODO: investigate efficiency of `write_buffer`
74		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				// idx_grid
130				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				// atlas
143				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				// output_img
156				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				// grid_width
169				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				// grid_height
180				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				// grid_width
191				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				// grid_height
202				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				// color_grid
213				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}