Los vecinos del mapa: WebGL, WebGPU y la Paint API
Delimitar el territorio del 2D situando lo que queda justo fuera de él, y saber cuándo un problema deja de ser de canvas o SVG y pasa a ser de otra cosa.
Un elemento, cuatro contextos
El elemento <canvas> no es “el canvas 2D”. Es una superficie de dibujo genérica a la que pides un contexto, y el contexto determina qué API tienes. Los identificadores válidos son cuatro.
const c = document.createElement('canvas');c.getContext('2d'); // CanvasRenderingContext2Dc.getContext('webgl'); // WebGLRenderingContext (WebGL 1)c.getContext('webgl2'); // WebGL2RenderingContextc.getContext('webgpu'); // GPUCanvasContextc.getContext('bitmaprenderer');// ImageBitmapRenderingContextSon cinco identificadores en realidad, contando bitmaprenderer, que no dibuja nada: solo muestra un ImageBitmap que produjiste en otro sitio. Y hay una regla que sorprende a todo el mundo la primera vez: un canvas solo admite un tipo de contexto en toda su vida. Una vez que has pedido "2d", pedir "webgl" sobre el mismo elemento devuelve null para siempre. Si necesitas los dos modelos, necesitas dos elementos.
La diferencia entre el contexto 2D y los de GPU no es de potencia sino de nivel de abstracción. El contexto 2D te da primitivas con significado: rutas, texto, imágenes, degradados, composición. WebGL y WebGPU te dan triángulos, búferes y programas que se ejecutan por vértice y por píxel. Dibujar un rectángulo con esquinas redondeadas y un borde punteado son dos líneas en 2D y varios cientos en WebGL, porque en WebGL tienes que teselar la forma, escribir el sombreador que decide el color de cada fragmento y montar la maquinaria de estado.
Cuándo el 2D deja de bastar
Hay tres señales claras, y ninguna es “quiero que vaya rápido”.
La primera es el cálculo por píxel. Si lo que quieres es aplicar una transformación a cada píxel de la imagen —un desenfoque grande, una distorsión, un efecto de iluminación, un mezclado de varias texturas— el 2D te obliga a hacerlo en JavaScript sobre un array, píxel a píxel, en el hilo principal. En GPU eso es un sombreador de fragmentos que se ejecuta en paralelo sobre millones de píxeles. La diferencia no es de dos veces: es de dos o tres órdenes de magnitud.
La segunda es la tercera dimensión de verdad: perspectiva, profundidad con búfer de profundidad, iluminación con normales, cámaras. Se puede simular una pseudoperspectiva en 2D proyectando a mano, y para un efecto aislado compensa; para una escena, no.
La tercera es la cantidad de instancias con estado propio. Cien mil partículas con su posición, velocidad y color, actualizadas cada fotograma, son un caso claro de cómputo en GPU: los datos ya están en memoria de vídeo y no tienen por qué volver.
Fuera de esas tres, el 2D es la respuesta, y no por comodidad: los navegadores modernos aceleran el canvas 2D por GPU internamente. Cuando rellenas un rectángulo, hay bastantes posibilidades de que eso acabe siendo una llamada de dibujo en la GPU igualmente, solo que sin que tú hayas tenido que escribirla.
La Paint API y los filtros que CSS toma de SVG
Hay dos puentes entre los modelos que conviene tener en el mapa porque resuelven casos concretos muy bien.
El primero es la CSS Painting API, parte de Houdini. Permite registrar un módulo de trabajo con una clase que tiene un método paint(ctx, size, props), donde ctx es un contexto 2D restringido, y usar el resultado como valor de background-image mediante paint(). La ventaja es que ese dibujo se comporta como CSS: se recalcula cuando cambia el tamaño del elemento, participa en la cascada, lee propiedades personalizadas. La desventaja es el soporte: es Chromium desde hace años y sigue sin estar en los otros motores, así que solo sirve como mejora progresiva sobre un fondo de reserva.
// paint-rayas.js, registrado con CSS.paintWorklet.addModule('paint-rayas.js')registerPaint('rayas', class { static get inputProperties() { return ['--raya-color', '--raya-ancho']; } paint(ctx, size, props) { const color = props.get('--raya-color').toString().trim() || '#89b4fa'; const ancho = parseFloat(props.get('--raya-ancho')) || 8; ctx.fillStyle = color; for (let x = -size.height; x < size.width; x += ancho * 2) { ctx.beginPath(); ctx.moveTo(x, size.height); ctx.lineTo(x + size.height, 0); ctx.lineTo(x + size.height + ancho, 0); ctx.lineTo(x + ancho, size.height); ctx.fill(); } }});El segundo puente va en la otra dirección: CSS puede usar recursos SVG. Las propiedades filter, clip-path y mask aceptan una referencia a un elemento SVG del documento, y con eso obtienes desde CSS toda la potencia del pipeline de filtros de SVG sin salir del modelo de cajas. Es el atajo que resuelve una gran parte de los efectos que la gente intenta hacer a mano en canvas.
.foto { filter: url(#papel); clip-path: url(#recorte); }El resto de los vecinos
Quedan dos que se confunden menos pero conviene nombrar.
El vídeo es un modelo aparte: un flujo de fotogramas decodificado por hardware que el compositor pinta directamente. Su relación con el canvas es que un <video> es una fuente válida para drawImage, lo que convierte al canvas en la herramienta estándar para analizar o procesar fotogramas de vídeo y de cámara. Esa es la puerta de entrada a la visión por computador en el navegador.
Las fuentes tipográficas son, técnicamente, gráficos vectoriales con un motor de rasterización propio y muy afinado, con sugerencias de rejilla y suavizado subpíxel que ni SVG ni canvas replican. Es la razón de que el texto renderizado por el navegador se vea mejor que el mismo texto dibujado a mano sobre una rejilla, y un argumento serio para no meter texto largo dentro de un canvas.
Con eso el mapa está cerrado: en el centro los tres modelos, y alrededor la GPU, la Paint API, los filtros SVG usados desde CSS, el vídeo y el texto. Todo lo demás son librerías construidas encima de alguno de ellos.