Issues with Babylon.js WebGPU Engine in React with TypeScript

I’ve been having some difficulties while trying to integrate Babylon.js WebGPU engine into my React project, which is built with TypeScript and Vite. While the setup works perfectly fine with Babylon.js WebGL engine, I’m consistently getting the following errors when switching to the WebGPUEngine:

wasm streaming compile failed: TypeError: Failed to execute 'compile' on 'WebAssembly': Incorrect response MIME type. Expected 'application/wasm'.

This appears to be caused by the cdn downloaded scripts, for glslang.js, twgsl.js

Additionally, I’m seeing warnings like:

[TextureView "TextureView_SwapChain_ResolveTarget"] is associated with [Device "BabylonWebGPUDevice0"], and cannot be used with [Device "BabylonWebGPUDevice1"].

heres an example of some code i am trying to run with the WebGPUEngine when getting these types of warnings:

import { useEffect, useRef, useState } from 'react';
import { Engine, WebGPUEngine, Scene, Color4, MeshBuilder, StandardMaterial, Color3, ArcRotateCamera, Vector3, HemisphericLight } from '@babylonjs/core';

const App: React.FC = () => {
  const canvasRef = useRef<HTMLCanvasElement>(null);
  const [sceneKey, setSceneKey] = useState<'sceneone' | 'scenetwo' | 'scenethree'>('sceneone');
  const scenes: Record<'sceneone' | 'scenetwo' | 'scenethree', { background: Color4; boxColor: Color3 }> = {
    sceneone: {
      background: new Color4(1, 1, 1, 1),
      boxColor: new Color3(0, 1, 0),
    },
    scenetwo: {
      background: new Color4(1, 1, 1, 1),
      boxColor: new Color3(1, 0, 0),
    },
    scenethree: {
      background: new Color4(1, 1, 1, 1),
      boxColor: new Color3(0, 0, 1),
    },
  };

  useEffect(() => {
    if (!canvasRef.current) return;

    const initializeEngine = async () => {
      console.log('Initializing engine');
      // const engine = n ew Engine(canvasRef.current!);
      const engine = new WebGPUEngine(canvasRef.current!);
      await engine.initAsync();

      const scene = new Scene(engine);
      scene.clearColor = scenes[sceneKey].background;

      const box = MeshBuilder.CreateBox('box', { size: 2 }, scene);
      const boxMaterial = new StandardMaterial('boxMaterial', scene);
      boxMaterial.diffuseColor = scenes[sceneKey].boxColor;
      box.material = boxMaterial;

      new HemisphericLight('light', new Vector3(0, 1, 0), scene);

      const camera = new ArcRotateCamera('camera', -Math.PI / 2, Math.PI / 2, 5, Vector3.Zero(), scene);
      camera.attachControl(canvasRef.current, true);

      engine.runRenderLoop(() => {
        scene.render();
      });

      return () => {
        scene.dispose();
        engine.dispose();
      };
    };

    initializeEngine();
  }, [sceneKey]);

  const handleSceneChange = () => {
    switch (sceneKey) {
      case 'sceneone':
        console.log('Switching to scenetwo');
        setSceneKey('scenetwo');
        break;
      case 'scenetwo':
        console.log('Switching to scenethree');
        setSceneKey('scenethree');
        break;
      case 'scenethree':
        console.log('Switching to sceneone');
        setSceneKey('sceneone');
        break;
      default:
        break;
    }
  };

  return (
    <div>
      <canvas ref={canvasRef} style={{ width: '100%', height: '400px' }} />
      <button onClick={handleSceneChange}>Switch Scene</button>
    </div>
  );
};

export default App;

try this

const engine = new WebGPUEngine(canvasRef.current!, {
    glslangOptions: {
        jsPath: new URL("@babylonjs/core/assets/glslang/glslang.js", import.meta.url).href,
        wasmPath: new URL("@babylonjs/core/assets/glslang/glslang.wasm", import.meta.url).href
    },
    twgslOptions: {
        jsPath: new URL("@babylonjs/core/assets/twgsl/twgsl.js", import.meta.url).href,
        wasmPath: new URL("@babylonjs/core/assets/twgsl/twgsl.wasm", import.meta.url).href
    }
});
2 Likes

thanks @noname0310 ! that is working to remove the wasm issues with webgpuengine.

for the other issues i think i found the way to resolve this, since the creation of webgpuengine is asynchronus, we can use react useState to maintain the control flow for engine creation, i found one way to do this:

creation of main.tsx:

