Skip to content

Commit fe85f6f

Browse files
author
Exoridus
committed
fix(webgpu): correct sprite index buffer size and avoid mid-pass growth
Three related bugs in WebGpuSpriteRenderer that surface together when the sprite count exceeds initialBatchCapacity (32): 1. Index buffer was allocated with size: indexData.byteLength * Uint32Array.BYTES_PER_ELEMENT indexData.byteLength is already bytes, so the buffer ended up 4x larger than intended. Benign alone, but wastes memory. 2. _ensureBatchCapacity was called inside the draw loop, after setVertexBuffer and setIndexBuffer were already recorded on the render pass. When a batch exceeds the current capacity, the method destroys the old GPU buffers and creates new ones — the pass then holds bindings to destroyed buffers, producing - "Index range (first: 0, count: N) does not fit in index buffer size (3072)" (the initial 32-sprite buffer) - "[Buffer (unlabeled)] used in submit while destroyed" 3. Hotfix above: walk all batches up front, find the largest sprite count, and ensureBatchCapacity once before begin of the render pass. All subsequent setVertexBuffer/setIndexBuffer bindings stay valid throughout the pass. The drawIndexed is the same as before — only the growth path moves.
1 parent 753c2ad commit fe85f6f

1 file changed

Lines changed: 19 additions & 2 deletions

File tree

src/rendering/webgpu/WebGpuSpriteRenderer.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,24 @@ export class WebGpuSpriteRenderer extends AbstractWebGpuRenderer<Sprite> {
299299
return;
300300
}
301301

302+
// Grow vertex/index buffers up front for the largest batch in this
303+
// flush. _ensureBatchCapacity destroys the old buffers and creates new
304+
// ones when capacity grows, so it must not run after setVertexBuffer /
305+
// setIndexBuffer — otherwise the pass's bindings point at destroyed
306+
// buffers and the submit is invalid.
307+
let maxBatchSpriteCount = 0;
308+
for (let start = 0; start < this._drawCallCount;) {
309+
const batchRange = this._getBatchRange(start);
310+
const batchSpriteCount = batchRange.end - batchRange.start;
311+
if (batchSpriteCount > maxBatchSpriteCount) {
312+
maxBatchSpriteCount = batchSpriteCount;
313+
}
314+
start = batchRange.end;
315+
}
316+
if (maxBatchSpriteCount > 0) {
317+
this._ensureBatchCapacity(maxBatchSpriteCount);
318+
}
319+
302320
const viewMatrix = renderManager.view.getTransform();
303321

304322
this._projectionData.set([
@@ -338,7 +356,6 @@ export class WebGpuSpriteRenderer extends AbstractWebGpuRenderer<Sprite> {
338356
const pipeline = this._getPipeline(batch.blendMode, renderManager.renderTargetFormat);
339357
const spriteCount = batch.end - batch.start;
340358

341-
this._ensureBatchCapacity(spriteCount);
342359
this._writeBatchVertexData(batch);
343360

344361
device.queue.writeBuffer(
@@ -388,7 +405,7 @@ export class WebGpuSpriteRenderer extends AbstractWebGpuRenderer<Sprite> {
388405
});
389406
const indexData = new Uint32Array(nextCapacity * spriteIndexCount);
390407
const indexBuffer = this._device.createBuffer({
391-
size: indexData.byteLength * Uint32Array.BYTES_PER_ELEMENT,
408+
size: indexData.byteLength,
392409
usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST,
393410
});
394411

0 commit comments

Comments
 (0)