mrdoob/three.js · error · Error

THREE.Renderer: .hasFeature() called before the backend is i

Error message

THREE.Renderer: .hasFeature() called before the backend is initialized. Use "await renderer.init();" before using this method.

What it means

Thrown by Renderer.hasFeature() when the renderer's backend has not yet been initialized. The WebGPU/WebGL backend (adapter, device, GL context) is created lazily during renderer.init(), which is async; calling hasFeature() before that point has no backend to query. The library throws rather than silently returning false so callers fail loudly on a missing await.

Source

Thrown at src/renderers/common/Renderer.js:3041

		if ( this._initialized === false ) await this.init();

		return this.backend.resolveTimestampsAsync( type );

	}

	/**
	 * Checks if the given feature is supported by the selected backend. If the
	 * renderer has not been initialized, this method always returns `false`.
	 *
	 * @param {string} name - The feature's name.
	 * @return {boolean} Whether the feature is supported or not.
	 */
	hasFeature( name ) {

		if ( this._initialized === false ) {

			throw new Error( 'THREE.Renderer: .hasFeature() called before the backend is initialized. Use "await renderer.init();" before using this method.' );

		}

		return this.backend.hasFeature( name );

	}

	/**
	 * Returns `true` when the renderer has been initialized.
	 *
	 * @return {boolean} Whether the renderer has been initialized or not.
	 */
	hasInitialized() {

		return this._initialized;

	}

View on GitHub (pinned to da05705fa3)

Solutions

  1. Add `await renderer.init();` immediately after constructing the renderer, before any hasFeature/initTexture/render call.
  2. Use the deprecated async wrapper `await renderer.hasFeatureAsync(name)` which inits internally (will emit a deprecation warning).
  3. Guard the call with `if (renderer.hasInitialized()) { renderer.hasFeature(name) }` and otherwise defer the check until after init resolves.
  4. Run your feature check inside the same async scope that renders the first frame, so init has already been triggered by render/compute.

Example fix

// before
const renderer = new WebGPURenderer();
if (renderer.hasFeature('float32-blendable')) { /* ... */ } // throws

// after
const renderer = new WebGPURenderer();
await renderer.init();
if (renderer.hasFeature('float32-blendable')) { /* ... */ }
Defensive patterns

Strategy: validation

Validate before calling

async function ensureInit(renderer) {
  if (!renderer.hasInitialized()) await renderer.init();
}
// usage:
await ensureInit(renderer);
if (renderer.hasFeature('float32-blendable')) { /* ... */ }

Type guard

function canQueryFeatures(renderer) {
  return typeof renderer.hasInitialized === 'function' && renderer.hasInitialized() === true;
}

Try / catch

try {
  if (renderer.hasFeature(name)) { /* path A */ }
} catch (e) {
  if (/hasFeature\(\) called before/.test(e.message)) { await renderer.init(); /* retry */ }
  else throw e;
}

Prevention

When it happens

Trigger: Calling renderer.hasFeature('float32-blendable') (or any GPUFeatureName) on a freshly constructed WebGPURenderer / Renderer without first awaiting renderer.init(). Common in module-top-level code, app bootstrap, or capability checks done synchronously after `new WebGPURenderer()`.

Common situations: Upgrading from a three.js version where the backend initialized synchronously in the constructor (pre-r181 async-init change). Copying examples that omit `await renderer.init()`. Doing feature detection at the top of a module before the async init promise has resolved.

Related errors


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