wde_wgpu/resources/buffer.rs
1//! GPU buffer helper utilities built on top of `wgpu::Buffer`.
2
3use std::{fmt::Formatter, sync::mpsc};
4use wde_logger::prelude::*;
5use wgpu::{BufferView, util::DeviceExt};
6
7use crate::{command_buffer::CommandBuffer, instance::RenderInstanceData};
8
9/// Buffer usages.
10pub type BufferUsage = wgpu::BufferUsages;
11
12/// Buffer binding types.
13pub type BufferBindingType = wgpu::BufferBindingType;
14
15/// Map the buffer.
16pub type BufferViewMut = wgpu::BufferViewMut;
17
18/// Create and manage GPU buffers.
19///
20/// # Examples
21/// Write once, render many:
22/// ```rust,no_run
23/// use wde_wgpu::{buffer::{Buffer, BufferUsage}, instance::RenderInstanceData};
24///
25/// let vertices: &[f32] = &[0.0, 0.5, 0.0, -0.5, -0.5, 0.0, 0.5, -0.5, 0.0];
26/// let mut vertex_buffer = Buffer::new(
27/// instance,
28/// "triangle-vertices",
29/// vertices.len() * std::mem::size_of::<f32>(),
30/// BufferUsage::VERTEX | BufferUsage::COPY_DST,
31/// None,
32/// );
33/// vertex_buffer.write(instance, bytemuck::cast_slice(vertices), 0);
34/// ```
35///
36/// Read back GPU results:
37/// ```rust,no_run
38/// use wde_wgpu::{buffer::{Buffer, BufferUsage}, instance::RenderInstanceData};
39///
40/// let readback = Buffer::new(
41/// instance,
42/// "readback",
43/// 1024,
44/// BufferUsage::MAP_READ | BufferUsage::COPY_DST,
45/// None,
46/// );
47/// readback.map_read(instance, |view| {
48/// let floats: &[f32] = bytemuck::cast_slice(&view);
49/// println!("first value = {}", floats.get(0).unwrap_or(&0.0));
50/// });
51/// ```
52///
53/// Copy between GPU resources:
54/// ```rust,no_run
55/// use wde_wgpu::{buffer::{Buffer, BufferUsage}, instance::RenderInstanceData};
56///
57/// let src = Buffer::new(instance, "src", 256, BufferUsage::COPY_SRC, Some(&[1u8; 256]));
58/// let dst = Buffer::new(instance, "dst", 256, BufferUsage::COPY_DST, None);
59/// dst.copy_from_buffer(instance, &src);
60/// ```
61pub struct Buffer {
62 pub label: String,
63 pub buffer: wgpu::Buffer
64}
65
66impl std::fmt::Debug for Buffer {
67 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
68 f.debug_struct("Buffer")
69 .field("label", &self.label)
70 .field("buffer_size", &self.buffer.size())
71 .finish()
72 }
73}
74
75impl Buffer {
76 /// Create a new buffer.
77 ///
78 /// # Arguments
79 ///
80 /// * `instance` - The render instance.
81 /// * `label` - The label of the buffer.
82 /// * `size` - The size of the buffer.
83 /// * `usage` - The usage of the buffer (vertex, index, uniform, storage).
84 /// * `content` - The content of the buffer.
85 pub fn new(
86 instance: &RenderInstanceData,
87 label: &str,
88 size: usize,
89 usage: BufferUsage,
90 content: Option<&[u8]>
91 ) -> Self {
92 event!(LogLevel::TRACE, "Creating new buffer {}.", label);
93
94 // In case the content is not provided, create an empty buffer.
95 match content {
96 Some(content) => {
97 // Create buffer
98 let buffer =
99 instance
100 .device
101 .create_buffer_init(&wgpu::util::BufferInitDescriptor {
102 label: Some(format!("{}-buffer", label).as_str()),
103 contents: content,
104 usage
105 });
106
107 Buffer {
108 label: label.to_string(),
109 buffer
110 }
111 }
112 None => {
113 // Create empty buffer of the given size
114 let buffer = instance.device.create_buffer(&wgpu::BufferDescriptor {
115 label: Some(format!("{}-buffer", label).as_str()),
116 size: size as u64,
117 usage,
118 mapped_at_creation: false
119 });
120
121 Buffer {
122 label: label.to_string(),
123 buffer
124 }
125 }
126 }
127 }
128
129 /// Copy data to the buffer from another buffer.
130 /// Note that the buffer must have the COPY_DST usage.
131 ///
132 /// # Arguments
133 ///
134 /// * `instance` - The render instance.
135 /// * `buffer` - The buffer to copy from.
136 pub fn copy_from_buffer(&self, instance: &RenderInstanceData<'_>, buffer: &Buffer) {
137 // Create command encoder
138 let mut command_buffer = CommandBuffer::new(
139 instance,
140 &format!("copy-from-{}-to-{}", buffer.label, self.label)
141 );
142
143 // Copy buffer
144 command_buffer.copy_buffer_to_buffer(buffer, self);
145
146 // Submit commands
147 command_buffer.submit(instance);
148 }
149
150 /// Copy data to the buffer from another buffer offset by a given offset.
151 /// Note that the buffer must have the COPY_DST usage.
152 ///
153 /// # Arguments
154 ///
155 /// * `instance` - The render instance.
156 /// * `buffer` - The buffer to copy from.
157 /// * `source_offset` - The offset in the source buffer to start copying from.
158 /// * `destination_offset` - The offset in the destination buffer to start copying to.
159 /// * `size` - The size of the data to copy.
160 pub fn copy_from_buffer_offset(
161 &self,
162 instance: &RenderInstanceData<'_>,
163 buffer: &Buffer,
164 source_offset: u64,
165 destination_offset: u64,
166 size: u64
167 ) {
168 // Create command encoder
169 let mut command_buffer = CommandBuffer::new(
170 instance,
171 &format!("copy-from-{}-to-{}", buffer.label, self.label)
172 );
173
174 // Copy buffer
175 command_buffer.copy_buffer_to_buffer_offset(
176 buffer,
177 self,
178 source_offset,
179 destination_offset,
180 size
181 );
182
183 // Submit commands
184 command_buffer.submit(instance);
185 }
186
187 /// Copy data to the buffer from a texture.
188 /// Note that the buffer must have the COPY_DST usage.
189 ///
190 /// # Arguments
191 ///
192 /// * `instance` - The render instance.
193 /// * `texture` - The texture to copy from.
194 pub fn copy_from_texture(
195 &mut self,
196 instance: &RenderInstanceData<'_>,
197 texture: &wgpu::Texture
198 ) {
199 let mut command_buffer = CommandBuffer::new(instance, &format!("copy-to-{}", self.label));
200 command_buffer.copy_texture_to_buffer(texture, self, texture.size());
201 command_buffer.submit(instance);
202 }
203
204 /// Copy a single layer from a texture array into this buffer.
205 pub fn copy_from_texture_layered(
206 &mut self,
207 instance: &RenderInstanceData<'_>,
208 texture: &wgpu::Texture,
209 layer: u32
210 ) {
211 let mut command_buffer = CommandBuffer::new(instance, &format!("copy-to-{}", self.label));
212 command_buffer.copy_texture_layer_to_buffer(texture, self, layer);
213 command_buffer.submit(instance);
214 }
215
216 /// Write data to the buffer.
217 /// Note that the buffer must have the COPY_DST usage.
218 ///
219 /// # Arguments
220 ///
221 /// * `instance` - The render instance.
222 /// * `content` - The content to write to the buffer.
223 /// * `offset` - The offset to write the content to.
224 pub fn write(&self, instance: &RenderInstanceData, content: &[u8], offset: usize) {
225 instance
226 .queue
227 .write_buffer(&self.buffer, offset as u64, content);
228 }
229
230 /// Map the buffer.
231 /// The access to the buffer is read-only.
232 /// This will wait for the buffer to be mapped.
233 /// Note that the buffer must have the MAP_READ usage.
234 ///
235 /// # Arguments
236 ///
237 /// * `instance` - The render instance.
238 /// * `callback` - A closure that takes a reference to the buffer.
239 /// The callback takes reference to the buffer.
240 pub fn map_read(&self, instance: &RenderInstanceData, callback: impl FnOnce(BufferView)) {
241 // Map buffer
242 let (sender, receiver) = std::sync::mpsc::channel();
243 let buffer_slice = self.buffer.slice(..);
244 buffer_slice.map_async(wgpu::MapMode::Read, move |r| sender.send(r).unwrap());
245
246 // Wait for the buffer to be mapped
247 if let Err(e) = instance.device.poll(wgpu::PollType::Wait {
248 submission_index: None,
249 timeout: None
250 }) {
251 error!(
252 "Failed to poll device while mapping buffer '{}': {:?}",
253 self.label, e
254 );
255 }
256 receiver.recv().unwrap().unwrap();
257
258 // Call callback
259 callback(buffer_slice.get_mapped_range());
260
261 // Unmap buffer
262 self.buffer.unmap();
263 }
264
265 /// Map the buffer as mutable.
266 /// This allows to write to the buffer.
267 /// This will wait for the buffer to be mapped.
268 /// Note that the buffer must have the MAP_WRITE usage.
269 ///
270 /// # Arguments
271 ///
272 /// * `instance` - The render instance.
273 /// * `callback` - A closure that takes a mutable reference to the buffer.
274 /// The callback takes a mutable reference to the buffer.
275 pub fn map_write(&self, instance: &RenderInstanceData, callback: impl FnOnce(BufferViewMut)) {
276 // Map buffer
277 let (sender, receiver) = std::sync::mpsc::channel();
278 let buffer_slice = self.buffer.slice(..);
279 buffer_slice.map_async(wgpu::MapMode::Write, move |r| sender.send(r).unwrap());
280
281 // Wait for the buffer to be mapped
282 {
283 let _span = trace_span!("wait_for_buffer_mapping").entered();
284 if let Err(e) = instance.device.poll(wgpu::PollType::Wait {
285 submission_index: None,
286 timeout: None
287 }) {
288 error!(
289 "Failed to poll device while mapping buffer '{}': {:?}",
290 self.label, e
291 );
292 }
293 receiver.recv().unwrap().unwrap();
294 }
295
296 // Call callback
297 callback(buffer_slice.get_mapped_range_mut());
298
299 // Unmap buffer
300 self.buffer.unmap();
301 }
302}
303
304/// A pending GPU→CPU readback. Created by [`AsyncReadback::from_texture_layer`].
305/// Call [`AsyncReadback::try_collect`] each frame (after a non-blocking device poll)
306/// until it returns `Ok(data)`.
307pub struct AsyncReadback {
308 buffer: wgpu::Buffer,
309 receiver: mpsc::Receiver<Result<(), wgpu::BufferAsyncError>>
310}
311
312// wgpu::Buffer is Send+Sync so AsyncReadback can cross thread boundaries.
313unsafe impl Send for AsyncReadback {}
314unsafe impl Sync for AsyncReadback {}
315
316impl AsyncReadback {
317 /// Copy one layer of a texture array into a fresh staging buffer and start an
318 /// async mapping. The GPU copy and the mapping are both non-blocking — the caller
319 /// must poll the device (e.g. `PollType::Poll`) and call `try_collect` in a later frame.
320 pub fn from_texture_layer(
321 instance: &RenderInstanceData,
322 texture: &wgpu::Texture,
323 layer: u32
324 ) -> Self {
325 let size = texture.size();
326 let bytes_per_pixel: u32 = match texture.format() {
327 wgpu::TextureFormat::R8Unorm => 1,
328 wgpu::TextureFormat::Rgba8Unorm => 4,
329 fmt => panic!("Unsupported format for async readback: {:?}", fmt)
330 };
331 // wgpu requires bytes_per_row to be a multiple of 256.
332 let bytes_per_row = (size.width * bytes_per_pixel).div_ceil(256) * 256;
333 let buffer_size = (bytes_per_row * size.height) as u64;
334
335 let staging = instance.device.create_buffer(&wgpu::BufferDescriptor {
336 label: Some("async-readback-staging"),
337 size: buffer_size,
338 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
339 mapped_at_creation: false
340 });
341
342 let mut encoder = instance
343 .device
344 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
345 label: Some("async-readback-copy")
346 });
347 encoder.copy_texture_to_buffer(
348 wgpu::TexelCopyTextureInfo {
349 texture,
350 mip_level: 0,
351 origin: wgpu::Origin3d {
352 x: 0,
353 y: 0,
354 z: layer
355 },
356 aspect: wgpu::TextureAspect::All
357 },
358 wgpu::TexelCopyBufferInfo {
359 buffer: &staging,
360 layout: wgpu::TexelCopyBufferLayout {
361 offset: 0,
362 bytes_per_row: Some(bytes_per_row),
363 rows_per_image: None
364 }
365 },
366 wgpu::Extent3d {
367 width: size.width,
368 height: size.height,
369 depth_or_array_layers: 1
370 }
371 );
372 instance.queue.submit(std::iter::once(encoder.finish()));
373
374 let (sender, receiver) = mpsc::channel();
375 staging.slice(..).map_async(wgpu::MapMode::Read, move |r| {
376 let _ = sender.send(r);
377 });
378
379 AsyncReadback {
380 buffer: staging,
381 receiver
382 }
383 }
384
385 /// Try to collect the result. Returns `Ok(data)` when the GPU has finished, or
386 /// `Err(self)` when the mapping is still in progress (call again next frame).
387 /// Call `instance.device.poll(PollType::Poll)` before this to process GPU callbacks.
388 pub fn try_collect(self) -> Result<Vec<u8>, Self> {
389 match self.receiver.try_recv() {
390 Ok(Ok(())) => {
391 let data = self.buffer.slice(..).get_mapped_range().to_vec();
392 self.buffer.unmap();
393 Ok(data)
394 }
395 Ok(Err(e)) => {
396 error!("Async readback mapping error: {:?}", e);
397 Ok(Vec::new())
398 }
399 Err(mpsc::TryRecvError::Empty) => Err(self),
400 Err(mpsc::TryRecvError::Disconnected) => Ok(Vec::new())
401 }
402 }
403}