Answer
1. Detect missing file vs CORS
The Texture constructor does not throw; loading happens asynchronously and the result is reported through onError. To distinguish a 404/missing‑file from a CORS block, inspect the network response inside the error handler:
const tex = new BABYLON.Texture(url, scene, null, false, undefined, undefined, undefined, true);
tex.onError = () => {
// The texture object still exists; we can check its underlying image.
const img = tex.getInternalTexture()?.baseTexture;
if (!img) {
console.warn('Texture object not created – likely invalid URL or scene');
return;
}
// Use a temporary Image to probe the URL with the same CORS mode.
const probe = new Image();
probe.crossOrigin = tex._internalTexture ? 'anonymous' : 'use-credentials';
probe.onload = () => console.log('URL loads fine – error was not network');
probe.onerror = () => console.warn('Probe failed – check response status and CORS headers');
probe.src = url;
};
tex.onLoad = () => console.log('Texture loaded successfully');
If the probe succeeds, the original failure was likely a transient issue; if it fails with status 0 or a CORS‑related error in the console, the server is blocking the request due to missing Access‑Control‑Allow‑Origin headers.
2. Automatic retry pattern
For transient network glitches, wrap the texture creation in a retry helper that respects exponential back‑off and limits attempts to avoid flooding the server or stalling the UI:
function loadTextureWithRetry(url, scene, options = {}, maxAttempts = 3, baseDelay = 300) {
let attempt = 0;
const tryLoad = () => {
attempt++;
const tex = new BABYLON.Texture(url, scene, ...options);
return new Promise((resolve, reject) => {
tex.onLoad = () => resolve(tex);
tex.onError = () => {
if (attempt >= maxAttempts) {
reject(new Error(`Texture failed after ${attempt} attempts`));
return;
}
const delay = baseDelay * 2 ** (attempt - 1);
setTimeout(tryLoad, delay);
};
});
};
return tryLoad();
}
// Usage
loadTextureWithRetry('textures/brick.jpg', scene)
.then(tex => console.log('Ready', tex))
.catch(err => console.error(err));
This approach keeps the main render loop free because each retry is scheduled with setTimeout. Adjust maxAttempts and baseDelay based on your network quality.
3. Texture cache sizing and eviction
Babylon.js maintains an internal GPU texture cache (engine.getCaps().maxTextureSize limits individual texture dimensions, while the number of simultaneous textures is bounded by the GPU’s texture unit count). When the cache fills, new textures may fail silently or fall back to a default texture.
- When to resize/clear: Only if you notice a pattern of load failures after many unique textures have been created (e.g., dynamic terrain streaming). Monitor
engine.getTextures().length or use engine._textures (internal) to gauge usage.
- Best practices:
- Prefer reusing textures: store them in a manager and call
texture.dispose() only when truly no longer needed.
- Use
Texture.DONT_PRELOAD for textures that can be loaded on demand.
- If you must clear, call
scene.getEngine()._releaseTexture(texture) followed by texture.dispose(), then optionally invoke scene.getEngine()._resetTextureCache() (internal) – but note this is engine‑specific and may change between versions.
In most applications, letting the GPU manage its own texture cache and disposing textures when their associated meshes are disposed is sufficient. Manual resizing is rarely required and should be undertaken only after profiling shows GPU memory pressure.