wgpu_util/
lib.rs

1//! wgpu-util is a utility crate for working with wgpu-rs.
2
3/// Owned [`wgpu::Label`].
4pub type OwnedLabel = Option<String>;
5
6/// [`wgpu::util::BufferInitDescriptor`] but with an additional `size` field.
7#[derive(Clone, Debug, PartialEq, Eq, Hash)]
8pub struct BufferInitDescriptor<'a> {
9    /// Debug label of a buffer. This will show up in graphics debuggers for easy identification.
10    pub label: wgpu::Label<'a>,
11    /// Contents of a buffer on creation.
12    pub contents: &'a [u8],
13    /// Size of the buffer. Must be at least size of `contents`. If unspecified, the size of `contents` is used.
14    pub size: Option<wgpu::BufferAddress>,
15    /// Usages of a buffer. If the buffer is used in any way that isn't specified here, the operation
16    /// will panic.
17    pub usage: wgpu::BufferUsages,
18}
19
20/// Extension trait for [`wgpu::Device`].
21pub trait DeviceExt {
22    /// [`wgpu::util::DeviceExt::create_buffer_init`] but for this [`BufferInitDescriptor`]
23    /// (includes size field).
24    fn create_buffer_init(&self, desc: &BufferInitDescriptor<'_>) -> wgpu::Buffer;
25}
26
27impl DeviceExt for wgpu::Device {
28    fn create_buffer_init(&self, descriptor: &BufferInitDescriptor<'_>) -> wgpu::Buffer {
29        let unpadded_size = {
30            let contents_size = descriptor.contents.len() as wgpu::BufferAddress;
31            match descriptor.size {
32                None => contents_size,
33                Some(specified_size) => {
34                    assert!(
35                        specified_size >= contents_size,
36                        "specified size must at least be size of contents"
37                    );
38                    specified_size
39                }
40            }
41        };
42
43        // Valid vulkan usage is
44        // 1. buffer size must be a multiple of COPY_BUFFER_ALIGNMENT.
45        // 2. buffer size must be greater than 0.
46        // Therefore we round the value up to the nearest multiple, and ensure it's at least COPY_BUFFER_ALIGNMENT.
47        let align_mask = wgpu::COPY_BUFFER_ALIGNMENT - 1;
48        let padded_size =
49            ((unpadded_size + align_mask) & !align_mask).max(wgpu::COPY_BUFFER_ALIGNMENT);
50
51        let normal_descriptor = wgpu::BufferDescriptor {
52            label: descriptor.label,
53            size: padded_size,
54            usage: descriptor.usage,
55            mapped_at_creation: true,
56        };
57
58        let buffer = self.create_buffer(&normal_descriptor);
59        {
60            let mut slice = buffer.slice(..).get_mapped_range_mut();
61            slice[0..unpadded_size as usize].copy_from_slice(descriptor.contents);
62
63            for i in unpadded_size..padded_size {
64                slice[i as usize] = 0;
65            }
66        }
67        buffer.unmap();
68        buffer
69    }
70}
71
72/// Thin [`wgpu::Buffer`] wrapper with size.
73#[derive(Debug)]
74pub struct SizedBuffer {
75    pub size: wgpu::BufferAddress,
76    pub buffer: wgpu::Buffer,
77}
78
79impl SizedBuffer {
80    pub fn new(size: wgpu::BufferAddress, buffer: wgpu::Buffer) -> Self {
81        Self { size, buffer }
82    }
83}
84
85pub struct BufferResizeWriteDescriptor<'a> {
86    pub label: wgpu::Label<'a>,
87    pub contents: &'a [u8],
88    pub usage: wgpu::BufferUsages,
89}
90
91/// Write contents into buffer, resizes if necessary.
92///
93/// If contents don't fit, creates new buffer with appropriate size.
94pub fn resize_write_buffer(
95    device: &wgpu::Device,
96    queue: &wgpu::Queue,
97    buffer: SizedBuffer,
98    descriptor: &BufferResizeWriteDescriptor,
99) -> SizedBuffer {
100    let contents_size = descriptor.contents.len() as wgpu::BufferAddress;
101    let enough_space = contents_size <= buffer.size;
102    if enough_space {
103        queue.write_buffer(&buffer.buffer, 0, descriptor.contents);
104        buffer
105    } else {
106        let new = device.create_buffer_init(&BufferInitDescriptor {
107            label: descriptor.label,
108            contents: descriptor.contents,
109            size: None,
110            usage: descriptor.usage,
111        });
112        SizedBuffer::new(contents_size, new)
113    }
114}
115
116/// A [`wgpu::Buffer`] which dynamically grows based on the contents.
117#[derive(Debug)]
118pub struct DynamicBuffer {
119    raw: wgpu::Buffer,
120
121    label: crate::OwnedLabel,
122    size: wgpu::BufferAddress,
123    usage: wgpu::BufferUsages,
124}
125
126impl DynamicBuffer {
127    const RESERVE: bool = true;
128
129    /// Create a new empty buffer.
130    pub fn new(device: &wgpu::Device, descriptor: &wgpu::BufferDescriptor) -> Self {
131        let raw = device.create_buffer(descriptor);
132
133        Self {
134            raw,
135            label: descriptor.label.map(|l| l.to_owned()),
136            size: descriptor.size,
137            usage: descriptor.usage,
138        }
139    }
140
141    /// Create a new buffer with contents.
142    pub fn new_init(device: &wgpu::Device, descriptor: &crate::BufferInitDescriptor) -> Self {
143        let raw = device.create_buffer_init(descriptor);
144
145        let descriptor = wgpu::BufferDescriptor {
146            label: descriptor.label,
147            size: descriptor.contents.len() as wgpu::BufferAddress,
148            usage: descriptor.usage,
149            mapped_at_creation: false,
150        };
151
152        Self {
153            raw,
154            label: descriptor.label.map(|l| l.to_owned()),
155            size: descriptor.size,
156            usage: descriptor.usage,
157        }
158    }
159
160    /// Uploads `contents` and resizes the buffer if needed.
161    ///
162    /// If `contents` fits, uploads using [`wgpu::Queue`], otherwise reallocates and uploads using
163    /// [`wgpu::Device`].
164    pub fn upload(&mut self, device: &wgpu::Device, queue: &wgpu::Queue, contents: &[u8]) {
165        if self.try_upload(queue, contents).is_err() {
166            self.upload_by_init(device, contents)
167        }
168    }
169
170    /// Uploades `data` using [`wgpu::Queue`] without resizing.
171    /// Fails if `data` doesn't fit in buffers and returns the size difference.
172    pub fn try_upload(
173        &mut self,
174        queue: &wgpu::Queue,
175        contents: &[u8],
176    ) -> Result<(), wgpu::BufferAddress> {
177        let contents_size = contents.len() as wgpu::BufferAddress;
178        if contents_size < self.size {
179            queue.write_buffer(&self.raw, 0, contents);
180            self.size = contents_size;
181            Ok(())
182        } else {
183            Err(contents_size - self.size)
184        }
185    }
186
187    /// Allocates a new buffer, replaces the old one and uploades the contents using
188    /// [`wgpu::Device`].
189    pub fn upload_by_init(&mut self, device: &wgpu::Device, contents: &[u8]) {
190        device.create_buffer_init(&crate::BufferInitDescriptor {
191            label: self.label.as_deref(),
192            contents,
193            usage: self.usage,
194            size: match Self::RESERVE {
195                true => Some(reserve_function(self.size)),
196                false => None,
197            },
198        });
199    }
200
201    /// Get a reference to the raw buffer.
202    pub fn raw(&self) -> &wgpu::Buffer {
203        &self.raw
204    }
205
206    /// Convert into raw buffer.
207    pub fn into_raw(self) -> wgpu::Buffer {
208        self.raw
209    }
210}
211
212fn reserve_function(last_size: wgpu::BufferAddress) -> wgpu::BufferAddress {
213    last_size.pow(2)
214}
215
216/// A [`wgpu::Buffer`] Pool (dynamic supply).
217#[derive(Debug)]
218pub struct BufferPool {
219    buffers: Vec<SizedBuffer>,
220    occupied: usize,
221
222    label: crate::OwnedLabel,
223    usage: wgpu::BufferUsages,
224}
225
226impl BufferPool {
227    /// Creates a new empty pool.
228    pub fn new(descriptor: &BufferPoolDescriptor) -> Self {
229        Self {
230            buffers: Vec::new(),
231            occupied: 0,
232
233            label: descriptor.label.map(|l| l.to_owned()),
234            usage: descriptor.usage,
235        }
236    }
237
238    /// Upload contents to a vacant buffer.
239    ///
240    /// Returns buffer index.
241    /// If no vacant buffer is available, a new one is allocated.
242    pub fn upload(&mut self, device: &wgpu::Device, queue: &wgpu::Queue, contents: &[u8]) -> usize {
243        if self.occupied < self.buffers.len() {
244            let buffer = &mut self.buffers[self.occupied];
245
246            // CCDF
247            let label = self.label.as_deref();
248            let usage = self.usage;
249            replace_with::replace_with_or_abort(buffer, |buffer| {
250                resize_write_buffer(
251                    device,
252                    queue,
253                    buffer,
254                    &BufferResizeWriteDescriptor {
255                        label,
256                        contents,
257                        usage,
258                    },
259                )
260            });
261        } else {
262            self.buffers.push(self.create_buffer(device, contents));
263        }
264        self.occupied += 1;
265        self.occupied
266    }
267
268    /// Clears pool. Buffers are marked as vacant and reusable.
269    pub fn clear(&mut self) {
270        self.occupied = 0;
271    }
272
273    /// Get occupied buffer by index.
274    pub fn get(&self, i: usize) -> Option<&wgpu::Buffer> {
275        if i < self.occupied {
276            Some(&self.buffers[i].buffer)
277        } else {
278            None
279        }
280    }
281
282    /// Get any (occupied and vacant) buffer by index.
283    pub fn get_any(&self, i: usize) -> Option<&wgpu::Buffer> {
284        self.buffers.get(i).map(|b| &b.buffer)
285    }
286
287    /// Pool size (occupied + vacant)
288    pub fn size(&self) -> usize {
289        self.buffers.len()
290    }
291
292    /// Number of occupied buffers
293    pub fn occupied(&self) -> usize {
294        self.occupied
295    }
296}
297
298impl BufferPool {
299    fn create_buffer(&self, device: &wgpu::Device, contents: &[u8]) -> SizedBuffer {
300        let buffer = device.create_buffer_init(&BufferInitDescriptor {
301            label: self.label.as_deref(),
302            contents,
303            usage: self.usage,
304            size: None,
305        });
306        SizedBuffer::new(contents.len() as wgpu::BufferAddress, buffer)
307    }
308}
309
310/// Descriptor for [`BufferPool`]
311pub struct BufferPoolDescriptor<'a> {
312    /// Label assigned to all buffers
313    pub label: wgpu::Label<'a>,
314    /// Usages for all buffer
315    pub usage: wgpu::BufferUsages,
316}