Add optional WebGPU engine support via ?webGPU=true query parameter

Allows launching the game with WebGPU rendering by passing ?webGPU=true in the URL.
Defaults to WebGL engine when the parameter is absent. Logs a warning that WebXR/VR
is not yet supported with WebGPU.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Michael Mainguy 2026-03-03 06:39:24 -06:00
parent 208d735ef4
commit 53388c8193
6 changed files with 33 additions and 18 deletions

View File

@ -1,11 +1,11 @@
import { Engine } from "@babylonjs/core";
import { AbstractEngine } from "@babylonjs/core";
import { DefaultScene } from "./defaultScene";
import { RockFactory } from "../environment/asteroids/rockFactory";
import log from './logger';
import Level from "../levels/level";
export interface CleanupContext {
getEngine(): Engine;
getEngine(): AbstractEngine;
getCurrentLevel(): Level | null;
setCurrentLevel(level: Level | null): void;
resetState(): void;

View File

@ -1,4 +1,4 @@
import { AudioEngineV2, Engine, ParticleHelper } from "@babylonjs/core";
import { AbstractEngine, AudioEngineV2, ParticleHelper } from "@babylonjs/core";
import { DefaultScene } from "../defaultScene";
import { Level1 } from "../../levels/level1";
import Level from "../../levels/level";
@ -20,7 +20,7 @@ export interface LevelSelectedContext {
initializeEngine(): Promise<void>;
initializeXR(): Promise<void>;
getAudioEngine(): AudioEngineV2;
getEngine(): Engine;
getEngine(): AbstractEngine;
setCurrentLevel(level: Level): void;
setProgressCallback(callback: (percent: number, message: string) => void): void;
play(): Promise<void>;
@ -169,7 +169,7 @@ function attachAudioListener(audioEngine: AudioEngineV2): void {
async function finalizeLevelStart(
level: Level1,
engine: Engine,
engine: AbstractEngine,
preloader: Preloader,
context: LevelSelectedContext
): Promise<void> {
@ -188,7 +188,7 @@ async function finalizeLevelStart(
await context.play();
}
function showCanvasForFlatMode(engine: Engine): void {
function showCanvasForFlatMode(engine: AbstractEngine): void {
const canvas = document.getElementById('gameCanvas');
if (canvas) canvas.style.display = 'block';
engine.stopRenderLoop();

View File

@ -1,4 +1,4 @@
import { Engine, FreeCamera, Vector3 } from "@babylonjs/core";
import { AbstractEngine, FreeCamera, Vector3 } from "@babylonjs/core";
import { DefaultScene } from "../defaultScene";
import { LevelConfig } from "../../levels/config/levelConfig";
import log from '../logger';
@ -9,7 +9,7 @@ import log from '../logger';
*/
export async function enterXRMode(
config: LevelConfig,
engine: Engine
engine: AbstractEngine
): Promise<any> {
if (!DefaultScene.XR) {
return startFlatMode(engine);
@ -39,7 +39,7 @@ function prePositionCamera(config: LevelConfig): void {
log.debug('[XR] Camera pre-positioned at cockpit:', cockpitPosition.toString());
}
function startFlatMode(engine: Engine): null {
function startFlatMode(engine: AbstractEngine): null {
const canvas = document.getElementById('gameCanvas');
if (canvas) canvas.style.display = 'block';
engine.stopRenderLoop();

3
src/core/queryParams.ts Normal file
View File

@ -0,0 +1,3 @@
const urlParams = new URLSearchParams(window.location.search);
export const useWebGPU = urlParams.get('webGPU') === 'true';

View File

@ -1,18 +1,22 @@
import {
AbstractEngine,
AudioEngineV2,
Color3,
CreateAudioEngineAsync,
Engine,
HavokPlugin,
Scene,
Vector3
Vector3,
WebGPUEngine,
} from "@babylonjs/core";
import HavokPhysics from "@babylonjs/havok";
import { DefaultScene } from "./defaultScene";
import { ProgressReporter } from "./xrSetup";
import { useWebGPU } from "./queryParams";
import log from './logger';
export interface SceneSetupResult {
engine: Engine;
engine: AbstractEngine;
audioEngine: AudioEngineV2;
}
@ -24,7 +28,7 @@ export async function setupScene(
reporter: ProgressReporter
): Promise<SceneSetupResult> {
reporter.reportProgress(5, 'Creating rendering engine...');
const engine = createEngine(canvas);
const engine = await createEngine(canvas);
reporter.reportProgress(10, 'Creating scene...');
createMainScene(engine);
@ -44,14 +48,22 @@ export async function setupScene(
return { engine, audioEngine };
}
function createEngine(canvas: HTMLCanvasElement): Engine {
const engine = new Engine(canvas, true);
async function createEngine(canvas: HTMLCanvasElement): Promise<AbstractEngine> {
let engine: AbstractEngine;
if (useWebGPU) {
log.info('[Engine] Creating WebGPU engine');
log.warn('[Engine] WebXR/VR is still experimental');
engine = await WebGPUEngine.CreateAsync(canvas, { antialias: true });
} else {
log.info('[Engine] Creating WebGL engine');
engine = new Engine(canvas, true);
}
engine.setHardwareScalingLevel(1 / window.devicePixelRatio);
window.onresize = () => engine.resize();
return engine;
}
function createMainScene(engine: Engine): void {
function createMainScene(engine: AbstractEngine): void {
// Dispose old scene if it exists (prevents doubling on reload)
if (DefaultScene.MainScene && !DefaultScene.MainScene.isDisposed) {
DefaultScene.MainScene.dispose();

View File

@ -1,4 +1,4 @@
import { AudioEngineV2, Engine } from "@babylonjs/core";
import { AbstractEngine, AudioEngineV2 } from "@babylonjs/core";
import '@babylonjs/loaders';
import { DefaultScene } from "./core/defaultScene";
@ -20,7 +20,7 @@ const canvas = document.querySelector('#gameCanvas') as HTMLCanvasElement;
export class Main implements LevelSelectedContext, CleanupContext {
private _currentLevel: Level | null = null;
private _engine: Engine;
private _engine: AbstractEngine;
private _audioEngine: AudioEngineV2;
private _initialized: boolean = false;
private _assetsLoaded: boolean = false;
@ -43,7 +43,7 @@ export class Main implements LevelSelectedContext, CleanupContext {
areAssetsLoaded(): boolean { return this._assetsLoaded; }
setAssetsLoaded(value: boolean): void { this._assetsLoaded = value; }
getAudioEngine(): AudioEngineV2 { return this._audioEngine; }
getEngine(): Engine { return this._engine; }
getEngine(): AbstractEngine { return this._engine; }
setCurrentLevel(level: Level): void { this._currentLevel = level; }
setProgressCallback(cb: (percent: number, message: string) => void): void {
this._progressCallback = cb;