1use alloc::borrow::Cow;
5
6use crate::{
7 render_asset::{AssetExtractionError, PrepareAssetError, RenderAsset, RenderAssetPlugin},
8 render_resource::{Buffer, BufferUsages},
9 renderer::{RenderDevice, RenderQueue},
10 Render, RenderApp, RenderSystems,
11};
12use bevy_app::{App, Plugin};
13use bevy_asset::{Asset, AssetApp, AssetId, RenderAssetUsages};
14use bevy_derive::{Deref, DerefMut};
15use bevy_ecs::{
16 resource::Resource,
17 schedule::IntoScheduleConfigs as _,
18 system::{
19 lifetimeless::{SRes, SResMut},
20 ResMut, SystemParamItem,
21 },
22};
23use bevy_platform::collections::{AlignedVec, HashSet};
24use bevy_reflect::{prelude::ReflectDefault, Reflect};
25use bevy_utils::default;
26use wgpu_types::BufferDescriptor;
27
28#[derive(Default)]
31pub struct StoragePlugin;
32
33impl Plugin for StoragePlugin {
34 fn build(&self, app: &mut App) {
35 app.add_plugins(RenderAssetPlugin::<GpuShaderBuffer>::default())
36 .init_asset::<ShaderBuffer>()
37 .register_asset_reflect::<ShaderBuffer>();
38
39 let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
40 return;
41 };
42 render_app
43 .init_resource::<RenderChangedShaderBuffers>()
44 .add_systems(
45 Render,
46 clear_changed_shader_buffers.in_set(RenderSystems::Cleanup),
47 );
48 }
49}
50
51#[derive(Asset, Reflect, Debug, Clone)]
58#[reflect(opaque)]
59#[reflect(Default, Debug, Clone)]
60pub struct ShaderBuffer {
61 pub data: ShaderBufferData,
63 pub label: Cow<'static, str>,
65 pub buffer_usage: BufferUsages,
67 pub asset_usage: RenderAssetUsages,
69 pub copy_on_resize: bool,
75}
76
77#[derive(Reflect, Debug, Clone)]
83#[reflect(Default, Debug, Clone)]
84#[reflect(opaque)]
85pub enum ShaderBufferData {
86 Uninitialized(wgpu_types::BufferAddress),
89 Initialized {
93 data: AlignedVec,
94 buffer_size: wgpu_types::BufferAddress,
95 },
96}
97
98impl Default for ShaderBuffer {
99 fn default() -> Self {
100 Self {
101 data: ShaderBufferData::Uninitialized(0),
102 label: Cow::Borrowed("shader buffer"),
103 buffer_usage: BufferUsages::STORAGE | BufferUsages::COPY_SRC | BufferUsages::COPY_DST,
104 asset_usage: RenderAssetUsages::default(),
105 copy_on_resize: false,
106 }
107 }
108}
109
110impl Default for ShaderBufferData {
111 fn default() -> Self {
112 ShaderBufferData::Uninitialized(0)
113 }
114}
115
116impl ShaderBuffer {
117 pub fn new<T: bytemuck::NoUninit>(data: Vec<T>, asset_usage: RenderAssetUsages) -> Self {
119 let data = AlignedVec::from(data);
120 let buffer_size = data.len() as u64;
121 ShaderBuffer {
122 data: ShaderBufferData::Initialized { data, buffer_size },
123 asset_usage,
124 ..default()
125 }
126 }
127
128 pub fn with_size(size: u64, asset_usage: RenderAssetUsages) -> Self {
130 ShaderBuffer {
131 data: ShaderBufferData::Uninitialized(size),
132 asset_usage,
133 ..default()
134 }
135 }
136
137 pub fn clear(&mut self) {
139 if let ShaderBufferData::Initialized { data, .. } = &mut self.data {
140 data.clear();
141 }
142 }
143
144 pub fn extend_from_slice<T>(&mut self, values: &[T])
149 where
150 T: bytemuck::NoUninit,
151 {
152 let data = core::mem::take(&mut self.data);
153 let mut data = match data {
154 ShaderBufferData::Uninitialized(_) => AlignedVec::new(align_of::<T>()),
155 ShaderBufferData::Initialized { data, .. } => data,
156 };
157 data.extend_from_slice(bytemuck::cast_slice(values));
158 let buffer_size = data.len() as u64;
159 self.data = ShaderBufferData::Initialized { data, buffer_size };
160 }
161
162 pub fn extend<T>(&mut self, values: impl IntoIterator<Item = T>)
167 where
168 T: bytemuck::NoUninit,
169 {
170 let values = values.into_iter();
171 let data = core::mem::take(&mut self.data);
172 let mut data = match data {
173 ShaderBufferData::Uninitialized(_) => AlignedVec::new(align_of::<T>()),
174 ShaderBufferData::Initialized { data, .. } => data,
175 };
176 data.reserve(values.size_hint().0 * size_of::<T>());
177 for value in values {
178 data.extend_from_slice(bytemuck::bytes_of(&value));
179 }
180 let buffer_size = data.len() as u64;
181 self.data = ShaderBufferData::Initialized { data, buffer_size };
182 }
183
184 pub fn cast_slice<T: bytemuck::AnyBitPattern>(&self) -> Option<&[T]> {
191 match &self.data {
192 ShaderBufferData::Uninitialized(_) => None,
193 ShaderBufferData::Initialized { data, .. } => Some(data.cast_slice()),
194 }
195 }
196
197 pub fn cast_slice_mut<T: bytemuck::NoUninit + bytemuck::AnyBitPattern>(
204 &mut self,
205 ) -> Option<&mut [T]> {
206 match &mut self.data {
207 ShaderBufferData::Uninitialized(_) => None,
208 ShaderBufferData::Initialized { data, .. } => Some(data.cast_slice_mut()),
209 }
210 }
211
212 pub fn resize(&mut self, new_size: wgpu_types::BufferAddress) {
222 match self.data {
223 ShaderBufferData::Initialized {
224 ref mut data,
225 ref mut buffer_size,
226 } => {
227 data.resize(new_size as usize, 0);
228 *buffer_size = new_size;
229 }
230 ShaderBufferData::Uninitialized(ref mut size) => {
231 *size = new_size;
232 }
233 }
234 }
235
236 pub fn resize_buffer(&mut self, new_size: wgpu_types::BufferAddress) {
246 match self.data {
247 ShaderBufferData::Initialized {
248 ref mut buffer_size,
249 ..
250 } => {
251 *buffer_size = new_size;
252 }
253 ShaderBufferData::Uninitialized(ref mut size) => {
254 *size = new_size;
255 }
256 }
257 }
258
259 pub fn buffer_size(&self) -> wgpu_types::BufferAddress {
261 match self.data {
262 ShaderBufferData::Initialized { buffer_size, .. } => buffer_size,
263 ShaderBufferData::Uninitialized(len) => len,
264 }
265 }
266}
267
268impl<T: bytemuck::NoUninit> From<Vec<T>> for ShaderBuffer {
269 fn from(value: Vec<T>) -> Self {
271 Self::new(value, Default::default())
272 }
273}
274
275#[derive(Resource, Default, Deref, DerefMut)]
290pub struct RenderChangedShaderBuffers(pub HashSet<AssetId<ShaderBuffer>>);
291
292pub struct GpuShaderBuffer {
294 pub buffer: Buffer,
296 pub label: Cow<'static, str>,
298 pub buffer_usage: BufferUsages,
300 pub had_data: bool,
302}
303
304impl RenderAsset for GpuShaderBuffer {
305 type SourceAsset = ShaderBuffer;
306 type Param = (
307 SRes<RenderDevice>,
308 SRes<RenderQueue>,
309 SResMut<RenderChangedShaderBuffers>,
310 );
311
312 fn asset_usage(source_asset: &Self::SourceAsset) -> RenderAssetUsages {
313 source_asset.asset_usage
314 }
315
316 fn take_gpu_data(
317 source: &mut Self::SourceAsset,
318 previous_gpu_asset: Option<&Self>,
319 ) -> Result<Self::SourceAsset, AssetExtractionError> {
320 let len = source.buffer_size();
321 let data = core::mem::replace(&mut source.data, ShaderBufferData::Uninitialized(len));
322
323 let valid_upload = matches!(data, ShaderBufferData::Initialized { .. })
324 || previous_gpu_asset.is_none_or(|prev| !prev.had_data);
325
326 valid_upload
327 .then(|| Self::SourceAsset {
328 data,
329 ..source.clone()
330 })
331 .ok_or(AssetExtractionError::AlreadyExtracted)
332 }
333
334 fn prepare_asset(
335 source_asset: Self::SourceAsset,
336 asset_id: AssetId<Self::SourceAsset>,
337 &mut (
338 ref render_device,
339 ref render_queue,
340 ref mut changed_shader_buffers,
341 ): &mut SystemParamItem<Self::Param>,
342 previous_asset: Option<&Self>,
343 ) -> Result<Self, PrepareAssetError<Self::SourceAsset>> {
344 let had_data = matches!(source_asset.data, ShaderBufferData::Initialized { .. });
345
346 let buffer = if let Some(prev) = previous_asset
347 && prev.buffer.size() == source_asset.buffer_size()
348 && prev.buffer.usage() == source_asset.buffer_usage
349 && *prev.label == *source_asset.label
350 && (!had_data || source_asset.buffer_usage.contains(BufferUsages::COPY_DST))
351 {
352 if let ShaderBufferData::Initialized { ref data, .. } = source_asset.data {
353 render_queue.write_buffer(
354 &prev.buffer,
355 0,
356 &data[..((source_asset.buffer_size() as usize).min(data.len()))],
357 );
358 }
359 prev.buffer.clone()
360 } else if let ShaderBufferData::Initialized { data, buffer_size } = source_asset.data {
361 changed_shader_buffers.insert(asset_id);
362 let desc = BufferDescriptor {
363 label: Some(&*source_asset.label),
364 usage: source_asset.buffer_usage,
365 size: buffer_size,
366 mapped_at_creation: buffer_size != 0,
367 };
368
369 let buffer = render_device.create_buffer(&desc);
370
371 if buffer_size != 0 {
373 let upload_len = (buffer_size as usize).min(data.len());
377 buffer
378 .get_mapped_range_mut(..upload_len as u64)
379 .unwrap()
380 .copy_from_slice(&data[..upload_len]);
381 buffer.unmap();
382 }
383 buffer
384 } else {
385 changed_shader_buffers.insert(asset_id);
386 let new_buffer = render_device.create_buffer(&BufferDescriptor {
387 label: Some(&*source_asset.label),
388 size: source_asset.buffer_size(),
389 usage: source_asset.buffer_usage,
390 mapped_at_creation: false,
391 });
392 if source_asset.copy_on_resize
393 && let Some(previous) = previous_asset
394 && previous.buffer.usage().contains(BufferUsages::COPY_SRC)
395 && source_asset.buffer_usage.contains(BufferUsages::COPY_DST)
396 {
397 let copy_size = source_asset.buffer_size().min(previous.buffer.size());
398 let mut encoder =
399 render_device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
400 label: Some("copy_buffer_on_resize"),
401 });
402 encoder.copy_buffer_to_buffer(&previous.buffer, 0, &new_buffer, 0, copy_size);
403 render_queue.submit([encoder.finish()]);
404 }
405 new_buffer
406 };
407
408 Ok(GpuShaderBuffer {
409 buffer,
410 label: source_asset.label,
411 buffer_usage: source_asset.buffer_usage,
412 had_data,
413 })
414 }
415}
416
417fn clear_changed_shader_buffers(mut changed_shader_buffers: ResMut<RenderChangedShaderBuffers>) {
420 changed_shader_buffers.clear();
421}
422
423#[cfg(test)]
424mod tests {
425 use super::*;
426 use alloc::vec;
427
428 use bevy_ecs::{
429 system::{lifetimeless::SRes, SystemState},
430 world::World,
431 };
432
433 use bevy_asset::Assets;
434
435 use crate::test_utils::create_dummy_device;
436
437 fn extract(
440 source: &mut ShaderBuffer,
441 previous_gpu_asset: Option<&GpuShaderBuffer>,
442 ) -> ShaderBuffer {
443 GpuShaderBuffer::take_gpu_data(source, previous_gpu_asset)
444 .expect("shader buffer should be extractable")
445 }
446
447 fn prepare(
456 asset_id: AssetId<ShaderBuffer>,
457 extracted: ShaderBuffer,
458 previous_asset: Option<&GpuShaderBuffer>,
459 device: &RenderDevice,
460 queue: &RenderQueue,
461 ) -> (GpuShaderBuffer, RenderChangedShaderBuffers) {
462 let mut world = World::new();
463 world.insert_resource(device.clone());
464 world.insert_resource(queue.clone());
465 world.insert_resource(RenderChangedShaderBuffers::default());
466 let mut system_state = SystemState::<(
467 SRes<RenderDevice>,
468 SRes<RenderQueue>,
469 SResMut<RenderChangedShaderBuffers>,
470 )>::new(&mut world);
471 let gpu_buffer = {
472 let mut params = system_state.get_mut(&mut world).expect(
473 "RenderDevice, RenderQueue and RenderChangedShaderBuffers resources should be present",
474 );
475 GpuShaderBuffer::prepare_asset(extracted, asset_id, &mut params, previous_asset)
476 .expect("shader buffer should be prepared successfully")
477 };
478 let changed_buffer = world
479 .remove_resource::<RenderChangedShaderBuffers>()
480 .expect("RenderChangedShaderBuffers resource should be present");
481 (gpu_buffer, changed_buffer)
482 }
483
484 fn extract_and_prepare(
487 source: &mut ShaderBuffer,
488 previous_asset: Option<&GpuShaderBuffer>,
489 device: &RenderDevice,
490 queue: &RenderQueue,
491 ) -> GpuShaderBuffer {
492 let extracted = extract(source, previous_asset);
493 let mut assets = Assets::<ShaderBuffer>::default();
494 let asset_id = assets.add(extracted.clone()).id();
495 prepare(asset_id, extracted, previous_asset, device, queue).0
496 }
497
498 fn extract_and_prepare_on_new_device(
501 source: &mut ShaderBuffer,
502 previous_asset: Option<&GpuShaderBuffer>,
503 ) -> GpuShaderBuffer {
504 let (device, queue) = create_dummy_device();
505 extract_and_prepare(source, previous_asset, &device, &queue)
506 }
507
508 #[test]
512 fn extract_and_create_gpu_buffer_from_data() {
513 let mut source = ShaderBuffer::new(
514 vec![1u32, 2, 3],
515 RenderAssetUsages::MAIN_WORLD | RenderAssetUsages::RENDER_WORLD,
516 );
517 let size = 3 * size_of::<u32>() as u64;
518
519 assert_eq!(source.buffer_size(), size);
521 assert_eq!(source.cast_slice::<u32>(), Some(&[1, 2, 3][..]));
522
523 let gpu = extract_and_prepare_on_new_device(&mut source, None);
524
525 assert!(matches!(source.data, ShaderBufferData::Uninitialized(s) if s == size));
528
529 assert_eq!(gpu.buffer.size(), size);
531 assert!(gpu.had_data);
532 assert_eq!(gpu.label, source.label);
533 assert_eq!(gpu.buffer_usage, source.buffer_usage);
534 }
535
536 #[test]
540 fn create_gpu_buffer_with_buffer_size_larger_than_data() {
541 let mut source = ShaderBuffer::new(vec![1u32], RenderAssetUsages::default());
542 source.resize_buffer(64);
544 assert_eq!(source.buffer_size(), 64);
545 assert_eq!(source.cast_slice::<u32>(), Some(&[1][..]));
546
547 let gpu = extract_and_prepare_on_new_device(&mut source, None);
548
549 assert_eq!(gpu.buffer.size(), 64);
550 assert!(gpu.had_data);
551 }
552
553 #[test]
557 fn create_gpu_buffer_with_buffer_size_smaller_than_data() {
558 let mut source = ShaderBuffer::new(vec![1u32, 2, 3, 4], RenderAssetUsages::default());
559 source.resize_buffer(8);
560 assert_eq!(source.buffer_size(), 8);
561
562 let gpu = extract_and_prepare_on_new_device(&mut source, None);
563
564 assert_eq!(gpu.buffer.size(), 8);
565 assert!(gpu.had_data);
566 }
567
568 #[test]
571 fn create_zero_sized_gpu_buffer() {
572 let mut source = ShaderBuffer::new(Vec::<u32>::new(), RenderAssetUsages::default());
574 assert_eq!(source.buffer_size(), 0);
575
576 let gpu = extract_and_prepare_on_new_device(&mut source, None);
577 assert_eq!(gpu.buffer.size(), 0);
578 assert!(gpu.had_data);
579
580 let mut source = ShaderBuffer::with_size(0, RenderAssetUsages::default());
582 let gpu = extract_and_prepare_on_new_device(&mut source, None);
583 assert_eq!(gpu.buffer.size(), 0);
584 assert!(!gpu.had_data);
585 }
586
587 #[test]
590 fn create_uninitialized_gpu_buffer() {
591 let mut source = ShaderBuffer::with_size(1024, RenderAssetUsages::default());
592 assert_eq!(source.buffer_size(), 1024);
593
594 let gpu = extract_and_prepare_on_new_device(&mut source, None);
595
596 assert_eq!(gpu.buffer.size(), 1024);
597 assert!(!gpu.had_data);
598 }
599
600 #[test]
605 fn extraction_rejects_buffer_whose_data_would_be_lost() {
606 let mut source = ShaderBuffer::new(vec![1u32], RenderAssetUsages::default());
607 assert!(GpuShaderBuffer::take_gpu_data(&mut source, None).is_ok());
608 assert!(matches!(source.data, ShaderBufferData::Uninitialized(4)));
609
610 assert!(GpuShaderBuffer::take_gpu_data(&mut source, None).is_ok());
613
614 let mut source = ShaderBuffer::with_size(4, RenderAssetUsages::default());
617 let previous = GpuShaderBuffer {
618 buffer: create_dummy_device().0.create_buffer(&BufferDescriptor {
619 label: Some("previous"),
620 size: 4,
621 usage: BufferUsages::STORAGE,
622 mapped_at_creation: false,
623 }),
624 label: Cow::Borrowed("shader buffer"),
625 buffer_usage: BufferUsages::STORAGE,
626 had_data: true,
627 };
628 assert!(matches!(
629 GpuShaderBuffer::take_gpu_data(&mut source, Some(&previous)),
630 Err(AssetExtractionError::AlreadyExtracted)
631 ));
632 }
633
634 #[test]
643 fn reuses_gpu_buffer_when_unchanged() {
644 let (device, queue) = create_dummy_device();
645
646 let mut assets = Assets::<ShaderBuffer>::default();
647 let handle = assets.add(ShaderBuffer::new(
648 vec![1u32, 2, 3],
649 RenderAssetUsages::default(),
650 ));
651 let asset_id = handle.id();
652
653 let extracted = extract(&mut assets.get_mut(asset_id).unwrap(), None);
655 let (first, changed) = prepare(asset_id, extracted, None, &device, &queue);
656 assert!(changed.contains(&asset_id));
657
658 assets.get_mut(asset_id).unwrap().extend([4u32, 5, 6]);
661 let extracted = extract(&mut assets.get_mut(asset_id).unwrap(), Some(&first));
662 let (second, changed) = prepare(asset_id, extracted, Some(&first), &device, &queue);
663 assert_eq!(second.buffer.id(), first.buffer.id());
664 assert!(changed.is_empty());
665
666 assets.get_mut(asset_id).unwrap().extend([7u32]);
668 let extracted = extract(&mut assets.get_mut(asset_id).unwrap(), Some(&first));
669 let (resized, changed) = prepare(asset_id, extracted, Some(&first), &device, &queue);
670 assert_ne!(resized.buffer.id(), first.buffer.id());
671 assert_eq!(resized.buffer.size(), 4);
672 assert!(changed.contains(&asset_id));
673 }
674}