import React, { useEffect, useRef, useState } from 'react';
import { WebGPUEngine } from '@babylonjs/core';
import { SceneOne } from '@components/scene/SceneOne';
import { SceneTwo } from '@components/scene/SceneTwo';
import { SceneThree } from '@components/scene/SceneThree';

let engineInstance: WebGPUEngine | null = null;

const App: React.FC = () => {
  const canvasRef = useRef<HTMLCanvasElement>(null);
  const [sceneKey, setSceneKey] = useState<'sceneone' | 'scenetwo' | 'scenethree'>('sceneone');
  const [isEngineInitialized, setIsEngineInitialized] = useState(false);

  useEffect(() => {
    const initializeEngine = async () => {
      if (!canvasRef.current) return;
      console.log('Initializing engine');
      if (!engineInstance) {
        engineInstance = new WebGPUEngine(canvasRef.current!, {
          glslangOptions: {
            jsPath: new URL("@babylonjs/core/assets/glslang/glslang.js", import.meta.url).href,
            wasmPath: new URL("@babylonjs/core/assets/glslang/glslang.wasm", import.meta.url).href
          },
          twgslOptions: {
            jsPath: new URL("@babylonjs/core/assets/twgsl/twgsl.js", import.meta.url).href,
            wasmPath: new URL("@babylonjs/core/assets/twgsl/twgsl.wasm", import.meta.url).href
          }
        });
        await engineInstance.initAsync();
        console.log('Engine initialized:', engineInstance);
        setIsEngineInitialized(true);
      }
    };

    initializeEngine().catch((error) => console.error('Error initializing engine:', error));
  }, []);

  useEffect(() => {
    let scene: any = null;

    const initializeScene = async () => {
      if (!canvasRef.current || !engineInstance || !isEngineInitialized) return;

      if (sceneKey === 'sceneone') {
        scene = await SceneOne(engineInstance, canvasRef.current!);
      } else if (sceneKey === 'scenetwo') {
        scene = await SceneTwo(engineInstance, canvasRef.current!);
      } else if (sceneKey === 'scenethree') {
        scene = await SceneThree(engineInstance, canvasRef.current!);
      }

      engineInstance.runRenderLoop(() => {
        if (scene && scene.render) {
          scene.render();
        }
      });

      console.log('Scene created:', scene);
    };

    initializeScene().catch((error) => console.error('Error initializing scene:', error));

    return () => {
      console.log('Cleaning up scene');
      if (scene) {
        console.log('Disposing scene');
        scene.dispose();
      }
    };
  }, [sceneKey, isEngineInitialized]);

  useEffect(() => {
    const handleKeyDown = (event: KeyboardEvent) => {
      if (event.key === 'g') {
        console.log('G key pressed');
        switchScene();
      }
    };

    window.addEventListener('keydown', handleKeyDown);

    return () => {
      window.removeEventListener('keydown', handleKeyDown);
    };
  }, [sceneKey]);

  const switchScene = () => {
    console.log('Current scene:', sceneKey);
    switch (sceneKey) {
      case 'sceneone':
        console.log('Switching to scenetwo');
        setSceneKey('scenetwo');
        break;
      case 'scenetwo':
        console.log('Switching to scenethree');
        setSceneKey('scenethree');
        break;
      case 'scenethree':
        console.log('Switching to sceneone');
        setSceneKey('sceneone');
        break;
      default:
        break;
    }
  };

  return (
    <div>
      <canvas ref={canvasRef} style={{ width: '100%', height: '400px' }} />
      <button onClick={switchScene}>Switch Scene</button>
    </div>
  );
};

export default App;

creation of sceneone.ts:

import { Scene, MeshBuilder, StandardMaterial, Color3, Color4, ArcRotateCamera, Vector3, HemisphericLight, WebGPUEngine } from '@babylonjs/core';

export const SceneOne = async (engine: WebGPUEngine, canvas: HTMLCanvasElement): Promise<Scene> => {
  const scene = new Scene(engine);
  scene.clearColor = new Color4(1, 1, 1, 1);

  const box = MeshBuilder.CreateBox('box', { size: 2 }, scene);
  const boxMaterial = new StandardMaterial('boxMaterial', scene);
  boxMaterial.diffuseColor = new Color3(0, 1, 0);
  box.material = boxMaterial;

  new HemisphericLight('light', new Vector3(0, 1, 0), scene);

  const camera = new ArcRotateCamera('camera', -Math.PI / 2, Math.PI / 2, 5, Vector3.Zero(), scene);
  camera.attachControl(canvas, true);

  return scene;
};
1 Like