Search code examples
rustwebgpuwgpu-rs

write_buffer doesn't write to buffer instead outputting zeros WGPU


Hello I have recently began learning compute shaders with wgpu and I'm having little difficulties figuring out how to correctly write data into a buffer.

I have created a small buffer mapped to the cpu with this code.

    let array_buffer = device.create_buffer(&wgpu::BufferDescriptor{
        label: Some("gpu_test"),
        size: 4,
        usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
        mapped_at_creation: true,
    });

I assume this gives me a buffer accessible on a CPU which I now write into using this line of code.

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

After which I read off the data off the buffer

    let slice = array_buffer.slice(..).get_mapped_range();
    println!("{:?}", slice.get(0..4));

My output is now Some([0, 0, 0, 0]) which after reading the docs I assume is normal behavier as docs say "Write is not immediately submitted, and instead enqueued internally to happen at the start of the next submit() call." The problem is that it doesn't really say how to submit I have attempted to run.

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

And a couple different variations of queue.submit(); but the output is still as it was Some([0, 0, 0, 0]) so if anyone could point at what am I not doing correctly I would be very thankful.

Full code for replication:

use wgpu;
use pollster;

fn main() {
    let instance = wgpu::Instance::new(wgpu::InstanceDescriptor{
        backends: wgpu::Backends::all(),
        dx12_shader_compiler: Default::default(),
    });

    let adapter = pollster::block_on(instance.request_adapter(&Default::default())).unwrap();
    // connect to gpu
    let (device, queue) = pollster::block_on(adapter.request_device(&Default::default(), None)).unwrap();

    let array_buffer = device.create_buffer(&wgpu::BufferDescriptor{
        label: Some("gpu_test"),
        size: 4,
        usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
        mapped_at_creation: true,
    });

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

    let slice = array_buffer.slice(..).get_mapped_range();
    println!("{:?}", slice.get(0..4));

    drop(slice);
    array_buffer.unmap();
}

Solution

  • You must map the buffer after writing it. You can't just have a buffer perpetually mapped; the CPU and GPU must take turns accessing the buffer.

    Right now, you're making use of the mapping created by mapped_at_creation: true, but that mapping only allows you to write data to it, or read the data you wrote that way, so in this case it is always zeroes. In order to read data that was written on the GPU (such as by executing a write_buffer() command in the queue), you must use .map_async() after the write.

    The necessary changes are:

    1. Change mapped_at_creation: true to false, since you're not actually using it. (You could use that mapping instead of the write_buffer() call, if you wanted, but even simpler than that is DeviceExt::create_buffer_init().)
    2. After the submit(), add a call to map_async() followed by poll(Maintain::Wait). Then the buffer will be mapped for reading and you can read it.
    fn main() {
        let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
            backends: wgpu::Backends::all(),
            dx12_shader_compiler: Default::default(),
        });
    
        let adapter = pollster::block_on(instance.request_adapter(&Default::default())).unwrap();
        // connect to gpu
        let (device, queue) =
            pollster::block_on(adapter.request_device(&Default::default(), None)).unwrap();
    
        let array_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("gpu_test"),
            size: 4,
            usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
            mapped_at_creation: false,
        });
    
        queue.write_buffer(&array_buffer, 0, &[1, 2, 3, 4]);
        queue.submit([]);
    
        array_buffer
            .slice(..)
            .map_async(wgpu::MapMode::Read, |result| {
                // In a real program this should be a message channel of some sort.
                // Unwrapping here will, if it fails, panic in whichever thread next
                // calls poll(), which isn't good error handling.
                result.unwrap();
            });
        device.poll(wgpu::Maintain::Wait);
    
        let slice = array_buffer.slice(..).get_mapped_range();
        println!("{:?}", slice.get(0..4));
    
        drop(slice);
        array_buffer.unmap();
    }
    

    Output:

    Some([1, 2, 3, 4])