Grow the sort indirect-dispatch buffer instead of overrunning a fixed 256 (closes #493)#538
Merged
djeedai merged 3 commits intoJun 20, 2026
Conversation
The sort indirect-dispatch buffer is a growable GpuBuffer whose capacity is
tracked in elements: allocate() may return an index past capacity, and
prepare_buffers() then grows the GPU buffer to fit. But GpuBuffer::new_allocated
took the element capacity as a separate argument, and SortBindGroups::new passed
the buffer's byte size (3072) there instead of its element count. With 12-byte
GpuDispatchIndirectArgs the physical buffer held 256 elements, while GpuBuffer
believed it had 3072 elements of headroom, so prepare_buffers() never grew it.
Once a ribbon-heavy scene needed a 257th sort dispatch, the indirect dispatch
read past the end:
dispatch indirect: buffer uses bytes 3072..3084 which overruns indirect
buffer of size 3072
(~260 trailed projectiles, matching the issue report.)
Remove the separate capacity argument from new_allocated and derive the element
capacity from the physical buffer (buffer.size() / item_size), so the recorded
and physical capacities cannot disagree. new_allocated has a single caller,
which now just sizes the buffer; a debug_assert guards a non-multiple size.
Closes djeedai#493
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
djeedai
approved these changes
Jun 20, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
With many ribbon effects alive (~260 trailed projectiles), the render thread crashes:
(The crash reported in #493.)
Cause
The sort indirect-dispatch buffer is a growable
GpuBufferwhose capacity is tracked in elements:allocate()may hand out an index past the current capacity, andprepare_buffers()then grows the GPU buffer to fit.GpuBuffer::new_allocatedtook the element capacity as a separate argument, andSortBindGroups::newpassed the buffer's byte size (3072) there instead of its element count. With 12-byteGpuDispatchIndirectArgsthe physical buffer holds 256 elements, whileGpuBufferbelieved it had 3072 elements of headroom — soprepare_buffers()never grew it. The 257th sort dispatch then read past the end of the physical buffer (byte 3072 in a 3072-byte buffer), exactly the reported numbers.Fix
Remove the separate capacity argument from
new_allocatedand derive the element capacity from the physical buffer (buffer.size() / item_size), so the recorded and physical capacities can't disagree.new_allocatedhas a single caller, which now just sizes the buffer; adebug_assertguards a non-multiple size.Testing
Verified in a ribbon-heavy app (every projectile carries a ribbon trail): pre-fix it crashes reliably once ~256 ribbon sort dispatches are concurrently active; post-fix it runs well past that as the buffer grows.
cargo buildpasses.Independent of #537 (stale-buffer overruns in the sort bind-group cache); this is the buffer growth bug, #493.
Closes #493