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`. * Reexported `Renderer`.
* `Renderer` no longer handles pass creation and depth buffer creation ([#2136](https://github.com/emilk/egui/pull/2136)) * `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)) * `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 ## 0.19.0 - 2022-08-20
* Enables deferred render + surface state initialization for Android ([#1634](https://github.com/emilk/egui/pull/1634)). * 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)] #![allow(unsafe_code)]
use std::num::NonZeroU64; use std::num::NonZeroU64;
use std::ops::Range;
use std::{borrow::Cow, collections::HashMap, num::NonZeroU32}; use std::{borrow::Cow, collections::HashMap, num::NonZeroU32};
use egui::epaint::Vertex;
use egui::NumExt;
use egui::{epaint::Primitive, PaintCallbackInfo}; use egui::{epaint::Primitive, PaintCallbackInfo};
use type_map::concurrent::TypeMap; use type_map::concurrent::TypeMap;
use wgpu; 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. /// Information about the screen used for rendering.
pub struct ScreenDescriptor { pub struct ScreenDescriptor {
/// Size of the window in physical pixels. /// Size of the window in physical pixels.
@ -119,20 +114,20 @@ struct UniformBuffer {
_padding: [u32; 2], _padding: [u32; 2],
} }
/// Wraps the buffers and includes additional information. struct SlicedBuffer {
#[derive(Debug)]
struct SizedBuffer {
buffer: wgpu::Buffer, buffer: wgpu::Buffer,
/// number of bytes slices: Vec<Range<wgpu::BufferAddress>>,
size: usize, capacity: wgpu::BufferAddress,
} }
/// Renderer for a egui based GUI. /// Renderer for a egui based GUI.
pub struct Renderer { pub struct Renderer {
pipeline: wgpu::RenderPipeline, pipeline: wgpu::RenderPipeline,
index_buffers: Vec<SizedBuffer>,
vertex_buffers: Vec<SizedBuffer>, index_buffer: SlicedBuffer,
uniform_buffer: SizedBuffer, vertex_buffer: SlicedBuffer,
uniform_buffer: wgpu::Buffer,
uniform_bind_group: wgpu::BindGroup, uniform_bind_group: wgpu::BindGroup,
texture_bind_group_layout: wgpu::BindGroupLayout, texture_bind_group_layout: wgpu::BindGroupLayout,
/// Map of egui texture IDs to textures and their associated bindgroups (texture view + /// 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, 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 = let uniform_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
@ -196,7 +187,7 @@ impl Renderer {
entries: &[wgpu::BindGroupEntry { entries: &[wgpu::BindGroupEntry {
binding: 0, binding: 0,
resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding { resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
buffer: &uniform_buffer.buffer, buffer: &uniform_buffer,
offset: 0, offset: 0,
size: None, size: None,
}), }),
@ -299,10 +290,23 @@ impl Renderer {
multiview: None, 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 { Self {
pipeline, pipeline,
vertex_buffers: Vec::with_capacity(64), vertex_buffer: SlicedBuffer {
index_buffers: Vec::with_capacity(64), 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_buffer,
uniform_bind_group, uniform_bind_group,
texture_bind_group_layout, texture_bind_group_layout,
@ -326,8 +330,8 @@ impl Renderer {
// run. // run.
let mut needs_reset = true; let mut needs_reset = true;
let mut index_buffers = self.index_buffers.iter(); let mut index_buffer_slices = self.index_buffer.slices.iter();
let mut vertex_buffers = self.vertex_buffers.iter(); let mut vertex_buffer_slices = self.vertex_buffer.slices.iter();
for egui::ClippedPrimitive { for egui::ClippedPrimitive {
clip_rect, clip_rect,
@ -355,8 +359,8 @@ impl Renderer {
// Skip rendering zero-sized clip areas. // Skip rendering zero-sized clip areas.
if let Primitive::Mesh(_) = primitive { if let Primitive::Mesh(_) = primitive {
// If this is a mesh, we need to advance the index and vertex buffer iterators: // If this is a mesh, we need to advance the index and vertex buffer iterators:
index_buffers.next().unwrap(); index_buffer_slices.next().unwrap();
vertex_buffers.next().unwrap(); vertex_buffer_slices.next().unwrap();
} }
continue; continue;
} }
@ -366,16 +370,19 @@ impl Renderer {
match primitive { match primitive {
Primitive::Mesh(mesh) => { Primitive::Mesh(mesh) => {
let index_buffer = index_buffers.next().unwrap(); let index_buffer_slice = index_buffer_slices.next().unwrap();
let vertex_buffer = vertex_buffers.next().unwrap(); let vertex_buffer_slice = vertex_buffer_slices.next().unwrap();
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(
index_buffer.buffer.slice(..), self.index_buffer.buffer.slice(index_buffer_slice.clone()),
wgpu::IndexFormat::Uint32, 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); render_pass.draw_indexed(0..mesh.indices.len() as u32, 0, 0..1);
} else { } else {
tracing::warn!("Missing texture: {:?}", mesh.texture_id); tracing::warn!("Missing texture: {:?}", mesh.texture_id);
@ -709,10 +716,9 @@ impl Renderer {
) { ) {
let screen_size_in_points = screen_descriptor.screen_size_in_points(); let screen_size_in_points = screen_descriptor.screen_size_in_points();
self.update_buffer( // Update uniform buffer
device, queue.write_buffer(
queue, &self.uniform_buffer,
&BufferType::Uniform,
0, 0,
bytemuck::cast_slice(&[UniformBuffer { bytemuck::cast_slice(&[UniformBuffer {
screen_size_in_points, 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() { for egui::ClippedPrimitive { primitive, .. } in paint_jobs.iter() {
match primitive { match primitive {
Primitive::Mesh(mesh) => { Primitive::Mesh(mesh) => {
let data: &[u8] = bytemuck::cast_slice(&mesh.indices); {
if mesh_idx < self.index_buffers.len() { let index_offset = self.index_buffer.slices.last().unwrap_or(&(0..0)).end;
self.update_buffer(device, queue, &BufferType::Index, mesh_idx, data); let data = bytemuck::cast_slice(&mesh.indices);
} else { queue.write_buffer(&self.index_buffer.buffer, index_offset, data);
let buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { self.index_buffer
label: Some("egui_index_buffer"), .slices
contents: data, .push(index_offset..(data.len() as wgpu::BufferAddress + index_offset));
usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,
});
self.index_buffers.push(SizedBuffer {
buffer,
size: data.len(),
});
} }
{
let data: &[u8] = bytemuck::cast_slice(&mesh.vertices); let vertex_offset = self.vertex_buffer.slices.last().unwrap_or(&(0..0)).end;
if mesh_idx < self.vertex_buffers.len() { let data = bytemuck::cast_slice(&mesh.vertices);
self.update_buffer(device, queue, &BufferType::Vertex, mesh_idx, data); queue.write_buffer(&self.vertex_buffer.buffer, vertex_offset, data);
} else { self.vertex_buffer.slices.push(
let buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { vertex_offset..(data.len() as wgpu::BufferAddress + vertex_offset),
label: Some("egui_vertex_buffer"), );
contents: data,
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
});
self.vertex_buffers.push(SizedBuffer {
buffer,
size: data.len(),
});
} }
mesh_idx += 1;
} }
Primitive::Callback(callback) => { Primitive::Callback(callback) => {
let cbfn = if let Some(c) = callback.callback.downcast_ref::<CallbackFn>() { 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 create_vertex_buffer(device: &wgpu::Device, size: u64) -> wgpu::Buffer {
fn update_buffer( device.create_buffer(&wgpu::BufferDescriptor {
&mut self, label: Some("egui_vertex_buffer"),
device: &wgpu::Device, usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
queue: &wgpu::Queue, size,
buffer_type: &BufferType, mapped_at_creation: false,
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",
),
};
if data.len() > buffer.size { fn create_index_buffer(device: &wgpu::Device, size: u64) -> wgpu::Buffer {
buffer.size = data.len(); device.create_buffer(&wgpu::BufferDescriptor {
buffer.buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("egui_index_buffer"),
label: Some(label), usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,
contents: bytemuck::cast_slice(data), size,
usage: storage | wgpu::BufferUsages::COPY_DST, mapped_at_creation: false,
}); })
} else {
queue.write_buffer(&buffer.buffer, 0, data);
}
}
} }
/// A Rect in physical pixel space, used for setting cliipping rectangles. /// A Rect in physical pixel space, used for setting cliipping rectangles.