using a shared vertex & index buffer in wgpu renderer (#2148)

* using a shared vertex & index buffer in wgpu renderer
capacity each doubles when exceeded.
This change means a lot less allocation during egui's lifetime.

* changelog update

* minor code cleanup and changelog fix

* fix linter issue
This commit is contained in:
Andreas Reich 2022-10-14 10:53:19 +02:00 committed by GitHub
parent 02d1e7492a
commit 944159d514
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 105 additions and 104 deletions

View file

@ -9,7 +9,7 @@ All notable changes to the `egui-wgpu` integration will be noted in this file.
* Reexported `Renderer`.
* `Renderer` no longer handles pass creation and depth buffer creation ([#2136](https://github.com/emilk/egui/pull/2136))
* `PrepareCallback` now passes `wgpu::CommandEncoder` ([#2136](https://github.com/emilk/egui/pull/2136))
* Only a single vertex & index buffer is now created and resized when necessary (previously, vertex/index buffers were allocated for every mesh) ([#2148](https://github.com/emilk/egui/pull/2148)).
## 0.19.0 - 2022-08-20
* Enables deferred render + surface state initialization for Android ([#1634](https://github.com/emilk/egui/pull/1634)).

View file

@ -1,8 +1,11 @@
#![allow(unsafe_code)]
use std::num::NonZeroU64;
use std::ops::Range;
use std::{borrow::Cow, collections::HashMap, num::NonZeroU32};
use egui::epaint::Vertex;
use egui::NumExt;
use egui::{epaint::Primitive, PaintCallbackInfo};
use type_map::concurrent::TypeMap;
use wgpu;
@ -82,14 +85,6 @@ impl CallbackFn {
}
}
/// Enum for selecting the right buffer type.
#[derive(Debug)]
enum BufferType {
Uniform,
Index,
Vertex,
}
/// Information about the screen used for rendering.
pub struct ScreenDescriptor {
/// Size of the window in physical pixels.
@ -119,20 +114,20 @@ struct UniformBuffer {
_padding: [u32; 2],
}
/// Wraps the buffers and includes additional information.
#[derive(Debug)]
struct SizedBuffer {
struct SlicedBuffer {
buffer: wgpu::Buffer,
/// number of bytes
size: usize,
slices: Vec<Range<wgpu::BufferAddress>>,
capacity: wgpu::BufferAddress,
}
/// Renderer for a egui based GUI.
pub struct Renderer {
pipeline: wgpu::RenderPipeline,
index_buffers: Vec<SizedBuffer>,
vertex_buffers: Vec<SizedBuffer>,
uniform_buffer: SizedBuffer,
index_buffer: SlicedBuffer,
vertex_buffer: SlicedBuffer,
uniform_buffer: wgpu::Buffer,
uniform_bind_group: wgpu::BindGroup,
texture_bind_group_layout: wgpu::BindGroupLayout,
/// Map of egui texture IDs to textures and their associated bindgroups (texture view +
@ -170,10 +165,6 @@ impl Renderer {
}]),
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
});
let uniform_buffer = SizedBuffer {
buffer: uniform_buffer,
size: std::mem::size_of::<UniformBuffer>(),
};
let uniform_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
@ -196,7 +187,7 @@ impl Renderer {
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
buffer: &uniform_buffer.buffer,
buffer: &uniform_buffer,
offset: 0,
size: None,
}),
@ -299,10 +290,23 @@ impl Renderer {
multiview: None,
});
const VERTEX_BUFFER_START_CAPACITY: wgpu::BufferAddress =
(std::mem::size_of::<Vertex>() * 1024) as _;
const INDEX_BUFFER_START_CAPACITY: wgpu::BufferAddress =
(std::mem::size_of::<u32>() * 1024 * 3) as _;
Self {
pipeline,
vertex_buffers: Vec::with_capacity(64),
index_buffers: Vec::with_capacity(64),
vertex_buffer: SlicedBuffer {
buffer: create_vertex_buffer(device, VERTEX_BUFFER_START_CAPACITY),
slices: Vec::with_capacity(64),
capacity: VERTEX_BUFFER_START_CAPACITY,
},
index_buffer: SlicedBuffer {
buffer: create_index_buffer(device, INDEX_BUFFER_START_CAPACITY),
slices: Vec::with_capacity(64),
capacity: INDEX_BUFFER_START_CAPACITY,
},
uniform_buffer,
uniform_bind_group,
texture_bind_group_layout,
@ -326,8 +330,8 @@ impl Renderer {
// run.
let mut needs_reset = true;
let mut index_buffers = self.index_buffers.iter();
let mut vertex_buffers = self.vertex_buffers.iter();
let mut index_buffer_slices = self.index_buffer.slices.iter();
let mut vertex_buffer_slices = self.vertex_buffer.slices.iter();
for egui::ClippedPrimitive {
clip_rect,
@ -355,8 +359,8 @@ impl Renderer {
// Skip rendering zero-sized clip areas.
if let Primitive::Mesh(_) = primitive {
// If this is a mesh, we need to advance the index and vertex buffer iterators:
index_buffers.next().unwrap();
vertex_buffers.next().unwrap();
index_buffer_slices.next().unwrap();
vertex_buffer_slices.next().unwrap();
}
continue;
}
@ -366,16 +370,19 @@ impl Renderer {
match primitive {
Primitive::Mesh(mesh) => {
let index_buffer = index_buffers.next().unwrap();
let vertex_buffer = vertex_buffers.next().unwrap();
let index_buffer_slice = index_buffer_slices.next().unwrap();
let vertex_buffer_slice = vertex_buffer_slices.next().unwrap();
if let Some((_texture, bind_group)) = self.textures.get(&mesh.texture_id) {
render_pass.set_bind_group(1, bind_group, &[]);
render_pass.set_index_buffer(
index_buffer.buffer.slice(..),
self.index_buffer.buffer.slice(index_buffer_slice.clone()),
wgpu::IndexFormat::Uint32,
);
render_pass.set_vertex_buffer(0, vertex_buffer.buffer.slice(..));
render_pass.set_vertex_buffer(
0,
self.vertex_buffer.buffer.slice(vertex_buffer_slice.clone()),
);
render_pass.draw_indexed(0..mesh.indices.len() as u32, 0, 0..1);
} else {
tracing::warn!("Missing texture: {:?}", mesh.texture_id);
@ -709,10 +716,9 @@ impl Renderer {
) {
let screen_size_in_points = screen_descriptor.screen_size_in_points();
self.update_buffer(
device,
queue,
&BufferType::Uniform,
// Update uniform buffer
queue.write_buffer(
&self.uniform_buffer,
0,
bytemuck::cast_slice(&[UniformBuffer {
screen_size_in_points,
@ -720,42 +726,58 @@ impl Renderer {
}]),
);
let mut mesh_idx = 0;
// Determine how many vertices & indices need to be rendered.
let (vertex_count, index_count) =
paint_jobs.iter().fold((0, 0), |acc, clipped_primitive| {
match &clipped_primitive.primitive {
Primitive::Mesh(mesh) => {
(acc.0 + mesh.vertices.len(), acc.1 + mesh.indices.len())
}
Primitive::Callback(_) => acc,
}
});
// Resize index buffer if needed.
{
self.index_buffer.slices.clear();
let required_size = (std::mem::size_of::<u32>() * index_count) as u64;
if self.index_buffer.capacity < required_size {
self.index_buffer.capacity =
(self.index_buffer.capacity * 2).at_least(required_size);
self.index_buffer.buffer = create_index_buffer(device, self.index_buffer.capacity);
}
}
// Resize vertex buffer if needed.
{
self.vertex_buffer.slices.clear();
let required_size = (std::mem::size_of::<Vertex>() * vertex_count) as u64;
if self.vertex_buffer.capacity < required_size {
self.vertex_buffer.capacity =
(self.vertex_buffer.capacity * 2).at_least(required_size);
self.vertex_buffer.buffer =
create_vertex_buffer(device, self.vertex_buffer.capacity);
}
}
// Upload index & vertex data and call user callbacks
for egui::ClippedPrimitive { primitive, .. } in paint_jobs.iter() {
match primitive {
Primitive::Mesh(mesh) => {
let data: &[u8] = bytemuck::cast_slice(&mesh.indices);
if mesh_idx < self.index_buffers.len() {
self.update_buffer(device, queue, &BufferType::Index, mesh_idx, data);
} else {
let buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("egui_index_buffer"),
contents: data,
usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,
});
self.index_buffers.push(SizedBuffer {
buffer,
size: data.len(),
});
{
let index_offset = self.index_buffer.slices.last().unwrap_or(&(0..0)).end;
let data = bytemuck::cast_slice(&mesh.indices);
queue.write_buffer(&self.index_buffer.buffer, index_offset, data);
self.index_buffer
.slices
.push(index_offset..(data.len() as wgpu::BufferAddress + index_offset));
}
let data: &[u8] = bytemuck::cast_slice(&mesh.vertices);
if mesh_idx < self.vertex_buffers.len() {
self.update_buffer(device, queue, &BufferType::Vertex, mesh_idx, data);
} else {
let buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("egui_vertex_buffer"),
contents: data,
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
});
self.vertex_buffers.push(SizedBuffer {
buffer,
size: data.len(),
});
{
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),
);
}
mesh_idx += 1;
}
Primitive::Callback(callback) => {
let cbfn = if let Some(c) = callback.callback.downcast_ref::<CallbackFn>() {
@ -770,45 +792,24 @@ impl Renderer {
}
}
}
}
/// Updates the buffers used by egui. Will properly re-size the buffers if needed.
fn update_buffer(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
buffer_type: &BufferType,
index: usize,
data: &[u8],
) {
let (buffer, storage, label) = match buffer_type {
BufferType::Index => (
&mut self.index_buffers[index],
wgpu::BufferUsages::INDEX,
"egui_index_buffer",
),
BufferType::Vertex => (
&mut self.vertex_buffers[index],
wgpu::BufferUsages::VERTEX,
"egui_vertex_buffer",
),
BufferType::Uniform => (
&mut self.uniform_buffer,
wgpu::BufferUsages::UNIFORM,
"egui_uniform_buffer",
),
};
fn create_vertex_buffer(device: &wgpu::Device, size: u64) -> wgpu::Buffer {
device.create_buffer(&wgpu::BufferDescriptor {
label: Some("egui_vertex_buffer"),
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
size,
mapped_at_creation: false,
})
}
if data.len() > buffer.size {
buffer.size = data.len();
buffer.buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some(label),
contents: bytemuck::cast_slice(data),
usage: storage | wgpu::BufferUsages::COPY_DST,
});
} else {
queue.write_buffer(&buffer.buffer, 0, data);
}
}
fn create_index_buffer(device: &wgpu::Device, size: u64) -> wgpu::Buffer {
device.create_buffer(&wgpu::BufferDescriptor {
label: Some("egui_index_buffer"),
usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,
size,
mapped_at_creation: false,
})
}
/// A Rect in physical pixel space, used for setting cliipping rectangles.