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

  1. Clamp x in [0, rt.width - width] and y in [0, rt.height - height] before calling; ensure width/height <= the target's dimensions.
  2. After resizing a render target, refresh any cached width/height used for readback and reallocate the buffer.
  3. Validate: if (x < 0 || y < 0 || x + width > rt.width || y + height > rt.height) skip/adjust.
  4. 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

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


AI-assisted analysis of mrdoob/three.js@da05705fa3 (2026-08-12). Data as JSON: /api/errors/25e900eecc377303. Report an issue: GitHub.