Skip to main content

wgpu_primitives/scan/
scanner.rs

1use super::pipeline::ScanPipeline;
2use crate::{Error, common, context::Context};
3
4#[derive(Clone, Copy)]
5enum ScanMode {
6    Inclusive,
7    Exclusive,
8}
9
10/// Performs inclusive and exclusive unsigned 32-bit prefix scans on a wgpu device.
11pub 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    /// Creates a scanner that submits work through an existing wgpu device and queue.
21    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    /// Creates a scanner from the crate's optional convenience context.
32    pub fn from_context(ctx: &Context) -> Self {
33        Self::new(&ctx.device, &ctx.queue)
34    }
35
36    /// Uploads values, scans them on the GPU, and downloads the inclusive prefixes.
37    pub async fn scan(&mut self, input: &[u32]) -> Result<Vec<u32>, Error> {
38        self.scan_slice(input, ScanMode::Inclusive).await
39    }
40
41    /// Uploads values, scans them on the GPU, and downloads the exclusive prefixes.
42    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    /// Scans caller-owned GPU buffers and submits the work immediately.
62    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    /// Exclusively scans caller-owned GPU buffers and submits the work immediately.
72    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    /// Records a GPU prefix scan without submitting or waiting for the work.
97    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    /// Records an exclusive GPU prefix scan without submitting or waiting for the work.
114    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}