BabylonJS/Babylon.js · error

WebGPUDurationMeasure: index out of range (" + index + ")

Error message

WebGPUDurationMeasure: index out of range (" + index + ")

What it means

WebGPUDurationMeasure (timestamp query helper) allocates a query set with a fixed count (this._count). startPass writes two timestamps at index+2 and index+3, so if index+3 exceeds the allocated count the writes would fall outside the query set. Babylon throws to prevent corrupting the GPU query set.

Source

Thrown at packages/dev/core/src/Engines/WebGPU/webgpuTimestampQuery.ts:131

    constructor(engine: WebGPUEngine, device: GPUDevice, bufferManager: WebGPUBufferManager, count = 2, querySetLabel?: string) {
        this._count = count;
        this._querySet = new WebGPUQuerySet(engine, count, WebGPUConstants.QueryType.Timestamp, device, bufferManager, true, querySetLabel);
    }

    public start(encoder: GPUCommandEncoder): void {
        (encoder as GPUCommandEncoderWithTimestamp).writeTimestamp?.(this._querySet.querySet, 0);
    }

    public async stop(encoder: GPUCommandEncoder): Promise<number | null> {
        (encoder as GPUCommandEncoderWithTimestamp).writeTimestamp?.(this._querySet.querySet, 1);

        return (encoder as GPUCommandEncoderWithTimestamp).writeTimestamp ? await this._querySet.readTwoValuesAndSubtract(0) : 0;
    }

    public startPass(descriptor: GPURenderPassDescriptor | GPUComputePassDescriptor, index: number): void {
        if (index + 3 > this._count) {
            throw new Error("WebGPUDurationMeasure: index out of range (" + index + ")");
        }

        descriptor.timestampWrites = {
            querySet: this._querySet.querySet,
            beginningOfPassWriteIndex: index + 2,
            endOfPassWriteIndex: index + 3,
        };
    }

    public async stopPass(index: number): Promise<number | null> {
        return await this._querySet.readTwoValuesAndSubtract(index + 2);
    }

    public dispose() {
        this._querySet.dispose();
    }
}

View on GitHub (pinned to 0592b347b8)

Solutions

  1. Initialize the duration measure with enough slots: call initTimeStampRegisters / create the measure with a count >= 2*(number of passes)+2
  2. Use distinct, incrementing indices per pass and verify the maximum index before calling
  3. Clamp or reuse measurement slots instead of allocating a new index per pass

Example fix

// before
durationMeasure = new WebGPUDurationMeasure(engine, 4);
durationMeasure.startPass(descriptor, 3); // 3+3 > 4
// after
durationMeasure = new WebGPUDurationMeasure(engine, 8); // at least index+4
durationMeasure.startPass(descriptor, 3);
Defensive patterns

Strategy: validation

Validate before calling

const maxIndex = durationMeasure.getContainerSize?.() ?? count; // slots allocated
if (index + 4 > count) throw new RangeError(`Pass index ${index} exceeds allocated timestamp slots (${count})`);

Type guard

function canStartPass(dm: { _count: number }, index: number): boolean {
  return index + 3 < dm._count;
}

Try / catch

try {
  durationMeasure.startPass(descriptor, i);
} catch (e) {
  if (e instanceof Error && e.message.startsWith('WebGPUDurationMeasure: index out of range')) {
    console.warn(`Skipping timing for pass ${i}: not enough query slots`);
  } else throw e;
}

Prevention

When it happens

Trigger: Passing an index too large to WebGPUDurationMeasure.startPass (or start/end pairs) such that index+3 >= count, e.g. measuring more passes than the duration measure was created for (initTimeStampRegisters/count).

Common situations: Performance instrumentation loops that call startPass/endPass for every draw pass but initialized the duration measure with too few registers; copy-pasted measurement code after increasing the number of passes.

Related errors


AI-assisted analysis of BabylonJS/Babylon.js@0592b347b8 (2026-08-30). Data as JSON: /api/errors/0c2fe6dcfa54ab3f. Report an issue: GitHub.