I'm afraid this may be a noobie question given how common this use case must be, but for the same reason I figured it would be an easy question for some wgpu gurus.
What's the best practice for updating per-draw uniform data? In particular, I really just want to pass a 4x4 transformation matrix for each draw call.
Some possibilities:
Buffer::map_read/map_write methods? I was kind of hoping to avoid them if I could for now, because of the situation with event loops described in the docs, but if someone tells me that that's the recommended way to do this, I might try it anyway.Are any of the above possibilities the recommended way to update per-draw data with wgpu today?
Are there other better ways to update per-draw uniform data?
That's a great question! It comes up regularly, and there is a reason for it - WebGPU doesn't offer a solution that would be obvious to use. The closest thing you can get to an obvious solution is push constants. I filed #734 for us to try implementing them as a native extension, but this wouldn't be portable to the web.
Here is how you'd do it in a portable way. First important fact: you can't do any transfers in the middle of a render pass. Transfers are done on command encoder outside of a render pass. Therefore, each separate object/entity that you draw in a render pass needs to use a separate area in a GPU buffer somewhere (if it needs its own matrix). That means, all your data has to be ready by the time a render pass starts executing (but not necessarily recording!). Here is some pseudo-code:
fn init() {
let uniform_buffer = device.create_buffer(size = <big enough for a frame>).
// this will allow us to specify the offsets at binding time
let uniform_bind_group = device.create_bind_group(uniform_buffer, dynamic_offset = true).
}
fn render(queue: &wgpu::Queue) {
let encoder = device.create_command_encoder();
let mut offset = 0;
let mut pass = encoder.begin_render_pass();
for entity in world.entities {
// this is an operation on the queue, it will be scheduled before anything in this render pass
queue.write_buffer(&self.uniform_buffer, offset, entity.matrix, size_of::<Matrix>());
// here comes handy the dynamic offset
pass.set_bind_group(1, &self.uniform_bind_group, &[offset]);
// we don't want to overwrite any existing data this frame
offset += size_of::<Matrix>();
}
// our draw commands are scheduled after the updates
queue.submit(iter::once(encoder.finish()));
}
Also worth noting that we landed an experimental staging belt in wgpu-rs, as an alternative to write_buffer.
Closing this. Please free to re-open and/or continue discussion!
Most helpful comment
That's a great question! It comes up regularly, and there is a reason for it - WebGPU doesn't offer a solution that would be obvious to use. The closest thing you can get to an obvious solution is push constants. I filed #734 for us to try implementing them as a native extension, but this wouldn't be portable to the web.
Here is how you'd do it in a portable way. First important fact: you can't do any transfers in the middle of a render pass. Transfers are done on command encoder outside of a render pass. Therefore, each separate object/entity that you draw in a render pass needs to use a separate area in a GPU buffer somewhere (if it needs its own matrix). That means, all your data has to be ready by the time a render pass starts executing (but not necessarily recording!). Here is some pseudo-code: