tirbofish/dropbear · diff
perf: created a DynamicBuffer to improve performance
Signature present but could not be verified.
Unverified
@@ -1,4 +1,4 @@ -use crate::buffer::{ResizableBuffer, UniformBuffer}; +use crate::buffer::{DynamicBuffer, UniformBuffer, WritableBuffer}; use crate::graphics::SharedGraphicsContext; use crate::model::{AnimationInterpolation, ChannelValues, Model, NodeTransform}; use dropbear_utils::Dirty; @@ -42,11 +42,11 @@ pub struct AnimationComponent { pub skinning_matrices: Dirty<Vec<Mat4>>, #[serde(skip)] - pub skinning_buffer: Option<ResizableBuffer<Mat4>>, + pub skinning_buffer: Option<DynamicBuffer<Mat4>>, #[serde(skip)] - pub morph_deltas_buffer: Option<ResizableBuffer<f32>>, + pub morph_deltas_buffer: Option<DynamicBuffer<f32>>, #[serde(skip)] - pub morph_weights_buffer: Option<ResizableBuffer<f32>>, + pub morph_weights_buffer: Option<DynamicBuffer<f32>>, #[serde(skip)] pub morph_info_buffer: Option<UniformBuffer<MorphTargetInfo>>, @@ -506,7 +506,7 @@ impl AnimationComponent { if has_skinning { let buffer = self.skinning_buffer.get_or_insert_with(|| { - ResizableBuffer::new( + DynamicBuffer::new( &graphics.device, self.skinning_matrices.len(), wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, @@ -545,7 +545,7 @@ impl AnimationComponent { } let weights_buffer = self.morph_weights_buffer.get_or_insert_with(|| { - ResizableBuffer::new( + DynamicBuffer::new( &graphics.device, flat.len().max(1), wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, @@ -1,18 +1,18 @@ use crate::asset::ASSET_REGISTRY; -use crate::buffer::UniformBuffer; +use crate::buffer::{DynamicBuffer, UniformBuffer, WritableBuffer}; use crate::graphics::SharedGraphicsContext; use crate::shader::Shader; use glam::Mat4; use std::sync::Arc; use wgpu::MultisampleState; -use wgpu::util::{BufferInitDescriptor, DeviceExt}; +use wgpu::util::{DeviceExt}; pub struct BillboardPipeline { pipeline: wgpu::RenderPipeline, transform_buffer: UniformBuffer<Mat4>, projection_buffer: UniformBuffer<Mat4>, - position_buffer: wgpu::Buffer, - tex_coord_buffer: wgpu::Buffer, + position_buffer: DynamicBuffer<[f32; 3]>, + tex_coord_buffer: DynamicBuffer<[f32; 2]>, uniform_bind_group_layout: wgpu::BindGroupLayout, sampler: wgpu::Sampler, } @@ -34,19 +34,27 @@ impl BillboardPipeline { [-10.0, 10.0, 0.0], ]; - let position_buffer = graphics.device.create_buffer_init(&BufferInitDescriptor { - label: Some("billboard position buffer"), - contents: bytemuck::cast_slice(&positions), - usage: wgpu::BufferUsages::VERTEX, - }); + let mut position_buffer = DynamicBuffer::new( + &graphics.device, + positions.len(), + wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, + "billboard position buffer" + ); + position_buffer.write(&graphics.device, &graphics.queue, &positions); let tex_coords: [[f32; 2]; 4] = [[1.0, 1.0], [1.0, 0.0], [0.0, 1.0], [0.0, 0.0]]; - let tex_coord_buffer = graphics.device.create_buffer_init(&BufferInitDescriptor { - label: Some("billboard tex coords buffer"), - contents: bytemuck::cast_slice(&tex_coords), - usage: wgpu::BufferUsages::VERTEX, - }); + let mut tex_coord_buffer = DynamicBuffer::new( + &graphics.device, + tex_coords.len(), + wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, + "billboard tex coords buffer" + ); + tex_coord_buffer.write( + &graphics.device, + &graphics.queue, + bytemuck::cast_slice(&tex_coords) + ); let sampler = graphics.device.create_sampler(&wgpu::SamplerDescriptor { label: Some("billboard sampler"), @@ -234,8 +242,8 @@ impl BillboardPipeline { }); render_pass.set_pipeline(&self.pipeline); - render_pass.set_vertex_buffer(0, self.position_buffer.slice(..)); - render_pass.set_vertex_buffer(1, self.tex_coord_buffer.slice(..)); + render_pass.set_vertex_buffer(0, self.position_buffer.buffer().slice(..)); + render_pass.set_vertex_buffer(1, self.tex_coord_buffer.buffer().slice(..)); render_pass.set_bind_group(0, &uniform_bind_group, &[]); render_pass.draw(0..4, 0..1); } @@ -1,98 +1,34 @@ //! Vertices and different buffers used for wgpu use std::marker::PhantomData; +use std::ops::Range; use bytemuck::NoUninit; use dropbear_utils::Dirty; -pub trait BufferType {} +pub trait WritableBuffer<T> { + fn write(&self, queue: &wgpu::Queue, value: &T); + fn buffer(&self) -> &wgpu::Buffer; +} -#[derive(Debug, Clone)] -pub struct ResizableBuffer<T> { + +#[derive(Debug, Clone, PartialEq)] +pub struct UniformBuffer<T> { buffer: wgpu::Buffer, - capacity: usize, - usage: wgpu::BufferUsages, label: String, _marker: PhantomData<T>, } -impl<T> BufferType for ResizableBuffer<T> {} - -impl<T: NoUninit> ResizableBuffer<T> { - pub fn new( - device: &wgpu::Device, - initial_capacity: usize, - usage: wgpu::BufferUsages, - label: &str, - ) -> Self { - let size = (initial_capacity * std::mem::size_of::<T>()) as wgpu::BufferAddress; - let buffer = device.create_buffer(&wgpu::BufferDescriptor { - label: Some(label), - size: size.max(16), - usage, - mapped_at_creation: false, - }); - - log::debug!( - "Registered new resizable buffer: {:?} (usage={:?})", - label, - usage - ); - Self { - buffer, - capacity: initial_capacity, - usage, - label: label.to_string(), - _marker: PhantomData, - } - } - - pub fn write(&mut self, device: &wgpu::Device, queue: &wgpu::Queue, data: &[T]) { +impl<T: NoUninit> WritableBuffer<T> for UniformBuffer<T> { + fn write(&self, queue: &wgpu::Queue, value: &T) { puffin::profile_function!(&self.label); - if data.is_empty() { - return; - } - - if data.len() > self.capacity { - self.capacity = data.len().max(self.capacity * 2); - - let new_size = (self.capacity * std::mem::size_of::<T>()) as wgpu::BufferAddress; - - log::debug!( - "Resizing buffer '{}' to hold {} items", - self.label, - self.capacity - ); - - self.buffer = device.create_buffer(&wgpu::BufferDescriptor { - label: Some(&self.label), - size: new_size, - usage: self.usage, - mapped_at_creation: false, - }); - } - - queue.write_buffer(&self.buffer, 0, bytemuck::cast_slice(data)); + queue.write_buffer(&self.buffer, 0, bytemuck::bytes_of(value)); } - pub fn buffer(&self) -> &wgpu::Buffer { + fn buffer(&self) -> &wgpu::Buffer { &self.buffer } - - pub fn slice(&self, count: usize) -> wgpu::BufferSlice<'_> { - let byte_count = (count * std::mem::size_of::<T>()) as wgpu::BufferAddress; - self.buffer.slice(0..byte_count) - } -} - -#[derive(Debug, Clone, PartialEq)] -pub struct UniformBuffer<T> { - buffer: wgpu::Buffer, - label: String, - _marker: PhantomData<T>, } -impl<T> BufferType for UniformBuffer<T> {} - impl<T: NoUninit> UniformBuffer<T> { pub fn new(device: &wgpu::Device, label: &str) -> Self { let size = (std::mem::size_of::<T>() as wgpu::BufferAddress).max(16); @@ -111,11 +47,6 @@ impl<T: NoUninit> UniformBuffer<T> { } } - pub fn write(&self, queue: &wgpu::Queue, value: &T) { - puffin::profile_function!(&self.label); - queue.write_buffer(&self.buffer, 0, bytemuck::bytes_of(value)); - } - pub fn buffer(&self) -> &wgpu::Buffer { &self.buffer } @@ -132,7 +63,16 @@ pub struct StorageBuffer<T> { _marker: PhantomData<T>, } -impl<T> BufferType for StorageBuffer<T> {} +impl<T: NoUninit> WritableBuffer<T> for StorageBuffer<T> { + fn write(&self, queue: &wgpu::Queue, value: &T) { + puffin::profile_function!(self.label()); + queue.write_buffer(&self.buffer, 0, bytemuck::bytes_of(value)); + } + + fn buffer(&self) -> &wgpu::Buffer { + &self.buffer + } +} impl<T: NoUninit> StorageBuffer<T> { pub fn new_read_only(device: &wgpu::Device, label: &str) -> Self { @@ -211,13 +151,13 @@ impl<T: NoUninit> StorageBuffer<T> { } #[derive(Clone, PartialEq)] -pub struct MutableDataBuffer<T> { +pub struct MutableDataBuffer<T, B: WritableBuffer<T> = UniformBuffer<T>> { data: Dirty<T>, - pub buffer: UniformBuffer<T>, + pub buffer: B, } -impl<T: NoUninit> MutableDataBuffer<T> { - pub fn new(data: Dirty<T>, buffer: UniformBuffer<T>) -> Self { +impl<T: NoUninit, B: WritableBuffer<T>> MutableDataBuffer<T, B> { + pub fn new(data: Dirty<T>, buffer: B) -> Self { Self { data, buffer, @@ -238,3 +178,228 @@ impl<T: NoUninit> MutableDataBuffer<T> { self.data.set(value); } } + +pub struct DynamicBuffer<T> { + data: Vec<T>, + dirty_range: Option<Range<usize>>, + buffer: wgpu::Buffer, + capacity: usize, + usage: wgpu::BufferUsages, + label: String, + _marker: PhantomData<T>, +} + +impl<T: Clone> Clone for DynamicBuffer<T> { + fn clone(&self) -> Self { + Self { + data: self.data.clone(), + dirty_range: self.dirty_range.clone(), + buffer: self.buffer.clone(), + capacity: self.capacity, + usage: self.usage, + label: self.label.clone(), + _marker: PhantomData, + } + } +} + +impl<T> std::fmt::Debug for DynamicBuffer<T> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("DynamicBuffer") + .field("label", &self.label) + .field("capacity", &self.capacity) + .field("len", &self.data.len()) + .field("dirty_range", &self.dirty_range) + .finish() + } +} + +impl<T: NoUninit> DynamicBuffer<T> { + /// Allocate an empty GPU buffer with `initial_capacity` element slots. + pub fn new( + device: &wgpu::Device, + initial_capacity: usize, + usage: wgpu::BufferUsages, + label: &str, + ) -> Self { + let size = ((initial_capacity * std::mem::size_of::<T>()) as wgpu::BufferAddress).max(16); + let buffer = device.create_buffer(&wgpu::BufferDescriptor { + label: Some(label), + size, + usage, + mapped_at_creation: false, + }); + log::debug!("Registered new dynamic buffer: {:?} (usage={:?})", label, usage); + Self { + data: Vec::with_capacity(initial_capacity), + dirty_range: None, + buffer, + capacity: initial_capacity, + usage, + label: label.to_string(), + _marker: PhantomData, + } + } + + /// Create a buffer pre-populated with `data`. The initial contents are immediately + /// uploaded via `create_buffer_init`, so no flush is needed after construction. + pub fn from_slice( + device: &wgpu::Device, + data: &[T], + usage: wgpu::BufferUsages, + label: &str, + ) -> Self { + use wgpu::util::DeviceExt; + let buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some(label), + contents: bytemuck::cast_slice(data), + usage, + }); + log::debug!("Registered new dynamic buffer (from_slice): {:?} (usage={:?})", label, usage); + Self { + capacity: data.len(), + data: data.to_vec(), + dirty_range: None, + buffer, + usage, + label: label.to_string(), + _marker: PhantomData, + } + } + + pub fn len(&self) -> usize { + self.data.len() + } + + pub fn is_empty(&self) -> bool { + self.data.is_empty() + } + + /// Borrow the CPU-side data slice. + pub fn data(&self) -> &[T] { + &self.data + } + + /// Consume the buffer and return the owned CPU-side data. + pub fn into_data(self) -> Vec<T> { + self.data + } + + /// Read an element without marking anything dirty. + pub fn get(&self, index: usize) -> Option<&T> { + self.data.get(index) + } + + /// Overwrite a single element and mark it dirty. + pub fn update(&mut self, index: usize, value: T) { + self.data[index] = value; + self.expand_dirty(index..index + 1); + } + + /// Overwrite a contiguous slice of elements starting at `start` and mark them dirty. + pub fn update_range(&mut self, start: usize, values: &[T]) + where + T: Copy, + { + let end = start + values.len(); + self.data[start..end].copy_from_slice(values); + self.expand_dirty(start..end); + } + + /// Append an element to the CPU buffer and mark the new slot dirty. + /// The GPU buffer will be reallocated on the next [`flush`](DynamicBuffer::flush) if needed. + pub fn push(&mut self, value: T) { + let idx = self.data.len(); + self.data.push(value); + self.expand_dirty(idx..idx + 1); + } + + /// Truncate the CPU buffer. Does not shrink the GPU allocation; the GPU buffer + /// will simply have unused capacity at the end until re-populated. + pub fn truncate(&mut self, len: usize) { + self.data.truncate(len); + if let Some(ref mut r) = self.dirty_range { + r.end = r.end.min(len); + if r.start >= r.end { + self.dirty_range = None; + } + } + } + + /// Upload only the dirty element range to the GPU. + /// + /// If the CPU buffer has grown beyond the current GPU capacity the GPU buffer is + /// reallocated (doubling strategy) and the full contents are re-uploaded. + /// Cheap no-op when nothing is dirty. + pub fn flush(&mut self, device: &wgpu::Device, queue: &wgpu::Queue) { + puffin::profile_function!(&self.label); + let Some(dirty) = self.dirty_range.take() else { return }; + + if self.data.len() > self.capacity { + self.capacity = self.data.len().max(self.capacity * 2); + let new_size = + ((self.capacity * std::mem::size_of::<T>()) as wgpu::BufferAddress).max(16); + log::debug!("Reallocating dynamic buffer '{}' to {} elements", self.label, self.capacity); + self.buffer = device.create_buffer(&wgpu::BufferDescriptor { + label: Some(&self.label), + size: new_size, + usage: self.usage, + mapped_at_creation: false, + }); + queue.write_buffer(&self.buffer, 0, bytemuck::cast_slice(&self.data)); + } else { + let stride = std::mem::size_of::<T>(); + let byte_offset = (dirty.start * stride) as wgpu::BufferAddress; + queue.write_buffer( + &self.buffer, + byte_offset, + bytemuck::cast_slice(&self.data[dirty]), + ); + } + } + + /// Convenience: replace all CPU data with `data` and immediately flush to the GPU. + /// + /// Equivalent to clearing, extending, marking everything dirty, then calling + /// [`flush`](DynamicBuffer::flush). Use this for buffers that are fully rewritten + /// every frame (instance buffers, debug geometry, etc.). + pub fn write(&mut self, device: &wgpu::Device, queue: &wgpu::Queue, data: &[T]) + where + T: Copy, + { + self.data.clear(); + self.data.extend_from_slice(data); + if !data.is_empty() { + self.dirty_range = Some(0..data.len()); + } + self.flush(device, queue); + } + + /// The underlying `wgpu::Buffer` — use this when binding to a render pass. + pub fn buffer(&self) -> &wgpu::Buffer { + &self.buffer + } + + /// A `BufferSlice` covering the first `count` elements (by byte length). + /// + /// Mirrors `ResizableBuffer::slice` for render-pass binding of partially-filled buffers. + pub fn slice(&self, count: usize) -> wgpu::BufferSlice<'_> { + let byte_len = (count * std::mem::size_of::<T>()) as wgpu::BufferAddress; + self.buffer.slice(0..byte_len) + } + + /// A `BufferSlice` covering exactly the live elements (`0..len`). + pub fn full_slice(&self) -> wgpu::BufferSlice<'_> { + let byte_len = (self.data.len() * std::mem::size_of::<T>()) as wgpu::BufferAddress; + self.buffer.slice(0..byte_len) + } + + fn expand_dirty(&mut self, range: Range<usize>) { + self.dirty_range = Some(match self.dirty_range.take() { + None => range, + Some(existing) => { + existing.start.min(range.start)..existing.end.max(range.end) + } + }); + } +} @@ -7,6 +7,7 @@ use serde::{Deserialize, Serialize}; use wgpu::Buffer; use crate::{buffer::UniformBuffer, graphics::SharedGraphicsContext}; +use crate::buffer::WritableBuffer; /// Matrix that converts OpenGL (from [`glam`]) to [`wgpu`] values #[rustfmt::skip] @@ -1,4 +1,4 @@ -use crate::buffer::{ResizableBuffer, UniformBuffer}; +use crate::buffer::{DynamicBuffer, UniformBuffer, WritableBuffer}; use crate::graphics::{CommandEncoder, SharedGraphicsContext}; use crate::shader::Shader; use glam::{Mat4, Quat, Vec3, Vec4}; @@ -15,7 +15,7 @@ use wgpu::{ pub struct DebugDraw { pipeline: Arc<DebugDrawPipeline>, vertices: Vec<DebugVertex>, - vertex_buffer: ResizableBuffer<DebugVertex>, + vertex_buffer: DynamicBuffer<DebugVertex>, } // main parts @@ -25,7 +25,7 @@ impl DebugDraw { pub fn new(graphics: Arc<SharedGraphicsContext>) -> Self { let pipeline = Arc::new(DebugDrawPipeline::new(graphics.clone())); let vertices = vec![]; - let vertex_buffer = ResizableBuffer::new( + let vertex_buffer = DynamicBuffer::new( &graphics.device, 1024, BufferUsages::VERTEX | BufferUsages::COPY_DST, @@ -532,7 +532,7 @@ impl DebugDrawPipeline { graphics: Arc<SharedGraphicsContext>, encoder: &mut CommandEncoder, view_proj: Mat4, - vertex_buffer: &ResizableBuffer<DebugVertex>, + vertex_buffer: &DynamicBuffer<DebugVertex>, vertex_count: u32, ) { // update camera uniform @@ -1,6 +1,6 @@ use crate::asset::{ASSET_REGISTRY, Handle}; use crate::attenuation::{Attenuation, RANGE_50}; -use crate::buffer::{ResizableBuffer, UniformBuffer}; +use crate::buffer::{DynamicBuffer, UniformBuffer, WritableBuffer}; use crate::graphics::SharedGraphicsContext; use crate::pipelines::light_cube::InstanceInput; use crate::procedural::ProcedurallyGeneratedObject; @@ -275,7 +275,7 @@ pub struct Light { pub label: String, pub buffer: UniformBuffer<LightUniform>, pub bind_group: wgpu::BindGroup, - pub instance_buffer: ResizableBuffer<InstanceInput>, + pub instance_buffer: DynamicBuffer<InstanceInput>, pub component: LightComponent, } @@ -331,7 +331,7 @@ impl Light { ) .into(); - let mut instance_buffer = ResizableBuffer::new( + let mut instance_buffer = DynamicBuffer::new( &graphics.device, 1, wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, @@ -1,5 +1,5 @@ use crate::asset::{AssetRegistry, Handle}; -use crate::buffer::UniformBuffer; +use crate::buffer::{DynamicBuffer, MutableDataBuffer, UniformBuffer, WritableBuffer}; use crate::texture::TextureBuilder; use crate::{ graphics::SharedGraphicsContext, @@ -39,11 +39,10 @@ pub struct Model { // #[derive(Clone)] pub struct Mesh { pub name: String, - pub vertex_buffer: wgpu::Buffer, - pub index_buffer: wgpu::Buffer, + pub vertex_buffer: DynamicBuffer<ModelVertex>, + pub index_buffer: DynamicBuffer<u32>, pub num_elements: u32, pub material: usize, - pub vertices: Vec<ModelVertex>, pub morph_deltas_offset: u32, pub morph_target_count: u32, pub morph_vertex_count: u32, @@ -1420,29 +1419,24 @@ impl Model { }); } - let vertex_buffer = - graphics - .device - .create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some(&format!("{} Vertex Buffer", model_label)), - contents: bytemuck::cast_slice(&vertices), - usage: wgpu::BufferUsages::VERTEX, - }); + let vertex_buffer = DynamicBuffer::from_slice( + &graphics.device, + &vertices, + wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, + &format!("{} Vertex Buffer", model_label), + ); - let index_buffer = - graphics - .device - .create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some(&format!("{} Index Buffer", model_label)), - contents: bytemuck::cast_slice(&mesh_info.indices), - usage: wgpu::BufferUsages::INDEX, - }); + let index_buffer = DynamicBuffer::from_slice( + &graphics.device, + &mesh_info.indices, + wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST, + &format!("{} Index Buffer", model_label), + ); gpu_meshes.push(Mesh { name: mesh_info.name, vertex_buffer, index_buffer, - vertices, num_elements: mesh_info.indices.len() as u32, material: mesh_info.material_index, morph_deltas_offset, @@ -1563,8 +1557,8 @@ where animation_bind_group: &'a wgpu::BindGroup, environment_bind_group: &'a wgpu::BindGroup, ) { - self.set_vertex_buffer(0, mesh.vertex_buffer.slice(..)); - self.set_index_buffer(mesh.index_buffer.slice(..), wgpu::IndexFormat::Uint32); + self.set_vertex_buffer(0, mesh.vertex_buffer.full_slice()); + self.set_index_buffer(mesh.index_buffer.full_slice(), wgpu::IndexFormat::Uint32); self.set_bind_group(0, globals_camera_bind_group, &[]); self.set_bind_group(1, &material.bind_group, &[]); self.set_bind_group(2, animation_bind_group, &[]); @@ -1663,8 +1657,8 @@ where camera_bind_group: &'a wgpu::BindGroup, light_bind_group: &'a wgpu::BindGroup, ) { - self.set_vertex_buffer(0, mesh.vertex_buffer.slice(..)); - self.set_index_buffer(mesh.index_buffer.slice(..), wgpu::IndexFormat::Uint32); + self.set_vertex_buffer(0, mesh.vertex_buffer.full_slice()); + self.set_index_buffer(mesh.index_buffer.full_slice(), wgpu::IndexFormat::Uint32); self.set_bind_group(0, camera_bind_group, &[]); self.set_bind_group(1, light_bind_group, &[]); self.draw_indexed(0..mesh.num_elements, 0, instances); @@ -1,7 +1,7 @@ use std::sync::Arc; use glam::{Mat4}; use crate::animation::{MorphTargetInfo, MAX_MORPH_WEIGHTS, MAX_SKINNING_MATRICES}; -use crate::buffer::{StorageBuffer, UniformBuffer}; +use crate::buffer::{StorageBuffer, UniformBuffer, WritableBuffer}; use crate::graphics::SharedGraphicsContext; pub struct AnimationDefaults { @@ -1,4 +1,4 @@ -use crate::buffer::UniformBuffer; +use crate::buffer::{UniformBuffer, WritableBuffer}; use crate::graphics::SharedGraphicsContext; use dropbear_utils::Dirty; use std::sync::Arc; @@ -1,4 +1,4 @@ -use crate::buffer::StorageBuffer; +use crate::buffer::{StorageBuffer, WritableBuffer}; use crate::graphics::SharedGraphicsContext; use crate::lighting::{Light, LightArrayUniform, MAX_LIGHTS}; use crate::model::{ModelVertex, Vertex}; @@ -2,6 +2,7 @@ //! vertices rather than from a model. use crate::asset::{AssetRegistry, Handle}; +use crate::buffer::DynamicBuffer; use crate::graphics::SharedGraphicsContext; use crate::model::ModelVertex; use crate::model::{Material, Mesh, Model}; @@ -76,21 +77,19 @@ impl ProcedurallyGeneratedObject { let vertices = self.vertices.clone(); let indices = self.indices.clone(); - let vertex_buffer = graphics - .device - .create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some(&format!("{label} Vertex Buffer")), - contents: bytemuck::cast_slice(&vertices), - usage: wgpu::BufferUsages::VERTEX, - }); - - let index_buffer = graphics - .device - .create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some(&format!("{label} Index Buffer")), - contents: bytemuck::cast_slice(&indices), - usage: wgpu::BufferUsages::INDEX, - }); + let vertex_buffer = DynamicBuffer::from_slice( + &graphics.device, + &vertices, + wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, + &format!("{label} Vertex Buffer"), + ); + + let index_buffer = DynamicBuffer::from_slice( + &graphics.device, + &indices, + wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST, + &format!("{label} Index Buffer"), + ); let mesh = Mesh { name: label.clone(), @@ -98,7 +97,6 @@ impl ProcedurallyGeneratedObject { index_buffer, num_elements: indices.len() as u32, material: 0, - vertices, morph_deltas_offset: 0, morph_target_count: 0, morph_vertex_count: self.vertices.len() as u32, @@ -1,9 +1,6 @@ use crate::component::{ Component, ComponentDescriptor, DisabilityFlags, InspectableComponent, SerializedComponent, }; -use crate::ptr::WorldPtr; -use crate::scripting::native::DropbearNativeError; -use crate::scripting::result::DropbearNativeResult; use dropbear_engine::animation::{AnimationComponent, AnimationSettings}; use dropbear_engine::asset::ASSET_REGISTRY; use dropbear_engine::entity::{EntityTransform, MeshRenderer}; @@ -31,9 +28,9 @@ impl Component for AnimationComponent { } fn init( - ser: &Self::SerializedForm, + ser: &'_ Self::SerializedForm, _graphics: Arc<SharedGraphicsContext>, - ) -> crate::component::ComponentInitFuture<Self> { + ) -> crate::component::ComponentInitFuture<'_, Self> { Box::pin(async move { Ok((ser.clone(),)) }) } @@ -146,7 +146,7 @@ impl InspectableComponent for ColliderGroup { let mut max = [f32::NEG_INFINITY; 3]; for mesh in &model.meshes { - for vertex in &mesh.vertices { + for vertex in mesh.vertex_buffer.data() { let p = vertex.position; for i in 0..3 { min[i] = min[i].min(p[i]); @@ -32,7 +32,7 @@ impl Component for CustomProperties { internal: false, fqtn: "eucalyptus_core::properties::CustomProperties".to_string(), type_name: "CustomProperties".to_string(), - category: None, + category: Some("Properties".to_string()), description: Some("Custom properties for an entity".to_string()), } } @@ -40,7 +40,7 @@ impl Component for CustomProperties { fn init( ser: &Self::SerializedForm, _graphics: Arc<SharedGraphicsContext>, - ) -> ComponentInitFuture<Self> { + ) -> ComponentInitFuture<'_, Self> { Box::pin(async move { Ok((ser.clone(),)) }) } @@ -6,7 +6,7 @@ use glam::{Mat4, Quat, Vec3}; use dropbear_engine::animation::{AnimationComponent, MorphTargetInfo}; use dropbear_engine::asset::{Handle, ASSET_REGISTRY}; use dropbear_engine::billboarding::BillboardPipeline; -use dropbear_engine::buffer::ResizableBuffer; +use dropbear_engine::buffer::DynamicBuffer; use dropbear_engine::camera::Camera; use dropbear_engine::entity::{EntityTransform, MeshRenderer, Transform}; use dropbear_engine::graphics::{CommandEncoder, InstanceRaw, SharedGraphicsContext}; @@ -177,7 +177,7 @@ impl RendererCommon { pub fn prepare_models( graphics: &SharedGraphicsContext, batches: &HashMap<u64, ModelBatch>, - instance_buffer_cache: &mut HashMap<u64, ResizableBuffer<InstanceRaw>>, + instance_buffer_cache: &mut HashMap<u64, DynamicBuffer<InstanceRaw>>, ) -> (Vec<PreparedModel>, HashMap<u64, Arc<Model>>) { puffin::profile_scope!("preparing models"); let registry = ASSET_REGISTRY.read(); @@ -202,7 +202,7 @@ impl RendererCommon { let instance_buffer = instance_buffer_cache .entry(*handle_id) - .or_insert_with(|| ResizableBuffer::new( + .or_insert_with(|| DynamicBuffer::new( &graphics.device, instances.len().max(1), wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, @@ -286,8 +286,8 @@ impl RendererCommon { environment_bind_group: &wgpu::BindGroup, pipeline: &MainRenderPipeline, animation_defaults: &AnimationDefaults, - instance_buffer_cache: &HashMap<u64, ResizableBuffer<InstanceRaw>>, - animated_instance_buffers: &mut HashMap<Entity, ResizableBuffer<InstanceRaw>>, + instance_buffer_cache: &HashMap<u64, DynamicBuffer<InstanceRaw>>, + animated_instance_buffers: &mut HashMap<Entity, DynamicBuffer<InstanceRaw>>, animated_bind_group_cache: &mut HashMap<Entity, (u64, wgpu::BindGroup)>, static_bind_group_cache: &mut HashMap<u64, wgpu::BindGroup>, last_morph_info_per_mesh: &mut HashMap<u32, MorphTargetInfo>, @@ -392,7 +392,7 @@ impl RendererCommon { { let buf = animated_instance_buffers.entry(inst.entity).or_insert_with(|| { - ResizableBuffer::new( + DynamicBuffer::new( &graphics.device, 1, wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, "animated instance buffer", @@ -1,4 +1,5 @@ use dropbear_engine::asset::{ASSET_REGISTRY, Handle}; +use dropbear_engine::buffer::DynamicBuffer; use dropbear_engine::graphics::SharedGraphicsContext; use dropbear_engine::model::{ AlphaMode, Animation, Material, Mesh, Model, ModelVertex, Node, Skin, @@ -158,7 +159,7 @@ impl From<Mesh> for EucalyptusMesh { name: value.name, num_elements: value.num_elements, material: value.material, - vertices: value.vertices, + vertices: value.vertex_buffer.into_data(), morph_deltas_offset: value.morph_deltas_offset, morph_target_count: value.morph_target_count, morph_vertex_count: value.morph_vertex_count, @@ -169,23 +170,21 @@ impl From<Mesh> for EucalyptusMesh { impl EucalyptusMesh { fn load(&self, graphics: Arc<SharedGraphicsContext>) -> Mesh { - let vertex_buffer = graphics - .device - .create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some(&format!("{} Vertex Buffer", self.name)), - contents: bytemuck::cast_slice(&self.vertices), - usage: wgpu::BufferUsages::VERTEX, - }); + let vertex_buffer = DynamicBuffer::from_slice( + &graphics.device, + &self.vertices, + wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, + &format!("{} Vertex Buffer", self.name), + ); let index_count = self.num_elements.min(self.vertices.len() as u32); let indices = (0..index_count).collect::<Vec<u32>>(); - let index_buffer = graphics - .device - .create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some(&format!("{} Index Buffer", self.name)), - contents: bytemuck::cast_slice(&indices), - usage: wgpu::BufferUsages::INDEX, - }); + let index_buffer = DynamicBuffer::from_slice( + &graphics.device, + &indices, + wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST, + &format!("{} Index Buffer", self.name), + ); Mesh { name: self.name.clone(), @@ -193,7 +192,6 @@ impl EucalyptusMesh { index_buffer, num_elements: index_count, material: self.material, - vertices: self.vertices.clone(), morph_deltas_offset: self.morph_deltas_offset, morph_target_count: self.morph_target_count, morph_vertex_count: self.morph_vertex_count, @@ -450,7 +450,7 @@ fn aabb_for_world_matrix(world_mat: glam::Mat4, mesh: &MeshRenderer) -> (glam::V let mut lo = Vec3::splat(f32::INFINITY); let mut hi = Vec3::splat(f32::NEG_INFINITY); for m in &model.meshes { - for v in &m.vertices { + for v in m.vertex_buffer.data() { let p = Vec3::from(v.position); lo = lo.min(p); hi = hi.max(p); @@ -355,14 +355,8 @@ impl Mouse for Editor { .query_one::<(&mut Camera, &CameraComponent)>(active_camera) .get() { - let frame_scale = if self.dt > 0.0 { - (self.dt as f64 / (1.0 / 60.0)).clamp(0.1, 4.0) - } else { - 1.0 - }; - if let Some((dx, dy)) = delta { - camera.track_mouse_delta(dx * frame_scale, dy * frame_scale); + camera.track_mouse_delta(dx, dy); self.input_state.mouse_delta = Some((dx, dy)); } else { log_once::warn_once!("Unable to track mouse delta, attempting fallback"); @@ -370,7 +364,7 @@ impl Mouse for Editor { if let Some(old_mouse_pos) = self.input_state.last_mouse_pos { let dx = position.x - old_mouse_pos.0; let dy = position.y - old_mouse_pos.1; - camera.track_mouse_delta(dx * frame_scale, dy * frame_scale); + camera.track_mouse_delta(dx, dy); self.input_state.mouse_delta = Some((dx, dy)); log_once::debug_once!("Fallback mouse tracking used"); } else { @@ -21,7 +21,7 @@ use crossbeam_channel::{Receiver, Sender, unbounded}; use docks::console::EucalyptusConsole; use dropbear_engine::animation::MorphTargetInfo; use dropbear_engine::billboarding::BillboardPipeline; -use dropbear_engine::buffer::ResizableBuffer; +use dropbear_engine::buffer::DynamicBuffer; use dropbear_engine::entity::EntityTransform; use dropbear_engine::graphics::InstanceRaw; use dropbear_engine::mipmap::MipMapper; @@ -83,7 +83,7 @@ pub struct Editor { pub ui_editor_dock_state: DockState<EditorTabId>, pub texture_id: Option<egui::TextureId>, pub size: Extent3d, - pub instance_buffer_cache: HashMap<u64, ResizableBuffer<InstanceRaw>>, + pub instance_buffer_cache: HashMap<u64, DynamicBuffer<InstanceRaw>>, pub color: Color, pub ui_editor: UiEditor, @@ -99,7 +99,7 @@ pub struct Editor { pub billboard_pipeline: Option<BillboardPipeline>, pub kino: Option<KinoState>, pub animation_pipeline: Option<AnimationDefaults>, - pub(crate) animated_instance_buffers: HashMap<Entity, ResizableBuffer<InstanceRaw>>, + pub(crate) animated_instance_buffers: HashMap<Entity, DynamicBuffer<InstanceRaw>>, pub(crate) animated_bind_group_cache: HashMap<Entity, (u64, wgpu::BindGroup)>, pub(crate) static_bind_group_cache: HashMap<u64, wgpu::BindGroup>, pub(crate) last_morph_info_per_mesh: HashMap<u32, MorphTargetInfo>, // key = morph_deltas_offset @@ -22,7 +22,7 @@ pub struct NModelVertex { impl ToJObject for NModelVertex { fn to_jobject<'a>(&self, env: &mut Env<'a>) -> DropbearNativeResult<JObject<'a>> { let class = env - .load_class(jni_str!("com/dropbear/asset/model/ModelVertex")) + .load_class(jni_str!("com.dropbear.asset.model.ModelVertex")) .map_err(|_| DropbearNativeError::JNIClassNotFound)?; let position = self.position.to_jobject(env)?; @@ -80,6 +80,7 @@ pub struct NMesh { pub num_elements: i32, pub material_index: i32, pub vertices: Vec<NModelVertex>, + pub indices: Vec<u32>, } impl ToJObject for NMesh { @@ -118,7 +119,8 @@ impl From<&Mesh> for NMesh { name: mesh.name.clone(), num_elements: mesh.num_elements as i32, material_index: mesh.material as i32, - vertices: mesh.vertices.iter().map(|v| (*v).into()).collect(), + vertices: mesh.vertex_buffer.data().iter().map(|v| (*v).into()).collect(), + indices: mesh.index_buffer.data().to_vec(), } } } @@ -2,4 +2,8 @@ A c header compile-time generator that sits in build.rs. -When paired with `dropbear_macro`, you can generate c headers and JNI headers easily for interop. +When paired with `dropbear_macro`, you can generate c headers and JNI headers easily for interop. + +> [!NOTE] +> AI has generated this crate. Yes, throw your rotten tomatoes at me, but I do not know how to generate +> headers, and it makes my life easier to just get AI to create me a header generator. @@ -3,7 +3,7 @@ use crossbeam_channel::{Receiver, unbounded}; use dropbear_engine::animation::MorphTargetInfo; use dropbear_engine::billboarding::BillboardPipeline; -use dropbear_engine::buffer::ResizableBuffer; +use dropbear_engine::buffer::DynamicBuffer; use dropbear_engine::camera::Camera; use dropbear_engine::future::{FutureHandle, FutureQueue}; use dropbear_engine::graphics::{InstanceRaw, SharedGraphicsContext}; @@ -131,8 +131,8 @@ pub struct PlayMode { light_cube_pipeline: Option<LightCubePipeline>, main_pipeline: Option<MainRenderPipeline>, shader_globals: Option<GlobalsUniform>, - instance_buffer_cache: HashMap<u64, ResizableBuffer<InstanceRaw>>, - animated_instance_buffers: HashMap<Entity, ResizableBuffer<InstanceRaw>>, + instance_buffer_cache: HashMap<u64, DynamicBuffer<InstanceRaw>>, + animated_instance_buffers: HashMap<Entity, DynamicBuffer<InstanceRaw>>, sky_pipeline: Option<SkyPipeline>, animation_pipeline: Option<AnimationDefaults>, billboard_pipeline: Option<BillboardPipeline>, @@ -304,11 +304,18 @@ typedef struct NModelVertexArray { size_t capacity; } NModelVertexArray; +typedef struct u32Array { + uint32_t* values; + size_t length; + size_t capacity; +} u32Array; + typedef struct NMesh { const char* name; int32_t num_elements; int32_t material_index; NModelVertexArray vertices; + u32Array indices; } NMesh; typedef struct NMeshArray { @@ -4,5 +4,6 @@ data class Mesh( val name: String, val numElements: Int, val materialIndex: Int, - val vertices: List<ModelVertex> + val vertices: List<ModelVertex>, + val indices: List<UInt>, ) @@ -19,7 +19,13 @@ actual fun Model.getLabel(): String = memScoped { if (rc != 0) "" else out.value?.toKString() ?: "" } -actual fun Model.getMeshes(): List<Mesh> = emptyList() // complex C array parsing deferred +actual fun Model.getMeshes(): List<Mesh> = memScoped { + TODO("tbc") +// +// val result = dropbear_asset_model_get_meshes( +// +// ) +} actual fun Model.getMaterials(): List<Material> = emptyList() actual fun Model.getSkins(): List<Skin> = emptyList() actual fun Model.getAnimations(): List<Animation> = emptyList()