diff --git a/CHANGELOG.md b/CHANGELOG.md index e10a5bd3..0c959ae5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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_ diff --git a/src/render/gpu_buffer.rs b/src/render/gpu_buffer.rs index d7daa5d8..39c802d0 100644 --- a/src/render/gpu_buffer.rs +++ b/src/render/gpu_buffer.rs @@ -114,9 +114,9 @@ impl GpuBuffer { /// [`ShaderType::assert_uniform_compat()`]. /// /// [`BufferUsages::UNIFORM`]: bevy::render::render_resource::BufferUsages::UNIFORM - pub fn new_allocated(buffer: Buffer, size: u32, label: Option) -> Self { - // GPU-aligned item size, compatible with WGSL rules - let item_size = ::SHADER_SIZE.get() as u32; + pub fn new_allocated(buffer: Buffer, label: Option) -> Self { + // GPU-aligned item size, compatible with WGSL rules. + let item_size = ::SHADER_SIZE.get(); let buffer_usage = buffer.usage(); assert!( buffer_usage.contains(BufferUsages::COPY_SRC | BufferUsages::COPY_DST), @@ -125,7 +125,16 @@ impl GpuBuffer { if buffer_usage.contains(BufferUsages::UNIFORM) { ::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, diff --git a/src/render/sort.rs b/src/render/sort.rs index 03c77a80..1a718809 100644 --- a/src/render/sort.rs +++ b/src/render/sort.rs @@ -12,7 +12,7 @@ use bevy::{ }, BindGroup, BindGroupEntries, BindGroupLayoutDescriptor, BindGroupLayoutEntries, Buffer, BufferId, CachedComputePipelineId, CachedPipelineState, ComputePipelineDescriptor, - PipelineCache, ShaderType, + PipelineCache, ShaderSize, ShaderType, }, renderer::RenderDevice, }, @@ -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 @@ -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()), );