requestAdapter: elegir GPU sin poder verla
Las cuatro opciones del descriptor de adaptador, qué son en realidad una preferencia y no una orden, qué información expone adapter.info y por qué está deliberadamente empobrecida.
Un adaptador representa una implementación de WebGPU sobre una GPU concreta. Pedirlo parece elegir hardware y no lo es: todas las opciones son pistas que el navegador puede ignorar, y la información que devuelve está intencionadamente recortada para no convertirse en huella digital. Saber qué controlas de verdad y qué no evita construir lógica sobre suposiciones que la especificación no garantiza.
- Enumerar las opciones de
GPURequestAdapterOptionsy su efecto real. - Explicar qué significa el modo de compatibilidad y cuándo interesa.
- Leer
adapter.info,adapter.featuresyadapter.limitscon el escepticismo adecuado. - Justificar por qué la información del adaptador está limitada por privacidad.
Las cuatro opciones
El descriptor tiene exactamente cuatro miembros, todos opcionales:
const adapter = await navigator.gpu.requestAdapter({
powerPreference: 'high-performance',
featureLevel: 'core',
forceFallbackAdapter: false,
xrCompatible: false,
});
powerPreference admite 'low-power' y 'high-performance', y si se omite no hay preferencia. En un portátil con gráficos conmutables, 'high-performance' pide la GPU discreta y 'low-power' la integrada. Es la opción con más efecto práctico y también la más fácil de usar mal: pedir la discreta en una visualización pequeña que se ve durante horas cuesta batería y calor sin ganar nada perceptible. La regla razonable es 'high-performance' solo cuando el trabajo lo justifique y 'low-power' o nada para el resto.
Y una advertencia importante: es una preferencia, no una orden. El navegador puede ignorarla por política de energía, porque el usuario está con batería, o porque la implementación no distingue GPUs en esa plataforma. No hay forma de verificar que se te ha concedido.
featureLevel es una cadena con dos valores definidos, 'core' —el valor por defecto— y 'compatibility'. El modo de compatibilidad es un subconjunto de WebGPU con límites más bajos y restricciones adicionales, pensado para alcanzar dispositivos cuyo hardware o controlador no llega al conjunto completo: GPUs móviles antiguas, sistemas con OpenGL ES 3.1 o Direct3D 11 por debajo. Si pides un nivel que la implementación no soporta, requestAdapter() resuelve a null.
forceFallbackAdapter pide explícitamente un adaptador de reserva por software. La especificación lo define y las implementaciones no están obligadas a ofrecer uno; en la práctica, en 2026 esta opción devuelve null en la mayoría de configuraciones. Es útil como herramienta de prueba conceptual, no como estrategia de despliegue.
xrCompatible pide un adaptador compatible con el dispositivo de realidad virtual o aumentada activo. En un sistema con varias GPUs, el visor puede estar conectado a una concreta, y sin esta pista podrías recibir la otra.
Nada impide llamar a requestAdapter() varias veces con opciones distintas antes de decidir. Un patrón razonable es pedir 'high-performance', mirar adapter.info y adapter.limits, y si el resultado no convence pedir otra vez con otra preferencia. Lo que no se puede es pedir dos dispositivos del mismo adaptador.
Qué te cuenta el adaptador
Tres propiedades, y las tres hay que leerlas con cuidado.
adapter.features es un GPUSupportedFeatures, un conjunto de solo lectura con las capacidades opcionales que este adaptador ofrece. Se consulta con has() y se puede recorrer:
console.log([...adapter.features].sort().join('\n'));
// texture-compression-bc
// timestamp-query
// depth32float-stencil8
// ...
adapter.limits es un GPUSupportedLimits con los valores máximos que este adaptador acepta. Merece su propia lección porque el matiz entre lo garantizado y lo real es el que más problemas causa.
adapter.info es un GPUAdapterInfo síncrono con siete campos: vendor, architecture, device, description, subgroupMinSize, subgroupMaxSize e isFallbackAdapter. Los cuatro primeros son cadenas que pueden estar vacías, y con frecuencia lo están.
const info = adapter.info;
console.log(info.vendor, info.architecture, info.device, info.description);
console.log('subgrupo entre', info.subgroupMinSize, 'y', info.subgroupMaxSize);
console.log('es adaptador de reserva:', info.isFallbackAdapter);
Aquí hay dos detalles que la documentación antigua contradice. El primero: adapter.info es una propiedad síncrona; el antiguo método requestAdapterInfo(), que devolvía una promesa, se eliminó de la especificación. El segundo: isFallbackAdapter vivía en GPUAdapter y ahora vive en GPUAdapterInfo. Si encuentras código con adapter.isFallbackAdapter, está desactualizado.
Existe además device.adapterInfo, que da acceso a la misma información desde el dispositivo, lo cual es cómodo cuando no has conservado el adaptador.
Por qué la información está recortada
Los cuatro campos de texto de adapter.info están deliberadamente empobrecidos, y la razón es la huella digital.
Cada bit de información que una página puede leer sobre el hardware del usuario contribuye a identificarlo de forma única entre millones. El modelo exacto de GPU, la versión del controlador y la lista completa de capacidades componen una firma muy discriminante, y combinada con otras señales permite seguir a un usuario entre sesiones sin cookies. WebGL cometió ese error con la extensión WEBGL_debug_renderer_info, que devolvía la cadena completa del renderizador y se convirtió en una de las señales de huella digital más usadas de la web.
WebGPU aprendió la lección y aplica tres mitigaciones.
Campos vacíos por defecto. La especificación permite que vendor, architecture, device y description sean cadenas vacías, y los navegadores devuelven cadenas genéricas o vacías salvo en configuraciones concretas.
Límites escalonados. Las implementaciones no reportan el límite exacto del hardware sino el escalón inferior de una lista corta de valores. Si tu GPU admite 16384 de dimensión de textura y los escalones son 2048, 8192 y 32768, se reporta 8192. Eso reduce mucho la entropía, y tiene una consecuencia práctica: el valor que lees en adapter.limits es una cota inferior conservadora, no la capacidad real.
Un conjunto de features corto. Hay pocas capacidades opcionales, deliberadamente, para que la combinación no sea identificativa.
De ahí sale la regla de diseño que hay que aplicar: no ramifiques por fabricante ni por modelo. La cadena que lees puede estar vacía, puede ser genérica, y puede cambiar entre versiones del navegador. Si necesitas adaptar el comportamiento, hazlo consultando features y limits, o midiendo el rendimiento real, que es lo que de verdad te interesa.
La reacción normal al descubrir que los límites vienen escalonados y la información recortada es de frustración: cómo optimizo para un hardware que no puedo identificar. La reacción productiva es la contraria, y cambia cómo se diseña la aplicación.
Los datos del adaptador sirven para decidir qué no intentar, no para decidir qué hacer. Si adapter.limits.maxStorageBufferBindingSize reporta 128 MiB, sabes con certeza que no puedes pedir 256; no sabes si el hardware los tiene. Es información de exclusión, y como tal es fiable. Cualquier lógica que asuma la dirección contraria —«si reporta 8192 entonces es una GPU de gama media, así que bajo la calidad»— está construida sobre un valor que el navegador se ha inventado para protegerte.
Lo que sí funciona, y es lo que hacen los productos que rinden bien en un parque heterogéneo, es medir en lugar de preguntar. Renderiza los primeros segundos con una configuración conservadora, mide el percentil 95 del tiempo de fotograma, y sube o baja la calidad en consecuencia. Es más código y es la única señal que refleja lo que de verdad importa, que no es qué GPU hay sino cuántos milisegundos tarda esta escena en este dispositivo con esta resolución y con lo que el usuario tenga abierto en las otras pestañas. Ninguna cadena de identificación te iba a decir eso.
Con el adaptador en la mano, toca convertirlo en algo que trabaje: requestDevice() y las features.