How can I change uniforms during a render pass in wgpu?
Asked Answered
B

1

6

I'm currently working in wgpu in order to reap the efficiency benefits over OpenGl. A common pattern in my previous graphics code is to draw many meshes with separate buffers, separate uniform data, but the same shaders. It looks something like this (in C++):

glUseProgram(m_myProgram();
for(Mesh &mesh : m_meshes){
    glBindVertexArray(mesh.vaoId());
    glUniformMatrix4fv(m_transformationMatrixId, camera.getMatrix(mesh.getLocation());
    glDrawElements(GL_TRIANGLES, mesh.indexCount(), GL_UINT, nullptr);    
}

(there may be some subtle and irrelevant error — I haven't used OpenGl in awhile)

Yet, when I try to replicate the same pattern in Rust with wgpu, I run into issues.

            let view_projection = self.camera.build_view_projection_matrix();

            let mut render_pass = encoder.begin_render_pass(&RenderPassDescriptor {
                color_attachments: &[RenderPassColorAttachmentDescriptor {
                    attachment: &frame.view,
                    resolve_target: None,
                    load_op: LoadOp::Clear,
                    store_op: StoreOp::Store,
                    clear_color: Color {
                        r: 0.1,
                        g: 0.2,
                        b: 0.3,
                        a: 1.0,
                    },
                }],
                depth_stencil_attachment: None,
            });

            render_pass.set_pipeline(&self.render_pipeline);
            render_pass.set_bind_group(0, &self.uniform_buffer_bind_group, &[]);
            for mesh in &self.mesh{
                mesh.render(&mut encoder, &mut render_pass, &view_projection, &self.uniform_buffer, &self.device); //ERROR! Can't borrow `encoder` mutably becuase `render_pass` is itself a mutable borrow, which already exists
            }

The render function looks like this:

        //build matrix
        let model_matrix = Matrix4::from_translation(self.location);
        let final_matrix = view_projection*model_matrix;

        //send matrix to gpu
        let uniforms = Uniforms::new_from_matrix(final_matrix);
        let buffer = device.create_buffer_with_data(
            bytemuck::cast_slice(&[uniforms]),
            BufferUsage::COPY_SRC
        );
        command_encoder.copy_buffer_to_buffer(&buffer, 0, &uniform_buffer, 0, std::mem::size_of_val(&uniforms) as BufferAddress); //A mutable borrow to the command encoder is needed here in order to update uniforms

        //setup buffers
        render_pass.set_vertex_buffer(0, &self.vertex_buffer, 0, 0);
        render_pass.set_index_buffer(&self.index_buffer, 0, 0);

        //render
        render_pass.draw(0..self.index_count, 0..1);

Clearly, the mutable borrow is needed to update uniforms. I strongly prefer, for efficiency, to not start a new render pass for every mesh. So, I ask, how can I update uniform data in wgpu during a render pass, to render many meshes with the same settings (aside from the uniforms)? Is there some other, better, way to accomplish what I want (render many meshes with their own buffers and uniforms, but shared shader)?

I read through the docs.rs entry for wgpu very thoroughly, but I can't find anything relevant to what I want. I also tried, and failed, to find a serious FOSS project that uses wgpu so that I could see how it handles this problem.

Note that I'm using wgpu 0.5.0 instead of 0.6.0 because that's what the best introductory documentation for wgpu that I can find uses.

Buckhound answered 30/8, 2020 at 23:36 Comment(0)
A
3

In a render_pass, all draw calls are executed at the same time, so inserting buffer updates in the render_pass will not give the expected result.

If you want to update the buffer before a draw call, you need to create a new render_pass.

Agamogenesis answered 26/12, 2022 at 2:44 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.