Saltar al contenido

El fallback automático a WebGL

Cómo funciona por dentro la caída al backend de WebGL2, en qué momento exacto ocurre, y qué implica de verdad para tu código.

El mecanismo, paso a paso

Todo empieza en el constructor de WebGPURenderer:

JS
constructor( parameters = {} ) {  let BackendClass;  if ( parameters.forceWebGL ) {    BackendClass = WebGLBackend;  } else {    BackendClass = WebGPUBackend;    parameters.getFallback = () => {      warn( 'WebGPURenderer: WebGPU is not available, running under WebGL2 backend.' );      return new WebGLBackend( parameters );    };  }  const backend = new BackendClass( parameters );  super( backend, parameters );  // ...}

Fíjate en lo que no pasa aquí: no se comprueba si WebGPU existe. Salvo que pidas forceWebGL, el backend construido es siempre WebGPUBackend, y lo único que se hace es dejar preparada una función de respaldo en los parámetros.

La decisión se toma en init(), en la clase base Renderer:

JS
this._initPromise = new Promise( async ( resolve, reject ) => {  let backend = this.backend;  try {    await backend.init( this );  } catch ( error ) {    if ( this._getFallback !== null ) {      // try the fallback      try {        this.backend = backend = this._getFallback( error );        await backend.init( this );      } catch ( error ) {        reject( error );        return;      }    } else {      reject( error );      return;    }  }  // ...

Es un try/catch alrededor de la inicialización del backend, con un segundo intento con el de respaldo. Y lo que dispara el catch es el error que ya viste:

JS
if ( adapter === null ) {  throw new Error( 'WebGPUBackend: Unable to create WebGPU adapter.' );}

Cualquier fallo del camino de WebGPU acaba ahí: que navigator.gpu no exista, que el adaptador venga nulo, que la creación del dispositivo falle.

Y el detalle que hay que retener: this.backend se sustituye en el sitio. La propiedad pública del renderer cambia de objeto durante la inicialización. Por eso preguntar qué backend tienes antes de que init() resuelva no solo es inútil, es engañoso: siempre dirá WebGPUBackend.

Qué cambia realmente al caer

Lo que sigue funcionando es casi todo, y esa es la gracia.

Los materiales de nodos y todo tu TSL funcionan, porque el generador emite GLSL ES 3.0 en lugar de WGSL. Las geometrías, las luces, las sombras, el instancing, el skinning y los cargadores funcionan. El grafo de escena es el mismo. La pila de post-procesado basada en nodos funciona. En una escena típica no notarás la diferencia mirando la imagen.

Lo que no sigue funcionando es lo que depende de capacidades que WebGL2 no tiene:

Característica Con WebGPU Con el respaldo WebGL2
Compute shaders sí no
Storage buffers sí no
Texturas de almacenamiento sí no
Índices de dibujo indirecto sí no
Sistema de coordenadas de profundidad 0 a 1 -1 a 1
Consultas de marca de tiempo sí, si el adaptador lo permite no
Coste de validación por draw call bajo alto

Esa última fila es la que más se nota en escenas con muchos objetos, y es la diferencia estructural entre las dos APIs más que una característica concreta.

La fila del sistema de coordenadas es la que puede morderte en código propio. renderer.coordinateSystem devuelve WebGLCoordinateSystem o WebGPUCoordinateSystem según el backend activo, y si haces matemáticas con profundidad a mano tienes que consultarlo. TSL lo hace por ti en los nodos integrados.

Forzar el respaldo para probarlo

La ruta de respaldo es código que se ejecuta en las máquinas de tus usuarios y no en la tuya, así que hay que probarla a propósito:

JS
const params = new URLSearchParams( location.search );const renderer = new THREE.WebGPURenderer( {  antialias: true,  forceWebGL: params.has( 'webgl' )} );

Con eso, añadir ?webgl a la URL ejecuta toda la aplicación sobre el backend de WebGL2. Es la comprobación más barata que existe y conviene dejarla puesta durante todo el desarrollo.

Ten en cuenta que forceWebGL no es lo mismo que el respaldo automático: construye WebGLBackend directamente y no instala ninguna función de recuperación. Sirve para probar el destino, no el camino.

Detectar antes de construir

Si quieres saber si hay WebGPU antes de montar nada, Three.js trae una utilidad en los addons:

JS
import WebGPU from 'three/addons/capabilities/WebGPU.js';if ( WebGPU.isAvailable() ) {  // camino completo} else {  document.body.appendChild( WebGPU.getErrorMessage() );}

Es un export por defecto con exactamente dos métodos estáticos, isAvailable() y getErrorMessage(). Y tiene una peculiaridad importante: el módulo hace una comprobación con await de nivel superior al importarse, pidiendo un adaptador de verdad. Es decir, importar el módulo bloquea el grafo de módulos hasta que el adaptador responde. Es una comprobación fiable, no una simple mirada a navigator.gpu, pero conviene saber que tiene ese coste al cargar.