{"record":{"id":"9264fc35f454f216","repo":"mrdoob/three.js","slug":"three-webgpurenderer-readbackbuffer-must-be-relea","errorCode":null,"errorMessage":"THREE.WebGPURenderer: ReadbackBuffer must be released before being used again.","messagePattern":"THREE\\.WebGPURenderer: ReadbackBuffer must be released before being used again\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/renderers/webgl-fallback/utils/WebGLAttributeUtils.js","lineNumber":289,"sourceCode":"\t\tconst { gl } = backend;\n\n\t\tconst bufferAttribute = attribute.isInterleavedBufferAttribute ? attribute.data : attribute;\n\t\tconst attributeInfo = backend.get( bufferAttribute );\n\t\tconst { bufferGPU } = attributeInfo;\n\n\t\tconst byteLength = count === - 1 ? attributeInfo.byteLength - offset : count;\n\n\t\t// read the data back\n\t\tlet dstBuffer;\n\t\tif ( target === null ) {\n\n\t\t\tdstBuffer = new Uint8Array( new ArrayBuffer( byteLength ) );\n\n\t\t} else if ( target.isReadbackBuffer ) {\n\n\t\t\tif ( target._mapped === true ) {\n\n\t\t\t\tthrow new Error( 'THREE.WebGPURenderer: ReadbackBuffer must be released before being used again.' );\n\n\t\t\t}\n\n\t\t\tconst releaseCallback = () => {\n\n\t\t\t\ttarget.buffer = null;\n\t\t\t\ttarget._mapped = false;\n\t\t\t\ttarget.removeEventListener( 'release', releaseCallback );\n\t\t\t\ttarget.removeEventListener( 'dispose', releaseCallback );\n\n\t\t\t};\n\n\t\t\ttarget.addEventListener( 'release', releaseCallback );\n\t\t\ttarget.addEventListener( 'dispose', releaseCallback );\n\n\t\t\t// WebGL has no concept of a \"mapped\" data buffer so we create a new buffer, instead.\n\t\t\tdstBuffer = new Uint8Array( new ArrayBuffer( byteLength ) );\n\t\t\ttarget.buffer = dstBuffer.buffer;","sourceCodeStart":271,"sourceCodeEnd":307,"githubUrl":"https://github.com/mrdoob/three.js/blob/da05705fa31f02710c19772a2aa70c7454807f0c/src/renderers/webgl-fallback/utils/WebGLAttributeUtils.js#L271-L307","documentation":"Thrown when calling readback with a ReadbackBuffer target that is still mapped (`_mapped === true`) from a previous readback. WebGL has no native mapped-buffer concept, so three emulates it; a ReadbackBuffer must be released (its 'release'/'dispose' event fired) before it can be reused for another readback. The error prevents overwriting a buffer the caller may still be consuming.","triggerScenarios":"Calling renderer.compute/com readback APIs (e.g. readbackBuffer, readback) twice in succession with the same ReadbackBuffer target without releasing it first. Holding the ReadbackBuffer across multiple frames and re-submitting it each frame without awaiting/completing the prior read.","commonSituations":"GPU readback loops (transform feedback, compute buffer readback) where the buffer is reused. Async readback where the caller doesn't await resolution before issuing the next readback. Forgetting to call release()/dispose() on the ReadbackBuffer after consuming its data.","solutions":["Release the ReadbackBuffer after consuming its data: `readbackBuffer.release()` (or let it dispose) before reusing.","Await the prior readback promise to completion before issuing the next one with the same target.","Use a pool/rotation of two or more ReadbackBuffers to overlap reads instead of reusing one synchronously.","Pass `null` as target to allocate a fresh ArrayBuffer each readback if reuse isn't required."],"exampleFix":"// before\nconst rb = new ReadbackBuffer();\nrenderer.readback(attr, 0, -1, rb);\nrenderer.readback(attr, 0, -1, rb); // throws - still mapped\n\n// after\nconst rb = new ReadbackBuffer();\nrenderer.readback(attr, 0, -1, rb);\nrb.release(); // unmap\nrenderer.readback(attr, 0, -1, rb);","handlingStrategy":"validation","validationCode":"function assertReadbackBufferFree(rb) {\n  if (rb && rb.isReadbackBuffer && rb._mapped === true) {\n    throw new Error('ReadbackBuffer is still mapped; call release() before reuse');\n  }\n}","typeGuard":"function readbackBufferIsFree(rb) {\n  return !rb || !rb.isReadbackBuffer || rb._mapped !== true;\n}","tryCatchPattern":"try {\n  renderer.readback(attr, 0, -1, rb);\n} catch (e) {\n  if (/ReadbackBuffer must be released/.test(e.message)) { rb.release(); renderer.readback(attr, 0, -1, rb); }\n  else throw e;\n}","preventionTips":["Always release a ReadbackBuffer after consuming its data.","For per-frame readbacks, rotate between two or more buffers to avoid stalls.","Await the previous readback before issuing the next with the same target."],"tags":["webgl","readback","resource-lifecycle","buffer","async"],"backgroundTag":null,"analyzedSha":"da05705fa31f02710c19772a2aa70c7454807f0c","analyzedAt":"2026-08-12T22:51:37.160Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}