mrdoob/three.js · error · Error

THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is

Error message

THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not THREE.WebGLRenderTarget.

What it means

Thrown by readRenderTargetPixelsAsync when the passed renderTarget is null/undefined or lacks the isWebGLRenderTarget flag. The async variant throws (unlike the sync readRenderTargetPixels which only warns) because async pipelines expect hard failures.

Source

Thrown at src/renderers/WebGLRenderer.js:3186

		 *
		 * It is recommended to use this version of `readRenderTargetPixels()` whenever possible.
		 *
		 * @async
		 * @param {WebGLRenderTarget} renderTarget - The render target to read from.
		 * @param {number} x - The `x` coordinate of the copy region's origin.
		 * @param {number} y - The `y` coordinate of the copy region's origin.
		 * @param {number} width - The width of the copy region.
		 * @param {number} height - The height of the copy region.
		 * @param {TypedArray} buffer - The result buffer.
		 * @param {number} [activeCubeFaceIndex] - The active cube face index.
		 * @param {number} [textureIndex=0] - The texture index of an MRT render target.
		 * @return {Promise<TypedArray>} A Promise that resolves when the read has been finished. The resolve provides the read data as a typed array.
		 */
		this.readRenderTargetPixelsAsync = async function ( renderTarget, x, y, width, height, buffer, activeCubeFaceIndex, textureIndex = 0 ) {

			if ( ! ( renderTarget && renderTarget.isWebGLRenderTarget ) ) {

				throw new Error( 'THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not THREE.WebGLRenderTarget.' );

			}

			let framebuffer = properties.get( renderTarget ).__webglFramebuffer;
			if ( renderTarget.isWebGLCubeRenderTarget && activeCubeFaceIndex !== undefined ) {

				framebuffer = framebuffer[ activeCubeFaceIndex ];

			}

			if ( framebuffer ) {

				// the following if statement ensures valid read requests (no out-of-bounds pixels, see #8604)
				if ( ( x >= 0 && x <= ( renderTarget.width - width ) ) && ( y >= 0 && y <= ( renderTarget.height - height ) ) ) {

					// set the active frame buffer to the one we want to read
					state.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer );

View on GitHub (pinned to da05705fa3)

Solutions

  1. Ensure the argument is a WebGLRenderTarget (or WebGLCubeRenderTarget, which extends it) before calling.
  2. Guard: if (renderTarget && renderTarget.isWebGLRenderTarget) { ... } around the call.
  3. For cube render targets, pass the correct activeCubeFaceIndex and confirm isWebGLCubeRenderTarget if needed.
  4. Check that the render target was created with new THREE.WebGLRenderTarget(...) and not disposed.

Example fix

// before
const data = await renderer.readRenderTargetPixelsAsync( someTexture, 0, 0, w, h, buf ); // not a RT

// after
if ( renderTarget && renderTarget.isWebGLRenderTarget ) {
  const data = await renderer.readRenderTargetPixelsAsync( renderTarget, 0, 0, w, h, buf );
}
Defensive patterns

Strategy: type-guard

Validate before calling

if ( renderTarget && renderTarget.isWebGLRenderTarget ) {
  const data = await renderer.readRenderTargetPixelsAsync( renderTarget, 0, 0, w, h, buf );
}

Type guard

function isWebGLRenderTarget( obj ) {
  return obj != null && obj.isWebGLRenderTarget === true;
}

Prevention

When it happens

Trigger: Passing a regular Texture, a canvas, null, a WebGLCubeRenderTarget used incorrectly, or any object that is not a WebGLRenderTarget to readRenderTargetPixelsAsync.

Common situations: Refactoring from readRenderTargetPixels (sync) to the async variant and passing the same unguarded arguments; passing a texture instead of its owning render target; passing the result of a function that may return null.

Related errors


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