navigator.gpu can exist with no usable adapter behind it (Power Preference). WebGPUEngine.IsSupportedAsync, a static promise, requests an adapter and resolves to true only if one arrives. Test it before creating any scene, since a scene cannot move to another engine:
async function createEngine(canvas) {
if (await BABYLON.WebGPUEngine.IsSupportedAsync) {
const engine = new BABYLON.WebGPUEngine(canvas, { antialias: true });
await engine.initAsync();
return engine;
}
return new BABYLON.Engine(canvas, true); // WebGL 2, or WebGL 1 as a last resort
}
createEngine(view).then((engine) => console.log(`gpu in navigator: ${'gpu' in navigator}, ` +
`engine: ${engine.description}`));Run normally, then with Chrome 1 's --disable-gpu switch, which keeps navigator.gpu but removes the adapter:
BJS - [15:36:24]: Babylon.js v9.28.0 - WebGPU1 engine gpu in navigator: true, engine: WebGPU1 BJS - [15:36:31]: Babylon.js v9.28.0 - WebGL2 gpu in navigator: true, engine: WebGL2
The second run fell back to WebGL 2 on SwiftShader 2,546 (SwiftShader). BABYLON.EngineFactory.CreateAsync(canvas) packages the same logic, ending with a NullEngine. Guard WebGPU-only code with engine.isWebGPU, and test both paths.
<!doctype html>
<style>
body { margin: 0; background: #f7f3ec; font: 13px system-ui, sans-serif; color: #333; }
canvas { width: 100%; max-width: 600px; height: 330px; display: block; touch-action: none; }
#info { max-width: 600px; padding: 6px 8px; font-family: monospace; white-space: pre; }
</style>
<canvas id="view"></canvas>
<div id="info">Detecting…</div>
<script src="https://cdn.jsdelivr.net/npm/babylonjs@9.28.0/babylon.js"></script>
<script>
const canvas = document.getElementById('view');
const info = document.getElementById('info');
async function createEngine(canvas) {
// navigator.gpu can exist with no adapter behind it; IsSupportedAsync asks for one
if (await BABYLON.WebGPUEngine.IsSupportedAsync) {
try {
const engine = new BABYLON.WebGPUEngine(canvas, { antialias: true });
await engine.initAsync();
return engine;
} catch (error) {
console.log('WebGPU init failed, falling back: ' + error.message);
}
}
return new BABYLON.Engine(canvas, true); // WebGL 2, or WebGL 1 as a last resort
}
createEngine(canvas).then((engine) => {
// Decide the engine BEFORE creating the scene: a scene cannot move to another engine
const scene = new BABYLON.Scene(engine);
scene.clearColor = BABYLON.Color4.FromHexString(engine.isWebGPU ? '#e3ecf5ff' : '#f3e7d8ff');
const camera = new BABYLON.ArcRotateCamera('cam', -Math.PI / 2.5, 1.2, 4, BABYLON.Vector3.Zero(), scene);
camera.attachControl(canvas, true);
new BABYLON.HemisphericLight('sky', new BABYLON.Vector3(0.3, 1, -0.4), scene);
const book = BABYLON.MeshBuilder.CreateBox('book', { width: 1, height: 1.5, depth: 0.25 }, scene);
const cover = new BABYLON.StandardMaterial('cover', scene);
// Guard backend-specific choices with engine.isWebGPU
cover.diffuseColor = BABYLON.Color3.FromHexString(engine.isWebGPU ? '#1f5f8b' : '#b5452f');
book.material = cover;
info.textContent = `'gpu' in navigator: ${'gpu' in navigator}\n` +
`engine.isWebGPU: ${!!engine.isWebGPU}\n` +
`engine.description: ${engine.description}`;
engine.runRenderLoop(() => { book.rotation.y += 0.01; scene.render(); });
addEventListener('resize', () => engine.resize());
});
</script>