mrdoob/three.js · error · Error
THREE.WebGPUTextureUtils: Texture already initialized.
Error message
THREE.WebGPUTextureUtils: Texture already initialized.
What it means
Thrown by WebGPUTextureUtils.createTexture when the backend already holds an initialized GPU texture for the given Texture object (textureData.initialized === true) and it is not an external XR texture. createTexture is the one-time GPU resource allocation path; once a texture is initialized the renderer drives updates through texture.version comparison (textureData.version vs texture.version) inside updateTexture, never by re-calling createTexture. Re-entering createTexture on the same texture would leak/duplicate GPU memory, so the library treats it as a programmer error.
Source
Thrown at src/renderers/webgpu/utils/WebGPUTextureUtils.js:316
*
* @param {Texture} texture - The texture.
* @param {Object} [options={}] - Optional configuration parameter.
*/
createTexture( texture, options = {} ) {
const backend = this.backend;
const textureData = backend.get( texture );
if ( textureData.initialized ) {
// Skip creation for external XR textures - they are already set up
if ( textureData.externalTexture === true ) {
return;
}
throw new Error( 'THREE.WebGPUTextureUtils: Texture already initialized.' );
}
if ( texture.isExternalTexture ) {
textureData.texture = texture.sourceTexture;
textureData.initialized = true;
return;
}
if ( options.needsMipmaps === undefined ) options.needsMipmaps = false;
if ( options.levels === undefined ) options.levels = 1;
if ( options.depth === undefined ) options.depth = 1;
const { width, height, depth, levels } = options;
View on GitHub (pinned to da05705fa3)
Solutions
- Drive updates through the public API: set texture.needsUpdate = true (or bump texture.version) and let the renderer's updateTexture path handle re-upload.
- If you genuinely need a fresh GPU resource, call texture.dispose() and create a new Texture instance (or reuse it only after the backend state for it has been cleared), not createTexture on the existing initialized one.
- If calling backend.createTexture directly, guard with: if (renderer.backend.get(texture).initialized) return; before invoking it.
- Remove any double-init code path such as calling createTexture inside a loop or render callback that already runs the normal texture update pipeline.
Example fix
// before: manual re-create after resize
function resize( texture, w, h ) {
texture.image.width = w; texture.image.height = h;
renderer.backend.textureUtils.createTexture( texture ); // throws once initialized
}
// after: go through the version-tracked update path
function resize( texture, w, h ) {
texture.image.width = w; texture.image.height = h;
texture.needsUpdate = true; // renderer re-uploads safely
} Defensive patterns
Strategy: validation
Validate before calling
// Never call backend.createTexture directly without checking the initialized flag.
function safeCreateTexture( renderer, texture, options ) {
const data = renderer.backend.get( texture );
if ( data && data.initialized ) {
// already on GPU: route through the version-tracked update path instead
texture.needsUpdate = true;
return;
}
renderer.backend.textureUtils.createTexture( texture, options );
} Try / catch
try {
renderer.backend.textureUtils.createTexture( texture );
} catch ( err ) {
if ( /Texture already initialized/.test( err.message ) ) {
// texture is already on the GPU; use the update path instead
texture.needsUpdate = true;
} else {
throw err;
}
} Prevention
- Prefer texture.needsUpdate = true over any direct backend.createTexture call.
- Treat createTexture as a one-shot allocation: to change size or format, dispose the Texture and build a new one.
- Keep texture lifecycle ownership in one place (the renderer); do not mix high-level scene rendering with manual backend texture calls.
- In HMR / hot-reload flows, dispose and recreate Texture instances rather than re-running createTexture on survivors.
When it happens
Trigger: Calling renderer.backend.textureUtils.createTexture(texture) (or the public Renderer path that reaches it) on a texture the renderer has already uploaded. Triggering a forced re-creation while the version bookkeeping still marks the texture initialized. Calling createTexture manually for a texture that is also attached to a scene already rendered once. Reusing a Texture object after dispose without resetting backend state.
Common situations: Mixing the high-level renderer API (which owns texture lifecycle via texture.needsUpdate = true) with manual low-level backend.createTexture calls. Hot-reload / HMR flows that re-run setup code on the same Texture instance. Calling createTexture to change a texture's size or format instead of disposing and creating a new Texture. Misusing a texture both as a render target attachment and as a manually-created resource.
Related errors
- THREE.Renderer: .initTexture() called before the backend is
- THREE.Renderer: .render() called before the backend is initi
- THREE.Renderer: .hasFeature() called before the backend is i
- THREE.Renderer: .initRenderTarget() called before the backen
- THREE.Renderer: .hasCompatibility() called before the backen
AI-assisted analysis of mrdoob/three.js@da05705fa3 (2026-08-12).
Data as JSON: /api/errors/45bef469b30733f8.
Report an issue: GitHub.