1use super::pipeline::{ScanDispatch, ScanPipeline};
2use crate::{
3 Error, common,
4 context::Context,
5 profiling::{GpuProfile, TimestampRecorder},
6};
7
8#[derive(Clone, Copy)]
9enum ScanMode {
10 Inclusive,
11 Exclusive,
12}
13
14struct ScanRecording<'a> {
15 input: &'a wgpu::Buffer,
16 output: &'a wgpu::Buffer,
17 num_items: u32,
18 mode: ScanMode,
19 profile_prefix: &'a str,
20}
21
22pub struct Scanner {
24 pipeline: ScanPipeline,
25 device: wgpu::Device,
26 queue: wgpu::Queue,
27 scratch_buffer: Option<wgpu::Buffer>,
28 scratch_size_bytes: u64,
29}
30
31impl Scanner {
32 pub fn new(device: &wgpu::Device, queue: &wgpu::Queue) -> Self {
34 Self {
35 pipeline: ScanPipeline::new(device),
36 device: device.clone(),
37 queue: queue.clone(),
38 scratch_buffer: None,
39 scratch_size_bytes: 0,
40 }
41 }
42
43 pub fn from_context(ctx: &Context) -> Self {
45 Self::new(&ctx.device, &ctx.queue)
46 }
47
48 pub async fn scan(&mut self, input: &[u32]) -> Result<Vec<u32>, Error> {
50 self.scan_slice(input, ScanMode::Inclusive).await
51 }
52
53 pub async fn scan_exclusive(&mut self, input: &[u32]) -> Result<Vec<u32>, Error> {
55 self.scan_slice(input, ScanMode::Exclusive).await
56 }
57
58 async fn scan_slice(&mut self, input: &[u32], mode: ScanMode) -> Result<Vec<u32>, Error> {
59 if input.is_empty() {
60 return Ok(Vec::new());
61 }
62
63 let num_items = common::math::checked_u32(input.len() as u64)?;
64 let data_buffer = common::buffers::create_storage_buffer(&self.device, input);
65 let dst_buffer =
66 common::buffers::create_empty_storage_buffer(&self.device, data_buffer.size());
67
68 self.submit_scan(&data_buffer, &dst_buffer, num_items, mode)?;
69
70 common::buffers::download_buffer(&self.device, &self.queue, &dst_buffer, input.len()).await
71 }
72
73 pub fn scan_gpu_to_gpu(
75 &mut self,
76 input_buf: &wgpu::Buffer,
77 output_buf: &wgpu::Buffer,
78 num_items: u32,
79 ) -> Result<(), Error> {
80 self.submit_scan(input_buf, output_buf, num_items, ScanMode::Inclusive)
81 }
82
83 pub fn scan_exclusive_gpu_to_gpu(
85 &mut self,
86 input_buf: &wgpu::Buffer,
87 output_buf: &wgpu::Buffer,
88 num_items: u32,
89 ) -> Result<(), Error> {
90 self.submit_scan(input_buf, output_buf, num_items, ScanMode::Exclusive)
91 }
92
93 fn submit_scan(
94 &mut self,
95 input_buf: &wgpu::Buffer,
96 output_buf: &wgpu::Buffer,
97 num_items: u32,
98 mode: ScanMode,
99 ) -> Result<(), Error> {
100 let mut encoder = self
101 .device
102 .create_command_encoder(&wgpu::CommandEncoderDescriptor::default());
103 self.record_scan_with_mode(
104 &mut encoder,
105 ScanRecording {
106 input: input_buf,
107 output: output_buf,
108 num_items,
109 mode,
110 profile_prefix: "scan",
111 },
112 None,
113 )?;
114 self.queue.submit(Some(encoder.finish()));
115 Ok(())
116 }
117
118 pub async fn profile_scan_gpu_to_gpu(
120 &mut self,
121 input_buf: &wgpu::Buffer,
122 output_buf: &wgpu::Buffer,
123 num_items: u32,
124 ) -> Result<GpuProfile, Error> {
125 self.profile_scan_with_mode(input_buf, output_buf, num_items, ScanMode::Inclusive)
126 .await
127 }
128
129 pub async fn profile_exclusive_scan_gpu_to_gpu(
131 &mut self,
132 input_buf: &wgpu::Buffer,
133 output_buf: &wgpu::Buffer,
134 num_items: u32,
135 ) -> Result<GpuProfile, Error> {
136 self.profile_scan_with_mode(input_buf, output_buf, num_items, ScanMode::Exclusive)
137 .await
138 }
139
140 async fn profile_scan_with_mode(
141 &mut self,
142 input_buf: &wgpu::Buffer,
143 output_buf: &wgpu::Buffer,
144 num_items: u32,
145 mode: ScanMode,
146 ) -> Result<GpuProfile, Error> {
147 let span_count = self.pipeline.compute_pass_count(num_items);
148 if span_count == 0 {
149 let mut encoder = self
150 .device
151 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
152 label: Some("Profiled Trivial Scan"),
153 });
154 self.record_scan_with_mode(
155 &mut encoder,
156 ScanRecording {
157 input: input_buf,
158 output: output_buf,
159 num_items,
160 mode,
161 profile_prefix: "scan",
162 },
163 None,
164 )?;
165 let submission = self.queue.submit(Some(encoder.finish()));
166 self.device.poll(wgpu::PollType::Wait {
167 submission_index: Some(submission),
168 timeout: None,
169 })?;
170 return Ok(GpuProfile::empty());
171 }
172
173 let mut profiler = TimestampRecorder::new(&self.device, &self.queue, span_count)?;
174 let mut encoder = self
175 .device
176 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
177 label: Some("Profiled Prefix Scan"),
178 });
179 self.record_scan_with_mode(
180 &mut encoder,
181 ScanRecording {
182 input: input_buf,
183 output: output_buf,
184 num_items,
185 mode,
186 profile_prefix: "scan",
187 },
188 Some(&mut profiler),
189 )?;
190 profiler.resolve(&mut encoder);
191 let submission = self.queue.submit(Some(encoder.finish()));
192 profiler.read(&self.device, submission).await
193 }
194
195 pub fn record_scan(
197 &mut self,
198 encoder: &mut wgpu::CommandEncoder,
199 input_buf: &wgpu::Buffer,
200 output_buf: &wgpu::Buffer,
201 num_items: u32,
202 ) -> Result<(), Error> {
203 self.record_scan_with_mode(
204 encoder,
205 ScanRecording {
206 input: input_buf,
207 output: output_buf,
208 num_items,
209 mode: ScanMode::Inclusive,
210 profile_prefix: "scan",
211 },
212 None,
213 )
214 }
215
216 pub fn record_exclusive_scan(
218 &mut self,
219 encoder: &mut wgpu::CommandEncoder,
220 input_buf: &wgpu::Buffer,
221 output_buf: &wgpu::Buffer,
222 num_items: u32,
223 ) -> Result<(), Error> {
224 self.record_scan_with_mode(
225 encoder,
226 ScanRecording {
227 input: input_buf,
228 output: output_buf,
229 num_items,
230 mode: ScanMode::Exclusive,
231 profile_prefix: "scan",
232 },
233 None,
234 )
235 }
236
237 pub(crate) fn record_profiled_scan(
238 &mut self,
239 encoder: &mut wgpu::CommandEncoder,
240 input_buf: &wgpu::Buffer,
241 output_buf: &wgpu::Buffer,
242 num_items: u32,
243 profile_prefix: &str,
244 profiler: &mut TimestampRecorder,
245 ) -> Result<(), Error> {
246 self.record_scan_with_mode(
247 encoder,
248 ScanRecording {
249 input: input_buf,
250 output: output_buf,
251 num_items,
252 mode: ScanMode::Inclusive,
253 profile_prefix,
254 },
255 Some(profiler),
256 )
257 }
258
259 pub(crate) fn compute_pass_count(&self, num_items: u32) -> u32 {
260 self.pipeline.compute_pass_count(num_items)
261 }
262
263 fn record_scan_with_mode(
264 &mut self,
265 encoder: &mut wgpu::CommandEncoder,
266 recording: ScanRecording<'_>,
267 mut profiler: Option<&mut TimestampRecorder>,
268 ) -> Result<(), Error> {
269 let ScanRecording {
270 input,
271 output,
272 num_items,
273 mode,
274 profile_prefix,
275 } = recording;
276 if num_items == 0 {
277 return Ok(());
278 }
279
280 let size_bytes = common::math::checked_byte_size(u64::from(num_items), 4)?;
281 common::buffers::validate_buffer(
282 input,
283 "scan input",
284 size_bytes,
285 wgpu::BufferUsages::COPY_SRC,
286 )?;
287 common::buffers::validate_buffer(
288 output,
289 "scan output",
290 size_bytes,
291 wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::STORAGE,
292 )?;
293
294 if num_items == 1 {
295 match mode {
296 ScanMode::Inclusive => {
297 encoder.copy_buffer_to_buffer(input, 0, output, 0, size_bytes);
298 }
299 ScanMode::Exclusive => encoder.clear_buffer(output, 0, Some(size_bytes)),
300 }
301 return Ok(());
302 }
303
304 encoder.copy_buffer_to_buffer(input, 0, output, 0, size_bytes);
305
306 self.prepare_scratch(num_items);
307
308 let scratch = self
309 .scratch_buffer
310 .as_ref()
311 .expect("scan scratch exists for multi-element inputs");
312
313 struct Level<'a> {
314 buf: &'a wgpu::Buffer,
315 offset: u64,
316 count: u32,
317 }
318
319 let mut levels = Vec::new();
320 levels.push(Level {
321 buf: output,
322 offset: 0,
323 count: num_items,
324 });
325
326 let mut current_scratch_offset = 0u64;
327
328 loop {
329 let current = levels.last().unwrap();
330 if current.count <= 1 {
331 break;
332 }
333
334 let items_per_block = self.pipeline.vt * self.pipeline.block_size;
335
336 let aux_count = current.count.div_ceil(items_per_block);
337 let aux_size = (aux_count * 4) as u64;
338 let aux_offset = crate::common::math::align_to(current_scratch_offset, 256);
339
340 let scan_pipeline = match (levels.len(), mode) {
341 (1, ScanMode::Exclusive) => &self.pipeline.exclusive_scan_pipeline,
342 _ => &self.pipeline.inclusive_scan_pipeline,
343 };
344 let profile_label = profiler
345 .is_some()
346 .then(|| format!("{profile_prefix}.level.{}", levels.len() - 1));
347
348 self.pipeline.dispatch(
349 &self.device,
350 encoder,
351 ScanDispatch {
352 pipeline: scan_pipeline,
353 data: (current.buf, current.offset),
354 auxiliary: (scratch, aux_offset),
355 num_items: current.count,
356 pass_label: "Prefix Scan",
357 profile_label,
358 },
359 profiler.as_deref_mut(),
360 );
361
362 levels.push(Level {
363 buf: scratch,
364 offset: aux_offset,
365 count: aux_count,
366 });
367 current_scratch_offset = aux_offset + aux_size;
368 }
369
370 for i in (0..levels.len() - 1).rev() {
371 let data_level = &levels[i];
372 let aux_level = &levels[i + 1];
373 let profile_label = profiler
374 .is_some()
375 .then(|| format!("{profile_prefix}.add.{i}"));
376
377 self.pipeline.dispatch(
378 &self.device,
379 encoder,
380 ScanDispatch {
381 pipeline: &self.pipeline.add_pipeline,
382 data: (data_level.buf, data_level.offset),
383 auxiliary: (aux_level.buf, aux_level.offset),
384 num_items: data_level.count,
385 pass_label: "Prefix Add",
386 profile_label,
387 },
388 profiler.as_deref_mut(),
389 );
390 }
391
392 Ok(())
393 }
394
395 fn prepare_scratch(&mut self, num_items: u32) {
396 let needed_bytes = self.pipeline.get_scratch_size(num_items);
397 if self.scratch_buffer.is_none() || needed_bytes > self.scratch_size_bytes {
398 self.scratch_buffer = Some(self.device.create_buffer(&wgpu::BufferDescriptor {
399 label: Some("Scanner Scratch"),
400 size: needed_bytes,
401 usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC,
402 mapped_at_creation: false,
403 }));
404 self.scratch_size_bytes = needed_bytes;
405 }
406 }
407}