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

I'm currently working on wgpu in order to reap the efficiency benefits over OpenGl. A common pattern in my 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)?

1 Like

in wgpu 0.6, the API has changed so that updating a buffer is done with Queue::write_buffer() instead of going through the Encoder interface.

If you're not ready to update wgpu, you should split the render function into two parts: one function to prepare the render pass by updating state, and another to do the actual drawing. This will break the mutable aliasing problem that your current render signature has.

This idea was lifted from the wgpu wiki; the article provides additional rationale for the design, beyond "satisfying the borrow checker".

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.