mrdoob/three.js · error · Error
THREE.WebGLRenderer.readRenderTargetPixelsAsync: requested r
Error message
THREE.WebGLRenderer.readRenderTargetPixelsAsync: requested read bounds are out of range.
What it means
Thrown by readRenderTargetPixelsAsync when the requested read rectangle falls outside the render target bounds. The guard at line 3200 requires x>=0, y>=0, x <= renderTarget.width - width, and y <= renderTarget.height - height; the else branch throws.
Source
Thrown at src/renderers/WebGLRenderer.js:3254
// check if the commands have finished every 8 ms
const sync = _gl.fenceSync( _gl.SYNC_GPU_COMMANDS_COMPLETE, 0 );
_gl.flush();
await probeAsync( _gl, sync, 4 );
// read the data and delete the buffer
_gl.bindBuffer( _gl.PIXEL_PACK_BUFFER, glBuffer );
_gl.getBufferSubData( _gl.PIXEL_PACK_BUFFER, 0, buffer );
_gl.deleteBuffer( glBuffer );
_gl.deleteSync( sync );
return buffer;
} else {
throw new Error( 'THREE.WebGLRenderer.readRenderTargetPixelsAsync: requested read bounds are out of range.' );
}
}
};
/**
* Copies pixels from the current bound framebuffer into the given texture.
*
* @param {FramebufferTexture} texture - The texture.
* @param {?Vector2} [position=null] - The start position of the copy operation.
* @param {number} [level=0] - The mip level. The default represents the base mip.
*/
this.copyFramebufferToTexture = function ( texture, position = null, level = 0 ) {
const levelScale = Math.pow( 2, - level );
const width = Math.floor( texture.image.width * levelScale );View on GitHub (pinned to da05705fa3)
Solutions
- Clamp x in [0, rt.width - width] and y in [0, rt.height - height] before calling; ensure width/height <= the target's dimensions.
- After resizing a render target, refresh any cached width/height used for readback and reallocate the buffer.
- Validate: if (x < 0 || y < 0 || x + width > rt.width || y + height > rt.height) skip/adjust.
- For full-target reads, pass exactly (0, 0, rt.width, rt.height) and a buffer of rt.width*rt.height*components.
Example fix
// before const buf = new Uint8Array( oldW * oldH * 4 ); await renderer.readRenderTargetPixelsAsync( rt, 0, 0, oldW, oldH, buf ); // throws if rt shrank // after const w = rt.width, h = rt.height; const buf = new Uint8Array( w * h * 4 ); await renderer.readRenderTargetPixelsAsync( rt, 0, 0, w, h, buf );
Defensive patterns
Strategy: validation
Validate before calling
const inBounds =
x >= 0 && y >= 0 &&
x <= renderTarget.width - width &&
y <= renderTarget.height - height;
if ( inBounds ) {
await renderer.readRenderTargetPixelsAsync( renderTarget, x, y, width, height, buf );
} Prevention
- Clamp x/y and ensure width/height fit within the target dimensions.
- Re-fetch rt.width/rt.height after every resize before reading.
- Reallocate the readback buffer to match current target size.
- Use (0,0,rt.width,rt.height) for full-target reads to avoid edge math.
When it happens
Trigger: Passing negative x/y, a width/height larger than the target, or an x/y offset whose rectangle extends past the target's right/bottom edge. Using stale dimensions after the target was resized smaller.
Common situations: Reading the full target with readRenderTargetPixelsAsync(rt, 0, 0, rt.width, rt.height, buf) where buf was sized for old dimensions; computing sub-regions with inverted/clamped math; passing window pixels instead of target pixels.
Related errors
- THREE.WebGLRenderer: Attached DepthTexture is initialized to
- THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is
- THREE.WebGLRenderer.readRenderTargetPixelsAsync: renderTarge
- THREE.WebGLRenderer.readRenderTargetPixelsAsync: renderTarge
- THREE.BatchedMesh: Instance ids outside the range ${ maxInst
AI-assisted analysis of mrdoob/three.js@da05705fa3 (2026-08-12).
Data as JSON: /api/errors/25e900eecc377303.
Report an issue: GitHub.