Model animation flickering on WebGPU (shapekeys animation)

Tested on Version “@babylonjs/core”: “^8.37.1”

Video
https://github.com/user-attachments/assets/8f34cd11-2ec6-49f3-bcaf-e6c69930deed

Desktop:

  • OS: Mac M4 - 15.7.1

  • Browser: chrome

Additional context

Hi, I noticed that model flickers during animation as shown in video on WebGPU in version 8.37.1 (works well on 7.42.0) when I switch to WebGL flickering is gone. Below minimal code for reproduction:

// MinimalAnimDemo.js
import { Engine } from '@babylonjs/core/Engines/engine';
import { WebGPUEngine } from '@babylonjs/core/Engines/webgpuEngine';
import { Scene } from '@babylonjs/core/scene';
import { ArcRotateCamera } from '@babylonjs/core/Cameras/arcRotateCamera';
import { Vector3 } from '@babylonjs/core/Maths/math.vector';
import { HemisphericLight } from '@babylonjs/core/Lights/hemisphericLight';
import { TransformNode } from '@babylonjs/core/Meshes/transformNode';
import { SceneLoader } from '@babylonjs/core/Loading/sceneLoader';

import '@babylonjs/loaders/glTF';

export class MinimalAnimDemo {
  static async create(canvas, { modelUrl, dpr = 1 } = {}) {
    if (!canvas) throw new Error('Canvas is required');
    if (!modelUrl) throw new Error('modelUrl is required');

    const engine = await MinimalAnimDemo._createEngine(canvas, dpr);

    const scene = new Scene(engine);

    const camera = new ArcRotateCamera(
      'cam',
      Math.PI / 4,
      Math.PI / 3,
      2.5,
      new Vector3(0, 0.8, 0),
      scene
    );
    camera.attachControl(canvas, true);

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

    const root = new TransformNode('root', scene);

    const result = await SceneLoader.ImportMeshAsync('', '', modelUrl, scene);

    result.meshes.forEach((m) => {
      if (m === scene.meshes[0]) return;
      m.parent = root;
    });

    if (result.meshes.length > 0) {
      const bb = result.meshes[0].getHierarchyBoundingVectors(true);
      const center = bb.min.add(bb.max).scale(0.5);
      camera.target = center;
    }

    console.log(
      '[MinimalAnimDemo] loaded animationGroups:',
      result.animationGroups.length
    );

    const app = new MinimalAnimDemo(engine, scene, camera, root, {
      animationGroups: result.animationGroups || [],
    });

    // 4) Render loop
    engine.runRenderLoop(() => {
      scene.render();
    });

    // resize
    const onResize = () => engine.resize();
    window.addEventListener('resize', onResize);
    app._onResize = onResize;

    console.log(scene);

    scene.animationGroups.forEach((g) => {
      g.stop();
      g.reset();

      g.play(true); // loop = true
    });

    return app;
  }

  static async _createEngine(canvas, dpr) {
    const hasNavigatorGPU = typeof navigator !== 'undefined' && !!navigator.gpu;

    if (hasNavigatorGPU) {
      const engine = new WebGPUEngine(canvas, {
        adaptToDeviceRatio: false,
      });
      await engine.initAsync();
      engine.setHardwareScalingLevel(1 / dpr);
      console.log('[MinimalAnimDemo] WebGPU engine');
      return engine;
    }

    const engine = new Engine(canvas, true);
    engine.setHardwareScalingLevel(1 / dpr);
    console.log('[MinimalAnimDemo] WebGL engine');
    return engine;
  }

  constructor(engine, scene, camera, root, { animationGroups }) {
    this.engine = engine;
    this.scene = scene;
    this.camera = camera;
    this.root = root;
    this.animationGroups = animationGroups || [];

    this._rotationObserver = null;
  }

  dispose() {
    if (this._onResize) {
      window.removeEventListener('resize', this._onResize);
      this._onResize = null;
    }

    this.scene.dispose();
    this.engine.dispose();
  }
}

Welcome aboard!

Can you setup a repro in the Playground?

This will make it easier for us to help you and find the problem more quickly.

If you need to host a file to use it in Playground, this page may be useful.