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:
parent
02d1e7492a
commit
944159d514
2 changed files with 105 additions and 104 deletions
|
@ -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)).
|
||||||
|
|
|
@ -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 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",
|
|
||||||
),
|
|
||||||
};
|
|
||||||
|
|
||||||
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_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,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.
|
/// A Rect in physical pixel space, used for setting cliipping rectangles.
|
||||||
|
|
Loading…
Reference in a new issue