Search code examples
rustwebgpuwgpu-rs

Creating writing and reading from buffer wgpu


I have just recently started learning about how to compute on a GPU and I have decided to start with WGPU as I'm familiar with rust and it can be run on pretty much every GPU. As far as my current understanding goes first I have to create a buffer that is accessible to my CPU, which I have done with the following code.

    let array_buffer = device.create_buffer(&wgpu::BufferDescriptor{
        label: Some("gpu_test"),
        size: (arr.len() * std::mem::size_of::<[i32; 5]>()) as u64,
        usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
        mapped_at_creation: false,
    });

After that I have wrote some random data to this buffer with the following line.

queue.write_buffer(&array_buffer, 0, &[1,2,3,4]);

As of right now I have no errors but the problem appears when I now want to read the data in this buffer, there is no example of how to read the data off a buffer in wgpu docs and I didn't see one in webGPU docs too.

In addition how do I know if the buffer is accessible on CPU or GPU webGPU docs talk about it but they don't have an explicate example of how to define a buffer for each one.


Solution

  • First, in order to be able to read a buffer, it must have BufferUsages::MAP_READ. You've already done that. If you wanted to read a buffer that can't have that usage, you'd need to copy data to a temporary buffer first.

    Given that prerequisite, the steps are:

    1. Call BufferView::map_async()
    2. Call Device::poll()
    3. Confirm that the map_async() callback has been called with a successful Result
    4. Call BufferView::get_mapped_range()

    Here's the code I've used for that, which maps the buffer in the variable temp_buffer:

    let (sender, receiver) = futures_channel::oneshot::channel();
    temp_buffer
        .slice(..)
        .map_async(wgpu::MapMode::Read, |result| {
            let _ = sender.send(result);
        });
    device.poll(wgpu::Maintain::Wait); // TODO: poll in the background instead of blocking
    receiver
        .await
        .expect("communication failed")
        .expect("buffer reading failed");
    let slice: &[u8] = &temp_buffer.slice(..).get_mapped_range();
    

    Note that as per the TODO, this particular code is a work in progress halfway between two sensible states:

    • If you intend only to block on the buffer being ready to read, then you don't need a channel, just a OnceCell because the message will always have arrived when poll(Maintain::Wait) returns.
    • If you want to be truly async, then something needs to be calling either poll(Maintain::Poll) or Queue::submit() repeatedly in the background to trigger checking for completion and thus the callback.