diff --git a/crates/bevy_pbr/src/render/morph.rs b/crates/bevy_pbr/src/render/morph.rs index cdc9c599ea24b..21beb3a99a60f 100644 --- a/crates/bevy_pbr/src/render/morph.rs +++ b/crates/bevy_pbr/src/render/morph.rs @@ -6,7 +6,7 @@ use bevy_ecs::prelude::*; use bevy_render::{ batching::NoAutomaticBatching, mesh::morph::{MeshMorphWeights, MAX_MORPH_WEIGHTS}, - render_resource::{BufferUsages, BufferVec}, + render_resource::{BufferUsages, RawBufferVec}, renderer::{RenderDevice, RenderQueue}, view::ViewVisibility, Extract, @@ -23,13 +23,13 @@ pub struct MorphIndices(EntityHashMap); #[derive(Resource)] pub struct MorphUniform { - pub buffer: BufferVec, + pub buffer: RawBufferVec, } impl Default for MorphUniform { fn default() -> Self { Self { - buffer: BufferVec::new(BufferUsages::UNIFORM), + buffer: RawBufferVec::new(BufferUsages::UNIFORM), } } } @@ -54,7 +54,7 @@ const fn can_align(step: usize, target: usize) -> bool { const WGPU_MIN_ALIGN: usize = 256; /// Align a [`BufferVec`] to `N` bytes by padding the end with `T::default()` values. -fn add_to_alignment(buffer: &mut BufferVec) { +fn add_to_alignment(buffer: &mut RawBufferVec) { let n = WGPU_MIN_ALIGN; let t_size = mem::size_of::(); if !can_align(n, t_size) { diff --git a/crates/bevy_pbr/src/render/skin.rs b/crates/bevy_pbr/src/render/skin.rs index 6a725b546c5e3..5d3e2ba2c9369 100644 --- a/crates/bevy_pbr/src/render/skin.rs +++ b/crates/bevy_pbr/src/render/skin.rs @@ -6,7 +6,7 @@ use bevy_math::Mat4; use bevy_render::{ batching::NoAutomaticBatching, mesh::skinning::{SkinnedMesh, SkinnedMeshInverseBindposes}, - render_resource::{BufferUsages, BufferVec}, + render_resource::{BufferUsages, RawBufferVec}, renderer::{RenderDevice, RenderQueue}, view::ViewVisibility, Extract, @@ -36,13 +36,13 @@ pub struct SkinIndices(EntityHashMap); // Notes on implementation: see comment on top of the `extract_skins` system. #[derive(Resource)] pub struct SkinUniform { - pub buffer: BufferVec, + pub buffer: RawBufferVec, } impl Default for SkinUniform { fn default() -> Self { Self { - buffer: BufferVec::new(BufferUsages::UNIFORM), + buffer: RawBufferVec::new(BufferUsages::UNIFORM), } } } diff --git a/crates/bevy_render/src/render_resource/buffer_vec.rs b/crates/bevy_render/src/render_resource/buffer_vec.rs index 26656c58ed983..460bc9139b56b 100644 --- a/crates/bevy_render/src/render_resource/buffer_vec.rs +++ b/crates/bevy_render/src/render_resource/buffer_vec.rs @@ -1,9 +1,15 @@ +use std::{iter, marker::PhantomData}; + use crate::{ render_resource::Buffer, renderer::{RenderDevice, RenderQueue}, }; use bytemuck::{cast_slice, Pod}; -use wgpu::BufferUsages; +use encase::{ + internal::{WriteInto, Writer}, + ShaderType, +}; +use wgpu::{BufferAddress, BufferUsages}; /// A structure for storing raw bytes that have already been properly formatted /// for use by the GPU. @@ -28,7 +34,7 @@ use wgpu::BufferUsages; /// * [`GpuArrayBuffer`](crate::render_resource::GpuArrayBuffer) /// * [`BufferVec`] /// * [`Texture`](crate::render_resource::Texture) -pub struct BufferVec { +pub struct RawBufferVec { values: Vec, buffer: Option, capacity: usize, @@ -38,7 +44,7 @@ pub struct BufferVec { label_changed: bool, } -impl BufferVec { +impl RawBufferVec { pub const fn new(buffer_usage: BufferUsages) -> Self { Self { values: Vec::new(), @@ -77,7 +83,7 @@ impl BufferVec { index } - pub fn append(&mut self, other: &mut BufferVec) { + pub fn append(&mut self, other: &mut RawBufferVec) { self.values.append(&mut other.values); } @@ -112,7 +118,7 @@ impl BufferVec { let size = self.item_size * capacity; self.buffer = Some(device.create_buffer(&wgpu::BufferDescriptor { label: self.label.as_deref(), - size: size as wgpu::BufferAddress, + size: size as BufferAddress, usage: BufferUsages::COPY_DST | self.buffer_usage, mapped_at_creation: false, })); @@ -154,9 +160,166 @@ impl BufferVec { } } -impl Extend for BufferVec { +impl Extend for RawBufferVec { #[inline] fn extend>(&mut self, iter: I) { self.values.extend(iter); } } + +/// Like [`RawBufferVec`], but doesn't require that the data type `T` be +/// [`Pod`]. +/// +/// This is a high-performance data structure that you should use whenever +/// possible if your data is more complex than is suitable for [`RawBufferVec`]. +/// The [`ShaderType`] trait from the `encase` library is used to ensure that +/// the data is correctly aligned for use by the GPU. +/// +/// For performance reasons, unlike [`RawBufferVec`], this type doesn't allow +/// CPU access to the data after it's been added via [`BufferVec::push`]. If you +/// need CPU access to the data, consider another type, such as +/// [`StorageBuffer`]. +pub struct BufferVec +where + T: ShaderType + WriteInto, +{ + data: Vec, + buffer: Option, + capacity: usize, + buffer_usage: BufferUsages, + label: Option, + label_changed: bool, + phantom: PhantomData, +} + +impl BufferVec +where + T: ShaderType + WriteInto, +{ + /// Creates a new [`BufferVec`] with the given [`BufferUsages`]. + pub const fn new(buffer_usage: BufferUsages) -> Self { + Self { + data: vec![], + buffer: None, + capacity: 0, + buffer_usage, + label: None, + label_changed: false, + phantom: PhantomData, + } + } + + /// Returns a handle to the buffer, if the data has been uploaded. + #[inline] + pub fn buffer(&self) -> Option<&Buffer> { + self.buffer.as_ref() + } + + /// Returns the amount of space that the GPU will use before reallocating. + #[inline] + pub fn capacity(&self) -> usize { + self.capacity + } + + /// Returns the number of items that have been pushed to this buffer. + #[inline] + pub fn len(&self) -> usize { + self.data.len() / u64::from(T::min_size()) as usize + } + + /// Returns true if the buffer is empty. + #[inline] + pub fn is_empty(&self) -> bool { + self.data.is_empty() + } + + /// Adds a new value and returns its index. + pub fn push(&mut self, value: T) -> usize { + let element_size = u64::from(T::min_size()) as usize; + let offset = self.data.len(); + + // TODO: Consider using unsafe code to push uninitialized, to prevent + // the zeroing. It shows up in profiles. + self.data.extend(iter::repeat(0).take(element_size)); + + // Take a slice of the new data for `write_into` to use. This is + // important: it hoists the bounds check up here so that the compiler + // can eliminate all the bounds checks that `write_into` will emit. + let mut dest = &mut self.data[offset..(offset + element_size)]; + value.write_into(&mut Writer::new(&value, &mut dest, 0).unwrap()); + + offset / u64::from(T::min_size()) as usize + } + + /// Changes the debugging label of the buffer. + /// + /// The next time the buffer is updated (via [`reserve`]), Bevy will inform + /// the driver of the new label. + pub fn set_label(&mut self, label: Option<&str>) { + let label = label.map(str::to_string); + + if label != self.label { + self.label_changed = true; + } + + self.label = label; + } + + /// Returns the label. + pub fn get_label(&self) -> Option<&str> { + self.label.as_deref() + } + + /// Creates a [`Buffer`] on the [`RenderDevice`] with size + /// at least `std::mem::size_of::() * capacity`, unless such a buffer already exists. + /// + /// If a [`Buffer`] exists, but is too small, references to it will be discarded, + /// and a new [`Buffer`] will be created. Any previously created [`Buffer`]s + /// that are no longer referenced will be deleted by the [`RenderDevice`] + /// once it is done using them (typically 1-2 frames). + /// + /// In addition to any [`BufferUsages`] provided when + /// the `BufferVec` was created, the buffer on the [`RenderDevice`] + /// is marked as [`BufferUsages::COPY_DST`](BufferUsages). + pub fn reserve(&mut self, capacity: usize, device: &RenderDevice) { + if capacity <= self.capacity && !self.label_changed { + return; + } + + self.capacity = capacity; + let size = u64::from(T::min_size()) as usize * capacity; + self.buffer = Some(device.create_buffer(&wgpu::BufferDescriptor { + label: self.label.as_deref(), + size: size as BufferAddress, + usage: BufferUsages::COPY_DST | self.buffer_usage, + mapped_at_creation: false, + })); + self.label_changed = false; + } + + /// Queues writing of data from system RAM to VRAM using the [`RenderDevice`] + /// and the provided [`RenderQueue`]. + /// + /// Before queuing the write, a [`reserve`](BufferVec::reserve) operation is + /// executed. + pub fn write_buffer(&mut self, device: &RenderDevice, queue: &RenderQueue) { + if self.data.is_empty() { + return; + } + + self.reserve(self.data.len() / u64::from(T::min_size()) as usize, device); + + let Some(buffer) = &self.buffer else { return }; + queue.write_buffer(buffer, 0, &self.data); + } + + /// Reduces the length of the buffer. + pub fn truncate(&mut self, len: usize) { + self.data.truncate(u64::from(T::min_size()) as usize * len); + } + + /// Removes all elements from the buffer. + pub fn clear(&mut self) { + self.data.clear(); + } +} diff --git a/crates/bevy_render/src/render_resource/gpu_array_buffer.rs b/crates/bevy_render/src/render_resource/gpu_array_buffer.rs index 60d8cc68e400c..9765cfc627adc 100644 --- a/crates/bevy_render/src/render_resource/gpu_array_buffer.rs +++ b/crates/bevy_render/src/render_resource/gpu_array_buffer.rs @@ -1,6 +1,6 @@ use super::{ binding_types::{storage_buffer_read_only, uniform_buffer_sized}, - BindGroupLayoutEntryBuilder, StorageBuffer, + BindGroupLayoutEntryBuilder, BufferVec, }; use crate::{ render_resource::batched_uniform_buffer::BatchedUniformBuffer, @@ -10,7 +10,7 @@ use bevy_ecs::{prelude::Component, system::Resource}; use encase::{private::WriteInto, ShaderSize, ShaderType}; use nonmax::NonMaxU32; use std::marker::PhantomData; -use wgpu::BindingResource; +use wgpu::{BindingResource, BufferUsages}; /// Trait for types able to go in a [`GpuArrayBuffer`]. pub trait GpuArrayBufferable: ShaderType + ShaderSize + WriteInto + Clone {} @@ -18,21 +18,23 @@ impl GpuArrayBufferable for T {} /// Stores an array of elements to be transferred to the GPU and made accessible to shaders as a read-only array. /// -/// On platforms that support storage buffers, this is equivalent to [`StorageBuffer>`]. -/// Otherwise, this falls back to a dynamic offset uniform buffer with the largest -/// array of T that fits within a uniform buffer binding (within reasonable limits). +/// On platforms that support storage buffers, this is equivalent to +/// [`BufferVec`]. Otherwise, this falls back to a dynamic offset +/// uniform buffer with the largest array of T that fits within a uniform buffer +/// binding (within reasonable limits). /// /// Other options for storing GPU-accessible data are: /// * [`StorageBuffer`] /// * [`DynamicStorageBuffer`](crate::render_resource::DynamicStorageBuffer) /// * [`UniformBuffer`](crate::render_resource::UniformBuffer) /// * [`DynamicUniformBuffer`](crate::render_resource::DynamicUniformBuffer) +/// * [`RawBufferVec`](crate::render_resource::RawBufferVec) /// * [`BufferVec`](crate::render_resource::BufferVec) /// * [`Texture`](crate::render_resource::Texture) #[derive(Resource)] pub enum GpuArrayBuffer { Uniform(BatchedUniformBuffer), - Storage(StorageBuffer>), + Storage(BufferVec), } impl GpuArrayBuffer { @@ -41,14 +43,14 @@ impl GpuArrayBuffer { if limits.max_storage_buffers_per_shader_stage == 0 { GpuArrayBuffer::Uniform(BatchedUniformBuffer::new(&limits)) } else { - GpuArrayBuffer::Storage(StorageBuffer::default()) + GpuArrayBuffer::Storage(BufferVec::new(BufferUsages::STORAGE)) } } pub fn clear(&mut self) { match self { GpuArrayBuffer::Uniform(buffer) => buffer.clear(), - GpuArrayBuffer::Storage(buffer) => buffer.get_mut().clear(), + GpuArrayBuffer::Storage(buffer) => buffer.clear(), } } @@ -56,9 +58,7 @@ impl GpuArrayBuffer { match self { GpuArrayBuffer::Uniform(buffer) => buffer.push(value), GpuArrayBuffer::Storage(buffer) => { - let buffer = buffer.get_mut(); - let index = buffer.len() as u32; - buffer.push(value); + let index = buffer.push(value) as u32; GpuArrayBufferIndex { index, dynamic_offset: None, @@ -91,7 +91,9 @@ impl GpuArrayBuffer { pub fn binding(&self) -> Option { match self { GpuArrayBuffer::Uniform(buffer) => buffer.binding(), - GpuArrayBuffer::Storage(buffer) => buffer.binding(), + GpuArrayBuffer::Storage(buffer) => { + buffer.buffer().map(|buffer| buffer.as_entire_binding()) + } } } diff --git a/crates/bevy_render/src/render_resource/storage_buffer.rs b/crates/bevy_render/src/render_resource/storage_buffer.rs index 418e35cfbca8b..d30a002c88d4a 100644 --- a/crates/bevy_render/src/render_resource/storage_buffer.rs +++ b/crates/bevy_render/src/render_resource/storage_buffer.rs @@ -24,6 +24,8 @@ use wgpu::{util::BufferInitDescriptor, BindingResource, BufferBinding, BufferUsa /// * [`UniformBuffer`](crate::render_resource::UniformBuffer) /// * [`DynamicUniformBuffer`](crate::render_resource::DynamicUniformBuffer) /// * [`GpuArrayBuffer`](crate::render_resource::GpuArrayBuffer) +/// * [`RawBufferVec`](crate::render_resource::RawBufferVec) +/// * [`BufferVec`](crate::render_resource::BufferVec) /// * [`BufferVec`](crate::render_resource::BufferVec) /// * [`Texture`](crate::render_resource::Texture) /// @@ -154,6 +156,8 @@ impl StorageBuffer { /// * [`UniformBuffer`](crate::render_resource::UniformBuffer) /// * [`DynamicUniformBuffer`](crate::render_resource::DynamicUniformBuffer) /// * [`GpuArrayBuffer`](crate::render_resource::GpuArrayBuffer) +/// * [`RawBufferVec`](crate::render_resource::RawBufferVec) +/// * [`BufferVec`](crate::render_resource::BufferVec) /// * [`BufferVec`](crate::render_resource::BufferVec) /// * [`Texture`](crate::render_resource::Texture) /// diff --git a/crates/bevy_render/src/render_resource/uniform_buffer.rs b/crates/bevy_render/src/render_resource/uniform_buffer.rs index 72a673a503132..622702a41c473 100644 --- a/crates/bevy_render/src/render_resource/uniform_buffer.rs +++ b/crates/bevy_render/src/render_resource/uniform_buffer.rs @@ -31,6 +31,8 @@ use super::IntoBinding; /// * [`DynamicStorageBuffer`](crate::render_resource::DynamicStorageBuffer) /// * [`DynamicUniformBuffer`] /// * [`GpuArrayBuffer`](crate::render_resource::GpuArrayBuffer) +/// * [`RawBufferVec`](crate::render_resource::RawBufferVec) +/// * [`BufferVec`](crate::render_resource::BufferVec) /// * [`BufferVec`](crate::render_resource::BufferVec) /// * [`Texture`](crate::render_resource::Texture) /// @@ -168,6 +170,8 @@ impl<'a, T: ShaderType + WriteInto> IntoBinding<'a> for &'a UniformBuffer { /// * [`UniformBuffer`] /// * [`DynamicUniformBuffer`] /// * [`GpuArrayBuffer`](crate::render_resource::GpuArrayBuffer) +/// * [`RawBufferVec`](crate::render_resource::RawBufferVec) +/// * [`BufferVec`](crate::render_resource::BufferVec) /// * [`BufferVec`](crate::render_resource::BufferVec) /// * [`Texture`](crate::render_resource::Texture) /// diff --git a/crates/bevy_sprite/src/render/mod.rs b/crates/bevy_sprite/src/render/mod.rs index 2110c0ec0a591..3da4df3ace0c3 100644 --- a/crates/bevy_sprite/src/render/mod.rs +++ b/crates/bevy_sprite/src/render/mod.rs @@ -413,16 +413,16 @@ impl SpriteInstance { #[derive(Resource)] pub struct SpriteMeta { view_bind_group: Option, - sprite_index_buffer: BufferVec, - sprite_instance_buffer: BufferVec, + sprite_index_buffer: RawBufferVec, + sprite_instance_buffer: RawBufferVec, } impl Default for SpriteMeta { fn default() -> Self { Self { view_bind_group: None, - sprite_index_buffer: BufferVec::::new(BufferUsages::INDEX), - sprite_instance_buffer: BufferVec::::new(BufferUsages::VERTEX), + sprite_index_buffer: RawBufferVec::::new(BufferUsages::INDEX), + sprite_instance_buffer: RawBufferVec::::new(BufferUsages::VERTEX), } } } diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index 49b3fb1ad979a..4b4459382a6f4 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -829,16 +829,16 @@ struct UiVertex { #[derive(Resource)] pub struct UiMeta { - vertices: BufferVec, - indices: BufferVec, + vertices: RawBufferVec, + indices: RawBufferVec, view_bind_group: Option, } impl Default for UiMeta { fn default() -> Self { Self { - vertices: BufferVec::new(BufferUsages::VERTEX), - indices: BufferVec::new(BufferUsages::INDEX), + vertices: RawBufferVec::new(BufferUsages::VERTEX), + indices: RawBufferVec::new(BufferUsages::INDEX), view_bind_group: None, } } diff --git a/crates/bevy_ui/src/render/ui_material_pipeline.rs b/crates/bevy_ui/src/render/ui_material_pipeline.rs index 1245cf3012f7a..fac7e542568c0 100644 --- a/crates/bevy_ui/src/render/ui_material_pipeline.rs +++ b/crates/bevy_ui/src/render/ui_material_pipeline.rs @@ -97,7 +97,7 @@ where #[derive(Resource)] pub struct UiMaterialMeta { - vertices: BufferVec, + vertices: RawBufferVec, view_bind_group: Option, marker: PhantomData, } @@ -105,7 +105,7 @@ pub struct UiMaterialMeta { impl Default for UiMaterialMeta { fn default() -> Self { Self { - vertices: BufferVec::new(BufferUsages::VERTEX), + vertices: RawBufferVec::new(BufferUsages::VERTEX), view_bind_group: Default::default(), marker: PhantomData, }