← Volver al blog

GlitchCore: Cuando C++ Conoce al Navegador

🚀 Ver Demo💻 GitHub▶️ Video

GlitchCore: Cuando C++ Conoce al Navegador

Todo comenzó con una frustración que probablemente muchos desarrolladores frontend conocen: una página web con animaciones que simplemente no corría a los FPS que necesitaba. Después de días optimizando Three.js y aprendiendo shaders en GLSL, una pregunta empezó a rondar mi cabeza: ¿Y si pudiera traer la velocidad bruta de C++ directamente al navegador?

Esa pregunta se convirtió en GlitchCore: un motor de procesamiento de imágenes escrito en C++ que corre a 60 FPS estables en tu navegador, sin plugins, sin extensiones, solo WebAssembly puro.

El Problema Real: No Era el Código, Era la Memoria

Cuando empecé a investigar WebAssembly, encontré que la tecnología en sí era bastante directa. Compilas C++ con Emscripten, generas un archivo .wasm, lo cargas en JavaScript y listo. Simple, ¿verdad?

Pues no tanto.

El verdadero cuello de botella apareció cuando intenté procesar imágenes en tiempo real. Estamos hablando de millones de píxeles que necesitan ser manipulados 60 veces por segundo. El flujo tradicional sería:

  • JavaScript lee los píxeles de un canvas
  • Los copia a un buffer que WebAssembly pueda leer
  • C++ procesa los datos
  • Los resultados se copian de vuelta a JavaScript
  • JavaScript los pinta en el canvas
  • Cada uno de esos pasos de "copia" es un asesino de rendimiento. Para una imagen de 1920x1080, estamos hablando de copiar 8 millones de bytes... 60 veces por segundo. Los números simplemente no cuadraban.

    La Solución: Arquitectura Zero-Copy

    La solución fue eliminar las copias por completo. En lugar de mover datos entre JavaScript y C++, ambos trabajan sobre la misma región de memoria.

    ¿Cómo funciona? WebAssembly tiene acceso a un bloque de memoria lineal (el famoso HEAPU8 de Emscripten). Cuando C++ procesa una imagen, escribe directamente en esta memoria. Del lado de JavaScript, en lugar de copiar esos datos, simplemente creamos una vista que apunta a la misma dirección de memoria.

    El resultado: cero copias, cero overhead. Solo píxeles fluyendo del heap de C++ directo a tu pantalla.

    Los Shaders: Por Qué C++ y No WebGL

    Una pregunta válida sería: ¿Por qué no usar WebGL para esto? Después de todo, la GPU está diseñada exactamente para este tipo de procesamiento paralelo.

    La respuesta está en el tipo de efectos que quería implementar. Algunos algoritmos son inherentemente secuenciales o requieren acceso a datos de formas que las GPUs no manejan bien:

    Pixel Sorting (Melting Effect): Este efecto ordena columnas de píxeles por su luminancia. Ordenar es una operación que se beneficia de algoritmos como QuickSort, que en la GPU serían un dolor de cabeza implementar.

    Sobel Edge Detection: Aunque las convoluciones pueden hacerse en GPU, implementarlas en C++ me dio más control sobre optimizaciones específicas y fue más rápido de iterar.

    Interactive Lens: Un efecto tipo lupa que aplica distorsiones matemáticas solo dentro de un radio del cursor. La GPU tendría que calcular esto para cada píxel de la imagen; en C++, puedo optimizar para solo procesar los píxeles afectados.

    En total, el engine incluye 11 efectos diferentes: Swirl, Jitter, Mosaic, Solarize, RGB Noise, Scanlines, Ripple, y más.

    El Stack Técnico

    Para los que les interese replicar algo similar, este es el stack que usé:

  • C++17 con la Standard Template Library para la lógica de procesamiento
  • Emscripten para compilar a WebAssembly
  • React + TypeScript para la interfaz
  • Vite como bundler
  • Strategy Pattern en C++ para manejar los diferentes efectos de manera limpia
  • El patrón Strategy fue clave para mantener el código organizado. Cada efecto es una clase que implementa una interfaz común, lo que hace trivial agregar nuevos shaders sin tocar el código existente.

    Lecciones Aprendidas

    Después de este experimento, algunas cosas me quedaron claras:

  • WebAssembly no es magia. Si no diseñas tu arquitectura pensando en minimizar la comunicación entre JS y Wasm, no vas a ver mejoras de rendimiento.
  • La memoria compartida es poderosa. Entender cómo funcionan los punteros y el heap de Wasm te abre posibilidades que no tendrías de otra forma.
  • C++ en el frontend es viable. Para casos de uso específicos donde el rendimiento es crítico, WebAssembly compilado desde C++ o Rust es una herramienta legítima.
  • No todo debe ir a la GPU. A veces, un algoritmo bien optimizado en CPU (especialmente compilado a Wasm) puede superar a una implementación genérica en shader.
  • ¿Qué Sigue?

    Este proyecto fue un experimento de aprendizaje, pero me dejó pensando en las posibilidades. ¿Editores de imagen en el navegador con rendimiento nativo? ¿Filtros de video en tiempo real sin depender de APIs propietarias? ¿Juegos 2D con física compleja corriendo a 60 FPS constantes?

    WebAssembly está abriendo puertas que antes estaban cerradas para el desarrollo web. Y apenas estamos empezando a explorar lo que es posible.


    ¿Te interesa explorar el código? El repositorio está en GitHub y hay una demo en vivo donde puedes probar todos los efectos. Los enlaces están arriba de este artículo.