From 944159d514bfa566a1093ee44b617704c90d0a38 Mon Sep 17 00:00:00 2001 From: Andreas Reich Date: Fri, 14 Oct 2022 10:53:19 +0200 Subject: [PATCH] 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 --- crates/egui-wgpu/CHANGELOG.md | 2 +- crates/egui-wgpu/src/renderer.rs | 207 ++++++++++++++++--------------- 2 files changed, 105 insertions(+), 104 deletions(-) diff --git a/crates/egui-wgpu/CHANGELOG.md b/crates/egui-wgpu/CHANGELOG.md index b6835c66..39835934 100644 --- a/crates/egui-wgpu/CHANGELOG.md +++ b/crates/egui-wgpu/CHANGELOG.md @@ -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)). diff --git a/crates/egui-wgpu/src/renderer.rs b/crates/egui-wgpu/src/renderer.rs index fb6b1130..0ad37720 100644 --- a/crates/egui-wgpu/src/renderer.rs +++ b/crates/egui-wgpu/src/renderer.rs @@ -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>, + capacity: wgpu::BufferAddress, } /// Renderer for a egui based GUI. pub struct Renderer { pipeline: wgpu::RenderPipeline, - index_buffers: Vec, - vertex_buffers: Vec, - 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::(), - }; 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::() * 1024) as _; + const INDEX_BUFFER_START_CAPACITY: wgpu::BufferAddress = + (std::mem::size_of::() * 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::() * 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_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::() { @@ -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.