1use super::pipeline::ScanPipeline;
2use crate::{Error, common, context::Context};
3
4#[derive(Clone, Copy)]
5enum ScanMode {
6 Inclusive,
7 Exclusive,
8}
9
10pub struct Scanner {
12 pipeline: ScanPipeline,
13 device: wgpu::Device,
14 queue: wgpu::Queue,
15 scratch_buffer: Option<wgpu::Buffer>,
16 scratch_size_bytes: u64,
17}
18
19impl Scanner {
20 pub fn new(device: &wgpu::Device, queue: &wgpu::Queue) -> Self {
22 Self {
23 pipeline: ScanPipeline::new(device),
24 device: device.clone(),
25 queue: queue.clone(),
26 scratch_buffer: None,
27 scratch_size_bytes: 0,
28 }
29 }
30
31 pub fn from_context(ctx: &Context) -> Self {
33 Self::new(&ctx.device, &ctx.queue)
34 }
35
36 pub async fn scan(&mut self, input: &[u32]) -> Result<Vec<u32>, Error> {
38 self.scan_slice(input, ScanMode::Inclusive).await
39 }
40
41 pub async fn scan_exclusive(&mut self, input: &[u32]) -> Result<Vec<u32>, Error> {
43 self.scan_slice(input, ScanMode::Exclusive).await
44 }
45
46 async fn scan_slice(&mut self, input: &[u32], mode: ScanMode) -> Result<Vec<u32>, Error> {
47 if input.is_empty() {
48 return Ok(Vec::new());
49 }
50
51 let num_items = common::math::checked_u32(input.len() as u64)?;
52 let data_buffer = common::buffers::create_storage_buffer(&self.device, input);
53 let dst_buffer =
54 common::buffers::create_empty_storage_buffer(&self.device, data_buffer.size());
55
56 self.submit_scan(&data_buffer, &dst_buffer, num_items, mode)?;
57
58 common::buffers::download_buffer(&self.device, &self.queue, &dst_buffer, input.len()).await
59 }
60
61 pub fn scan_gpu_to_gpu(
63 &mut self,
64 input_buf: &wgpu::Buffer,
65 output_buf: &wgpu::Buffer,
66 num_items: u32,
67 ) -> Result<(), Error> {
68 self.submit_scan(input_buf, output_buf, num_items, ScanMode::Inclusive)
69 }
70
71 pub fn scan_exclusive_gpu_to_gpu(
73 &mut self,
74 input_buf: &wgpu::Buffer,
75 output_buf: &wgpu::Buffer,
76 num_items: u32,
77 ) -> Result<(), Error> {
78 self.submit_scan(input_buf, output_buf, num_items, ScanMode::Exclusive)
79 }
80
81 fn submit_scan(
82 &mut self,
83 input_buf: &wgpu::Buffer,
84 output_buf: &wgpu::Buffer,
85 num_items: u32,
86 mode: ScanMode,
87 ) -> Result<(), Error> {
88 let mut encoder = self
89 .device
90 .create_command_encoder(&wgpu::CommandEncoderDescriptor::default());
91 self.record_scan_with_mode(&mut encoder, input_buf, output_buf, num_items, mode)?;
92 self.queue.submit(Some(encoder.finish()));
93 Ok(())
94 }
95
96 pub fn record_scan(
98 &mut self,
99 encoder: &mut wgpu::CommandEncoder,
100 input_buf: &wgpu::Buffer,
101 output_buf: &wgpu::Buffer,
102 num_items: u32,
103 ) -> Result<(), Error> {
104 self.record_scan_with_mode(
105 encoder,
106 input_buf,
107 output_buf,
108 num_items,
109 ScanMode::Inclusive,
110 )
111 }
112
113 pub fn record_exclusive_scan(
115 &mut self,
116 encoder: &mut wgpu::CommandEncoder,
117 input_buf: &wgpu::Buffer,
118 output_buf: &wgpu::Buffer,
119 num_items: u32,
120 ) -> Result<(), Error> {
121 self.record_scan_with_mode(
122 encoder,
123 input_buf,
124 output_buf,
125 num_items,
126 ScanMode::Exclusive,
127 )
128 }
129
130 fn record_scan_with_mode(
131 &mut self,
132 encoder: &mut wgpu::CommandEncoder,
133 input_buf: &wgpu::Buffer,
134 output_buf: &wgpu::Buffer,
135 num_items: u32,
136 mode: ScanMode,
137 ) -> Result<(), Error> {
138 if num_items == 0 {
139 return Ok(());
140 }
141
142 let size_bytes = common::math::checked_byte_size(u64::from(num_items), 4)?;
143 common::buffers::validate_buffer(
144 input_buf,
145 "scan input",
146 size_bytes,
147 wgpu::BufferUsages::COPY_SRC,
148 )?;
149 common::buffers::validate_buffer(
150 output_buf,
151 "scan output",
152 size_bytes,
153 wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::STORAGE,
154 )?;
155
156 if num_items == 1 {
157 match mode {
158 ScanMode::Inclusive => {
159 encoder.copy_buffer_to_buffer(input_buf, 0, output_buf, 0, size_bytes);
160 }
161 ScanMode::Exclusive => encoder.clear_buffer(output_buf, 0, Some(size_bytes)),
162 }
163 return Ok(());
164 }
165
166 encoder.copy_buffer_to_buffer(input_buf, 0, output_buf, 0, size_bytes);
167
168 self.prepare_scratch(num_items);
169
170 let scratch = self
171 .scratch_buffer
172 .as_ref()
173 .expect("scan scratch exists for multi-element inputs");
174
175 struct Level<'a> {
176 buf: &'a wgpu::Buffer,
177 offset: u64,
178 count: u32,
179 }
180
181 let mut levels = Vec::new();
182 levels.push(Level {
183 buf: output_buf,
184 offset: 0,
185 count: num_items,
186 });
187
188 let mut current_scratch_offset = 0u64;
189
190 loop {
191 let current = levels.last().unwrap();
192 if current.count <= 1 {
193 break;
194 }
195
196 let items_per_block = self.pipeline.vt * self.pipeline.block_size;
197
198 let aux_count = current.count.div_ceil(items_per_block);
199 let aux_size = (aux_count * 4) as u64;
200 let aux_offset = crate::common::math::align_to(current_scratch_offset, 256);
201
202 let scan_pipeline = match (levels.len(), mode) {
203 (1, ScanMode::Exclusive) => &self.pipeline.exclusive_scan_pipeline,
204 _ => &self.pipeline.inclusive_scan_pipeline,
205 };
206
207 self.pipeline.dispatch(
208 &self.device,
209 encoder,
210 scan_pipeline,
211 (current.buf, current.offset),
212 (scratch, aux_offset),
213 current.count,
214 );
215
216 levels.push(Level {
217 buf: scratch,
218 offset: aux_offset,
219 count: aux_count,
220 });
221 current_scratch_offset = aux_offset + aux_size;
222 }
223
224 for i in (0..levels.len() - 1).rev() {
225 let data_level = &levels[i];
226 let aux_level = &levels[i + 1];
227
228 self.pipeline.dispatch(
229 &self.device,
230 encoder,
231 &self.pipeline.add_pipeline,
232 (data_level.buf, data_level.offset),
233 (aux_level.buf, aux_level.offset),
234 data_level.count,
235 );
236 }
237
238 Ok(())
239 }
240
241 fn prepare_scratch(&mut self, num_items: u32) {
242 let needed_bytes = self.pipeline.get_scratch_size(num_items);
243 if self.scratch_buffer.is_none() || needed_bytes > self.scratch_size_bytes {
244 self.scratch_buffer = Some(self.device.create_buffer(&wgpu::BufferDescriptor {
245 label: Some("Scanner Scratch"),
246 size: needed_bytes,
247 usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC,
248 mapped_at_creation: false,
249 }));
250 self.scratch_size_bytes = needed_bytes;
251 }
252 }
253}