Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Removed the `typetag` dependency.
- Removed the `serde` feature. Serialization is always available.

### Fixed

- Fixed the sort indirect-dispatch buffer never growing past its initial 256 entries, causing
an indirect buffer overrun and crash with many sorted (e.g. ribbon) effects alive at once. (#493)

## [0.18.0] 2026-02-01

_This version is compatible with Bevy 0.18_
Expand Down
17 changes: 13 additions & 4 deletions src/render/gpu_buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,9 @@ impl<T: Pod + ShaderType + ShaderSize> GpuBuffer<T> {
/// [`ShaderType::assert_uniform_compat()`].
///
/// [`BufferUsages::UNIFORM`]: bevy::render::render_resource::BufferUsages::UNIFORM
pub fn new_allocated(buffer: Buffer, size: u32, label: Option<String>) -> Self {
// GPU-aligned item size, compatible with WGSL rules
let item_size = <T as ShaderSize>::SHADER_SIZE.get() as u32;
pub fn new_allocated(buffer: Buffer, label: Option<String>) -> Self {
// GPU-aligned item size, compatible with WGSL rules.
let item_size = <T as ShaderSize>::SHADER_SIZE.get();
let buffer_usage = buffer.usage();
assert!(
buffer_usage.contains(BufferUsages::COPY_SRC | BufferUsages::COPY_DST),
Expand All @@ -125,7 +125,16 @@ impl<T: Pod + ShaderType + ShaderSize> GpuBuffer<T> {
if buffer_usage.contains(BufferUsages::UNIFORM) {
<T as ShaderType>::assert_uniform_compat();
}
trace!("GpuBuffer: item_size={}", item_size);
// Capacity is derived from the physical buffer so the two can't disagree.
debug_assert_eq!(
buffer.size() % item_size,
0,
"GpuBuffer physical size ({} bytes) is not a multiple of the element size ({} bytes)",
buffer.size(),
item_size,
);
let size = (buffer.size() / item_size) as u32;
trace!("GpuBuffer: item_size={item_size} capacity={size}");
Self {
buffer: Some(BufferAndSize { buffer, size }),
buffer_usage,
Expand Down
8 changes: 4 additions & 4 deletions src/render/sort.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use bevy::{
},
BindGroup, BindGroupEntries, BindGroupLayoutDescriptor, BindGroupLayoutEntries, Buffer,
BufferId, CachedComputePipelineId, CachedPipelineState, ComputePipelineDescriptor,
PipelineCache, ShaderType,
PipelineCache, ShaderSize, ShaderType,
},
renderer::RenderDevice,
},
Expand Down Expand Up @@ -127,10 +127,11 @@ impl SortBindGroups {
mapped_at_creation: false,
});

let indirect_buffer_size = 3 * 1024;
// Initial room for 256 ribbon sort dispatches; the GpuBuffer grows on demand.
let indirect_item_size = GpuDispatchIndirectArgs::SHADER_SIZE.get();
let indirect_buffer = render_device.create_buffer(&BufferDescriptor {
label: Some("hanabi:buffer:sort:indirect"),
size: indirect_buffer_size,
size: 256 * indirect_item_size,
usage: BufferUsages::COPY_SRC
| BufferUsages::COPY_DST
| BufferUsages::STORAGE
Expand All @@ -139,7 +140,6 @@ impl SortBindGroups {
});
let indirect_buffer = GpuBuffer::new_allocated(
indirect_buffer,
indirect_buffer_size as u32,
Some("hanabi:buffer:sort:indirect".to_string()),
);

Expand Down
Loading