use std::{num::NonZeroU64, ops::Range};
use bevy::{
ecs::{
lifecycle::Remove,
observer::On,
query::{With, Without},
system::{Commands, Query},
},
log::{error, trace},
prelude::{Component, Entity, ResMut, Resource},
render::{
render_resource::{
binding_types::storage_buffer, BindGroup, BindGroupEntries, BindGroupLayout,
BindGroupLayoutEntries, Buffer, ShaderSize as _, ShaderType,
},
renderer::{RenderDevice, RenderQueue},
},
};
use bytemuck::{Pod, Zeroable};
use thiserror::Error;
#[cfg(debug_assertions)]
use wgpu::util::BufferInitDescriptor;
#[cfg(not(debug_assertions))]
use wgpu::BufferDescriptor;
use wgpu::{BufferUsages, CommandEncoder, ShaderStages};
use super::{
aligned_buffer_vec::HybridAlignedBufferVec, effect_cache::SlabState, gpu_buffer::GpuBuffer,
BufferBindingSource, EffectBindGroups, GpuDispatchIndirectArgs,
};
use crate::{
render::{effect_cache::SlabId, ChildEffectOf},
ParticleLayout,
};
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct EventSlice {
slice: Range<u32>,
}
pub struct EventBuffer {
buffer: Buffer,
capacity: u32,
size: u32,
slices: Vec<EventSlice>,
}
impl EventBuffer {
pub fn new(buffer: Buffer, capacity: u32) -> Self {
Self {
buffer,
capacity,
size: 0,
slices: vec![],
}
}
pub fn buffer(&self) -> &Buffer {
&self.buffer
}
pub fn allocate(&mut self, size: u32) -> Option<EventSlice> {
if self.size + size > self.capacity {
return None;
}
if self.slices.is_empty() {
let slice = EventSlice { slice: 0..size };
self.slices.push(slice.clone());
self.size += size;
return Some(slice);
}
let mut start = 0;
for (idx, es) in self.slices.iter().enumerate() {
let avail_size = es.slice.start - start;
if size > avail_size {
start = es.slice.end;
continue;
}
let slice = EventSlice {
slice: start..start + size,
};
self.slices.insert(idx, slice.clone());
self.size += size;
return Some(slice);
}
if start + size <= self.capacity {
let slice = EventSlice {
slice: start..start + size,
};
self.slices.push(slice.clone());
self.size += size;
Some(slice)
} else {
None
}
}
pub fn free(&mut self, slice: &EventSlice) -> SlabState {
if let Some(idx) = self.slices.iter().position(|es| es == slice) {
self.slices.remove(idx);
}
if self.slices.is_empty() {
SlabState::Free
} else {
SlabState::Used
}
}
}
#[derive(Debug, PartialEq, Component)]
pub(crate) struct CachedParentInfo {
pub children: Vec<(Entity, BufferBindingSource)>,
pub byte_range: Range<u32>,
}
#[derive(Debug, Clone, PartialEq, Component)]
pub(crate) struct CachedChildInfo {
pub parent_slab_id: SlabId,
pub parent_slab_offset: u32,
pub parent_particle_layout: ParticleLayout,
pub parent_buffer_binding_source: BufferBindingSource,
pub local_child_index: u32,
pub global_child_index: u32,
pub init_indirect_dispatch_index: u32,
}
impl CachedChildInfo {
pub fn is_locally_equal(&self, other: &CachedChildInfo) -> bool {
self.parent_slab_id == other.parent_slab_id
&& self.parent_slab_offset == other.parent_slab_offset
&& self.parent_particle_layout == other.parent_particle_layout
&& self.parent_buffer_binding_source == other.parent_buffer_binding_source
&& self.local_child_index == other.local_child_index
&& self.init_indirect_dispatch_index == other.init_indirect_dispatch_index
}
}
#[repr(C)]
#[derive(Debug, Default, Clone, Copy, Pod, Zeroable, ShaderType)]
pub struct GpuChildInfo {
pub init_indirect_dispatch_index: u32,
pub event_count: i32,
}
#[derive(Debug, Clone, Component)]
pub struct CachedEffectEvents {
pub buffer_index: u32,
pub range: Range<u32>,
pub init_indirect_dispatch_index: u32,
}
impl CachedEffectEvents {
#[allow(dead_code)]
pub fn capacity(&self) -> u32 {
self.range.len() as u32
}
}
pub(crate) fn allocate_events(
mut commands: Commands,
mut event_cache: ResMut<EventCache>,
mut q_child_effects: Query<(Entity, Option<&mut CachedEffectEvents>), With<ChildEffectOf>>,
q_old_child_effects: Query<Entity, (With<CachedEffectEvents>, Without<ChildEffectOf>)>,
) {
#[cfg(feature = "trace")]
let _span = bevy::log::info_span!("allocate_events").entered();
trace!("allocate_events");
event_cache.clear_previous_frame_resizes();
for (entity, maybe_cached_events) in &mut q_child_effects {
if let Some(_cached_events) = maybe_cached_events {
} else {
const FIXME_HARD_CODED_EVENT_COUNT: u32 = 256;
let cached_effect_events = event_cache.allocate(FIXME_HARD_CODED_EVENT_COUNT);
commands.entity(entity).insert(cached_effect_events);
}
}
for entity in &q_old_child_effects {
commands.entity(entity).remove::<CachedEffectEvents>();
}
}
pub(crate) fn on_remove_cached_effect_events(
trigger: On<Remove, CachedEffectEvents>,
query: Query<(Entity, &CachedEffectEvents)>,
mut event_cache: ResMut<EventCache>,
) {
#[cfg(feature = "trace")]
let _span = bevy::log::info_span!("on_remove_cached_effect_events").entered();
trace!("on_remove_cached_effect_events");
if let Ok((entity, cached_effect_event)) = query.get(trigger.event().entity) {
if let Err(err) = event_cache.free(cached_effect_event) {
error!("Error while freeing cached events for effect {entity:?}: {err:?}");
}
};
}
#[derive(Debug, Error)]
pub enum CachedEventsError {
#[error("Invalid buffer index #{0}.")]
InvalidBufferIndex(u32),
#[error("Buffer at index #{0} was deallocated.")]
BufferDeallocated(u32),
}
#[derive(Resource)]
pub struct EventCache {
device: RenderDevice,
child_infos_buffer: HybridAlignedBufferVec,
buffers: Vec<Option<EventBuffer>>,
init_indirect_dispatch_buffer: GpuBuffer<GpuDispatchIndirectArgs>,
indirect_child_info_buffer_bind_group_layout: BindGroupLayout,
indirect_child_info_buffer_bind_group: Option<BindGroup>,
}
impl EventCache {
pub fn new(device: RenderDevice) -> Self {
let init_indirect_dispatch_buffer = GpuBuffer::new(
BufferUsages::STORAGE | BufferUsages::INDIRECT,
Some("hanabi:buffer:init_indirect_dispatch".to_string()),
);
let child_infos_bind_group_layout = device.create_bind_group_layout(
"hanabi:bgl:indirect:child_infos@3",
&BindGroupLayoutEntries::single(
ShaderStages::COMPUTE,
storage_buffer::<GpuChildInfo>(false),
),
);
Self {
device,
child_infos_buffer: HybridAlignedBufferVec::new(
BufferUsages::STORAGE,
NonZeroU64::new(4).unwrap(),
Some("hanabi:buffer:child_infos".to_string()),
),
buffers: vec![],
init_indirect_dispatch_buffer,
indirect_child_info_buffer_bind_group_layout: child_infos_bind_group_layout,
indirect_child_info_buffer_bind_group: None,
}
}
#[allow(dead_code)]
#[inline]
pub fn buffers(&self) -> &[Option<EventBuffer>] {
&self.buffers
}
#[allow(dead_code)]
#[inline]
pub fn buffers_mut(&mut self) -> &mut [Option<EventBuffer>] {
&mut self.buffers
}
#[inline]
pub fn get_buffer(&self, index: u32) -> Option<&Buffer> {
self.buffers
.get(index as usize)
.and_then(|opt_eb| opt_eb.as_ref().map(|eb| eb.buffer()))
}
#[inline]
pub fn child_infos_buffer(&self) -> Option<&Buffer> {
self.child_infos_buffer.buffer()
}
pub fn allocate(&mut self, num_events: u32) -> CachedEffectEvents {
assert!(num_events > 0);
let init_indirect_dispatch_index = self.init_indirect_dispatch_buffer.allocate();
let mut empty_index = None;
for (buffer_index, buffer) in self.buffers.iter_mut().enumerate() {
let Some(buffer) = buffer.as_mut() else {
if empty_index.is_none() {
empty_index = Some(buffer_index);
}
continue;
};
if let Some(event_slice) = buffer.allocate(num_events) {
trace!("Allocate new slice in event buffer #{buffer_index} for {num_events} events: range={event_slice:?}");
return CachedEffectEvents {
buffer_index: buffer_index as u32,
init_indirect_dispatch_index,
range: event_slice.slice,
};
}
}
let buffer_index = empty_index.unwrap_or(self.buffers.len());
let label = format!("hanabi:buffer:event_buffer{buffer_index}");
let align = self.device.limits().min_storage_buffer_offset_alignment;
let capacity = num_events.max(16 * 1024); let byte_size = (capacity as u64 * 4).next_multiple_of(align as u64);
let capacity = (byte_size / 4) as u32;
#[cfg(debug_assertions)]
let buffer = {
let mut contents: Vec<u32> = Vec::with_capacity(capacity as usize);
contents.resize(capacity as usize, 0xDEADBEEF);
self.device.create_buffer_with_data(&BufferInitDescriptor {
label: Some(&label[..]),
usage: BufferUsages::COPY_DST | BufferUsages::STORAGE,
contents: bytemuck::cast_slice(contents.as_slice()),
})
};
#[cfg(not(debug_assertions))]
let buffer = self.device.create_buffer(&BufferDescriptor {
label: Some(&label[..]),
size: byte_size,
usage: BufferUsages::COPY_DST | BufferUsages::STORAGE,
mapped_at_creation: false,
});
trace!("Created new event buffer #{buffer_index} '{label}' with {byte_size} bytes ({capacity} events; align={align}B)");
let mut buffer = EventBuffer::new(buffer, capacity);
let event_slice = buffer.allocate(num_events).expect("Failed to allocate event slice inside new buffer specifically created for this allocation.");
trace!("Allocate new slice in event buffer #{buffer_index} for {num_events} events: range={event_slice:?}");
if buffer_index >= self.buffers.len() {
self.buffers.push(Some(buffer));
} else {
debug_assert!(self.buffers[buffer_index].is_none());
self.buffers[buffer_index] = Some(buffer);
}
CachedEffectEvents {
buffer_index: buffer_index as u32,
init_indirect_dispatch_index,
range: event_slice.slice,
}
}
pub fn free(
&mut self,
cached_effect_events: &CachedEffectEvents,
) -> Result<SlabState, CachedEventsError> {
trace!(
"Removing cached event {:?} from cache.",
cached_effect_events
);
self.init_indirect_dispatch_buffer
.free(cached_effect_events.init_indirect_dispatch_index);
let entry = self
.buffers
.get_mut(cached_effect_events.buffer_index as usize)
.ok_or(CachedEventsError::InvalidBufferIndex(
cached_effect_events.buffer_index,
))?;
let buffer = entry.as_mut().ok_or(CachedEventsError::BufferDeallocated(
cached_effect_events.buffer_index,
))?;
if buffer.free(&EventSlice {
slice: cached_effect_events.range.clone(),
}) == SlabState::Free
{
let buffer = entry.take().unwrap();
buffer.buffer.destroy();
Ok(SlabState::Free)
} else {
Ok(SlabState::Used)
}
}
pub fn allocate_child_infos(
&mut self,
parent_entity: Entity,
children: Vec<(Entity, BufferBindingSource)>,
child_infos: &[GpuChildInfo],
) -> CachedParentInfo {
assert_eq!(children.len(), child_infos.len());
assert!(!children.is_empty());
let byte_range = self.child_infos_buffer.push_many(child_infos);
assert_eq!(byte_range.start as usize % size_of::<GpuChildInfo>(), 0);
trace!(
"Parent {:?}: newly allocated ChildInfo[] array at +{}",
parent_entity,
byte_range.start
);
CachedParentInfo {
children,
byte_range,
}
}
pub fn reallocate_child_infos(
&mut self,
parent_entity: Entity,
children: Vec<(Entity, BufferBindingSource)>,
child_infos: &[GpuChildInfo],
cached_parent_info: &mut CachedParentInfo,
) {
trace!(
"Parent {:?}: De-allocating old ChildInfo[] entry at range {:?}",
parent_entity,
cached_parent_info.byte_range
);
self.child_infos_buffer
.remove(cached_parent_info.byte_range.clone());
let byte_range = self.child_infos_buffer.push_many(child_infos);
assert_eq!(
byte_range.start as usize % GpuChildInfo::SHADER_SIZE.get() as usize,
0
);
trace!(
"Parent {:?}: Allocated new ChildInfo[] entry at byte range {:?}",
parent_entity,
byte_range
);
cached_parent_info.children = children;
cached_parent_info.byte_range = byte_range;
}
pub fn prepare_buffers(
&mut self,
render_device: &RenderDevice,
render_queue: &RenderQueue,
_effect_bind_groups: &mut ResMut<EffectBindGroups>,
) {
self.init_indirect_dispatch_buffer
.prepare_buffers(render_device);
self.child_infos_buffer
.write_buffer(render_device, render_queue);
}
#[inline]
pub fn write_buffers(&self, command_encoder: &mut CommandEncoder) {
self.init_indirect_dispatch_buffer
.write_buffers(command_encoder);
}
#[inline]
pub fn clear_previous_frame_resizes(&mut self) {
self.init_indirect_dispatch_buffer
.clear_previous_frame_resizes();
}
#[inline]
pub fn init_indirect_dispatch_buffer(&self) -> Option<&Buffer> {
self.init_indirect_dispatch_buffer.buffer()
}
#[inline]
pub fn child_infos(&self) -> &HybridAlignedBufferVec {
&self.child_infos_buffer
}
pub fn ensure_indirect_child_info_buffer_bind_group(
&mut self,
device: &RenderDevice,
) -> Option<&BindGroup> {
let buffer = self.child_infos_buffer()?;
self.indirect_child_info_buffer_bind_group = Some(device.create_bind_group(
"hanabi:bg:indirect:child_infos@3",
&self.indirect_child_info_buffer_bind_group_layout,
&BindGroupEntries::single(buffer.as_entire_binding()),
));
self.indirect_child_info_buffer_bind_group.as_ref()
}
pub fn indirect_child_info_buffer_bind_group(&self) -> Option<&BindGroup> {
self.indirect_child_info_buffer_bind_group.as_ref()
}
}