[egui-wgpu] Do vertex & index buffer in a single copy each (#2820)
* [egui-wgpu] Do vertex & index buffer in a single copy each Also, copy uniform buffer only if necessary. Previously, we did hundreds of small copies via queue.write_buffer which would create a new buffer for each of these copies. Now, there are only two gpu sided copy operations and the memory goes directly to the staging buffer. In a quick debug test on Rerun this decreased time for the `update_buffer` method from about 0.87ms to 0.37ms! * fix comparing padding on UniformBuffer in wgpu renderer --------- Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
This commit is contained in:
parent
5ccc71415e
commit
b1f837ca2f
|
|
@ -4,6 +4,7 @@ All notable changes to the `egui-wgpu` integration will be noted in this file.
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
* Add `read_screan_rgba` to the egui-wgpu `Painter`, to allow for capturing the current frame when using wgpu. Used in conjuction with `Frame::request_screenshot`. ([#2676](https://github.com/emilk/egui/pull/2676))
|
* Add `read_screan_rgba` to the egui-wgpu `Painter`, to allow for capturing the current frame when using wgpu. Used in conjuction with `Frame::request_screenshot`. ([#2676](https://github.com/emilk/egui/pull/2676))
|
||||||
|
* Improve performance of `update_buffers` ([#2820](https://github.com/emilk/egui/pull/2820))
|
||||||
|
|
||||||
|
|
||||||
## 0.21.0 - 2023-02-08
|
## 0.21.0 - 2023-02-08
|
||||||
|
|
@ -13,6 +14,7 @@ All notable changes to the `egui-wgpu` integration will be noted in this file.
|
||||||
* `egui-wgpu` now only depends on `epaint` instead of the entire `egui` ([#2438](https://github.com/emilk/egui/pull/2438)).
|
* `egui-wgpu` now only depends on `epaint` instead of the entire `egui` ([#2438](https://github.com/emilk/egui/pull/2438)).
|
||||||
* `winit::Painter` now supports transparent backbuffer ([#2684](https://github.com/emilk/egui/pull/2684)).
|
* `winit::Painter` now supports transparent backbuffer ([#2684](https://github.com/emilk/egui/pull/2684)).
|
||||||
|
|
||||||
|
|
||||||
## 0.20.0 - 2022-12-08 - web support
|
## 0.20.0 - 2022-12-08 - web support
|
||||||
* Renamed `RenderPass` to `Renderer`.
|
* Renamed `RenderPass` to `Renderer`.
|
||||||
* Renamed `RenderPass::execute` to `RenderPass::render`.
|
* Renamed `RenderPass::execute` to `RenderPass::render`.
|
||||||
|
|
|
||||||
|
|
@ -133,9 +133,15 @@ struct UniformBuffer {
|
||||||
_padding: [u32; 2],
|
_padding: [u32; 2],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl PartialEq for UniformBuffer {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.screen_size_in_points == other.screen_size_in_points
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct SlicedBuffer {
|
struct SlicedBuffer {
|
||||||
buffer: wgpu::Buffer,
|
buffer: wgpu::Buffer,
|
||||||
slices: Vec<Range<wgpu::BufferAddress>>,
|
slices: Vec<Range<usize>>,
|
||||||
capacity: wgpu::BufferAddress,
|
capacity: wgpu::BufferAddress,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -147,6 +153,7 @@ pub struct Renderer {
|
||||||
vertex_buffer: SlicedBuffer,
|
vertex_buffer: SlicedBuffer,
|
||||||
|
|
||||||
uniform_buffer: wgpu::Buffer,
|
uniform_buffer: wgpu::Buffer,
|
||||||
|
previous_uniform_buffer_content: UniformBuffer,
|
||||||
uniform_bind_group: wgpu::BindGroup,
|
uniform_bind_group: wgpu::BindGroup,
|
||||||
texture_bind_group_layout: wgpu::BindGroupLayout,
|
texture_bind_group_layout: wgpu::BindGroupLayout,
|
||||||
|
|
||||||
|
|
@ -332,6 +339,11 @@ impl Renderer {
|
||||||
capacity: INDEX_BUFFER_START_CAPACITY,
|
capacity: INDEX_BUFFER_START_CAPACITY,
|
||||||
},
|
},
|
||||||
uniform_buffer,
|
uniform_buffer,
|
||||||
|
// Buffers on wgpu are zero initialized, so this is indeed its current state!
|
||||||
|
previous_uniform_buffer_content: UniformBuffer {
|
||||||
|
screen_size_in_points: [0.0, 0.0],
|
||||||
|
_padding: [0, 0],
|
||||||
|
},
|
||||||
uniform_bind_group,
|
uniform_bind_group,
|
||||||
texture_bind_group_layout,
|
texture_bind_group_layout,
|
||||||
textures: HashMap::new(),
|
textures: HashMap::new(),
|
||||||
|
|
@ -403,12 +415,16 @@ impl Renderer {
|
||||||
if let Some((_texture, bind_group)) = self.textures.get(&mesh.texture_id) {
|
if let Some((_texture, bind_group)) = self.textures.get(&mesh.texture_id) {
|
||||||
render_pass.set_bind_group(1, bind_group, &[]);
|
render_pass.set_bind_group(1, bind_group, &[]);
|
||||||
render_pass.set_index_buffer(
|
render_pass.set_index_buffer(
|
||||||
self.index_buffer.buffer.slice(index_buffer_slice.clone()),
|
self.index_buffer.buffer.slice(
|
||||||
|
index_buffer_slice.start as u64..index_buffer_slice.end as u64,
|
||||||
|
),
|
||||||
wgpu::IndexFormat::Uint32,
|
wgpu::IndexFormat::Uint32,
|
||||||
);
|
);
|
||||||
render_pass.set_vertex_buffer(
|
render_pass.set_vertex_buffer(
|
||||||
0,
|
0,
|
||||||
self.vertex_buffer.buffer.slice(vertex_buffer_slice.clone()),
|
self.vertex_buffer.buffer.slice(
|
||||||
|
vertex_buffer_slice.start as u64..vertex_buffer_slice.end as u64,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
render_pass.draw_indexed(0..mesh.indices.len() as u32, 0, 0..1);
|
render_pass.draw_indexed(0..mesh.indices.len() as u32, 0, 0..1);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -748,17 +764,18 @@ impl Renderer {
|
||||||
|
|
||||||
let screen_size_in_points = screen_descriptor.screen_size_in_points();
|
let screen_size_in_points = screen_descriptor.screen_size_in_points();
|
||||||
|
|
||||||
{
|
let uniform_buffer_content = UniformBuffer {
|
||||||
crate::profile_scope!("uniforms");
|
screen_size_in_points,
|
||||||
// Update uniform buffer
|
_padding: Default::default(),
|
||||||
|
};
|
||||||
|
if uniform_buffer_content != self.previous_uniform_buffer_content {
|
||||||
|
crate::profile_scope!("update uniforms");
|
||||||
queue.write_buffer(
|
queue.write_buffer(
|
||||||
&self.uniform_buffer,
|
&self.uniform_buffer,
|
||||||
0,
|
0,
|
||||||
bytemuck::cast_slice(&[UniformBuffer {
|
bytemuck::cast_slice(&[uniform_buffer_content]),
|
||||||
screen_size_in_points,
|
|
||||||
_padding: Default::default(),
|
|
||||||
}]),
|
|
||||||
);
|
);
|
||||||
|
self.previous_uniform_buffer_content = uniform_buffer_content;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine how many vertices & indices need to be rendered.
|
// Determine how many vertices & indices need to be rendered.
|
||||||
|
|
@ -774,73 +791,104 @@ impl Renderer {
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
{
|
if index_count > 0 {
|
||||||
// Resize index buffer if needed:
|
crate::profile_scope!("indices");
|
||||||
|
|
||||||
self.index_buffer.slices.clear();
|
self.index_buffer.slices.clear();
|
||||||
let required_size = (std::mem::size_of::<u32>() * index_count) as u64;
|
let required_index_buffer_size = (std::mem::size_of::<u32>() * index_count) as u64;
|
||||||
if self.index_buffer.capacity < required_size {
|
if self.index_buffer.capacity < required_index_buffer_size {
|
||||||
|
// Resize index buffer if needed.
|
||||||
self.index_buffer.capacity =
|
self.index_buffer.capacity =
|
||||||
(self.index_buffer.capacity * 2).at_least(required_size);
|
(self.index_buffer.capacity * 2).at_least(required_index_buffer_size);
|
||||||
self.index_buffer.buffer = create_index_buffer(device, self.index_buffer.capacity);
|
self.index_buffer.buffer = create_index_buffer(device, self.index_buffer.capacity);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
{
|
let mut index_buffer_staging = queue
|
||||||
// Resize vertex buffer if needed:
|
.write_buffer_with(
|
||||||
|
&self.index_buffer.buffer,
|
||||||
|
0,
|
||||||
|
NonZeroU64::new(required_index_buffer_size).unwrap(),
|
||||||
|
)
|
||||||
|
.expect("Failed to create staging buffer for index data");
|
||||||
|
let mut index_offset = 0;
|
||||||
|
for epaint::ClippedPrimitive { primitive, .. } in paint_jobs.iter() {
|
||||||
|
match primitive {
|
||||||
|
Primitive::Mesh(mesh) => {
|
||||||
|
let size = mesh.indices.len() * std::mem::size_of::<u32>();
|
||||||
|
let slice = index_offset..(size + index_offset);
|
||||||
|
index_buffer_staging[slice.clone()]
|
||||||
|
.copy_from_slice(bytemuck::cast_slice(&mesh.indices));
|
||||||
|
self.index_buffer.slices.push(slice);
|
||||||
|
index_offset += size;
|
||||||
|
}
|
||||||
|
Primitive::Callback(_) => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if vertex_count > 0 {
|
||||||
|
crate::profile_scope!("vertices");
|
||||||
|
|
||||||
self.vertex_buffer.slices.clear();
|
self.vertex_buffer.slices.clear();
|
||||||
let required_size = (std::mem::size_of::<Vertex>() * vertex_count) as u64;
|
let required_vertex_buffer_size = (std::mem::size_of::<Vertex>() * vertex_count) as u64;
|
||||||
if self.vertex_buffer.capacity < required_size {
|
if self.vertex_buffer.capacity < required_vertex_buffer_size {
|
||||||
|
// Resize vertex buffer if needed.
|
||||||
self.vertex_buffer.capacity =
|
self.vertex_buffer.capacity =
|
||||||
(self.vertex_buffer.capacity * 2).at_least(required_size);
|
(self.vertex_buffer.capacity * 2).at_least(required_vertex_buffer_size);
|
||||||
self.vertex_buffer.buffer =
|
self.vertex_buffer.buffer =
|
||||||
create_vertex_buffer(device, self.vertex_buffer.capacity);
|
create_vertex_buffer(device, self.vertex_buffer.capacity);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Upload index & vertex data and call user callbacks
|
let mut vertex_buffer_staging = queue
|
||||||
let mut user_cmd_bufs = Vec::new(); // collect user command buffers
|
.write_buffer_with(
|
||||||
|
&self.vertex_buffer.buffer,
|
||||||
crate::profile_scope!("primitives");
|
0,
|
||||||
for epaint::ClippedPrimitive { primitive, .. } in paint_jobs.iter() {
|
NonZeroU64::new(required_vertex_buffer_size).unwrap(),
|
||||||
match primitive {
|
)
|
||||||
Primitive::Mesh(mesh) => {
|
.expect("Failed to create staging buffer for vertex data");
|
||||||
{
|
let mut vertex_offset = 0;
|
||||||
let index_offset = self.index_buffer.slices.last().unwrap_or(&(0..0)).end;
|
for epaint::ClippedPrimitive { primitive, .. } in paint_jobs.iter() {
|
||||||
let data = bytemuck::cast_slice(&mesh.indices);
|
match primitive {
|
||||||
queue.write_buffer(&self.index_buffer.buffer, index_offset, data);
|
Primitive::Mesh(mesh) => {
|
||||||
self.index_buffer
|
let size = mesh.vertices.len() * std::mem::size_of::<Vertex>();
|
||||||
.slices
|
let slice = vertex_offset..(size + vertex_offset);
|
||||||
.push(index_offset..(data.len() as wgpu::BufferAddress + index_offset));
|
vertex_buffer_staging[slice.clone()]
|
||||||
|
.copy_from_slice(bytemuck::cast_slice(&mesh.vertices));
|
||||||
|
self.vertex_buffer.slices.push(slice);
|
||||||
|
vertex_offset += size;
|
||||||
}
|
}
|
||||||
{
|
Primitive::Callback(_) => {}
|
||||||
let vertex_offset = self.vertex_buffer.slices.last().unwrap_or(&(0..0)).end;
|
|
||||||
let data = bytemuck::cast_slice(&mesh.vertices);
|
|
||||||
queue.write_buffer(&self.vertex_buffer.buffer, vertex_offset, data);
|
|
||||||
self.vertex_buffer.slices.push(
|
|
||||||
vertex_offset..(data.len() as wgpu::BufferAddress + vertex_offset),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Primitive::Callback(callback) => {
|
|
||||||
let cbfn = if let Some(c) = callback.callback.downcast_ref::<CallbackFn>() {
|
|
||||||
c
|
|
||||||
} else {
|
|
||||||
tracing::warn!("Unknown paint callback: expected `egui_wgpu::CallbackFn`");
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
crate::profile_scope!("callback");
|
|
||||||
user_cmd_bufs.extend((cbfn.prepare)(
|
|
||||||
device,
|
|
||||||
queue,
|
|
||||||
encoder,
|
|
||||||
&mut self.paint_callback_resources,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
user_cmd_bufs
|
{
|
||||||
|
crate::profile_scope!("user command buffers");
|
||||||
|
let mut user_cmd_bufs = Vec::new(); // collect user command buffers
|
||||||
|
for epaint::ClippedPrimitive { primitive, .. } in paint_jobs.iter() {
|
||||||
|
match primitive {
|
||||||
|
Primitive::Mesh(_) => {}
|
||||||
|
Primitive::Callback(callback) => {
|
||||||
|
let cbfn = if let Some(c) = callback.callback.downcast_ref::<CallbackFn>() {
|
||||||
|
c
|
||||||
|
} else {
|
||||||
|
tracing::warn!(
|
||||||
|
"Unknown paint callback: expected `egui_wgpu::CallbackFn`"
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
crate::profile_scope!("callback");
|
||||||
|
user_cmd_bufs.extend((cbfn.prepare)(
|
||||||
|
device,
|
||||||
|
queue,
|
||||||
|
encoder,
|
||||||
|
&mut self.paint_callback_resources,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
user_cmd_bufs
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue