Eskiueskiu v0.7.0
Caso de estudio

Eskiu en la Nintendo 3DS

Un demo de realidad aumentada con cámara y giroscopio: un modelo 3D que se queda fijo sobre el video en vivo de la cámara, corriendo en una Nintendo 3DS con su lógica escrita en Eskiu y el render a cargo de ViroCore. Verificado corriendo en una 3DS real.
Una New Nintendo 3DS corriendo el demo de ReactVision y Eskiu: un modelo 3D low-poly de un shiba montado sobre el video en vivo de la cámara exterior en la pantalla de arriba, con los controles y diagnósticos por frame del demo en la pantalla de abajo.
El demo en una New Nintendo 3DS: el shiba montado sobre el video de la cámara exterior, con la interfaz que maneja Eskiu y los diagnósticos por frame (frames de cámara, unidad de transferencia, estado de la GPU) abajo.

La consola

La Nintendo 3DS monta un ARM11 (ARMv6k, 32 bits) como procesador principal y una GPU PICA200 de pipeline casi fijo. El homebrew para ella está muy maduro: devkitARM (un toolchain de GCC), libctru para el hardware, y citro3d / citro2d para la GPU. Los programas se distribuyen como un .3dsx reubicable que el Homebrew Launcher carga en cualquier dirección.

Todo ese ecosistema da por hecho C. La idea era volver a Eskiu, un lenguaje de sistemas nativo basado en LLVM, un ciudadano de primera en la consola: compilar Eskiu al ARM11, enlazarlo contra libctru, y manejar la cámara y la GPU desde código Eskiu.

Lo que faltaba

Eskiu ya cross-compila (su kernel de ejemplo arranca en ARM64 bajo QEMU), pero la 3DS dejó ver un hueco concreto: el compilador solo registraba los backends de LLVM AArch64 y x86. La 3DS es ARM de 32 bits, un backend totalmente distinto. Tres cosas se atravesaban entre "compila" y "arranca", y cada una daba una falla diferente:

Cada una terminó siendo una capacidad de verdad, y general, del compilador: un backend de ARM de 32 bits, y las banderas --mcpu, --mattr y --reloc. Con ellas, un "hola mundo" escrito en Eskiu enlaza contra libctru y arma un .3dsx.

Hablar con el hardware

Un programa necesita el SDK. Solo libctru son cientos de funciones y decenas de structs, y citro3d/citro2d suman cientos más. Escribir a mano las declaraciones extern es tedioso y una fuente de errores silenciosos y peligrosos: el orden equivocado de un campo o un valor de enum mal puesto corrompe la memoria sin avisar.

Por eso los bindings se generan, al estilo bindgen, a partir del AST en JSON que clang saca de los headers del SDK. Las funciones se vuelven declaraciones extern, los structs y unions se copian campo por campo, los valores de enum se leen ya evaluados, y lo que no se puede mapear con seguridad se salta con un // TODO en lugar de adivinarlo. Una sola corrida sacó bindings tipados de Eskiu para libctru (gráficos, entrada, eventos de GPU, consola), la cámara, citro3d (~140 funciones, 17 structs) y citro2d, del orden de 360 bindings tipados. El generador reprodujo por su cuenta, y bien, justo los detalles que a mano es fácil equivocar: el orden raro {x, z, y} del struct del giroscopio, un enum de eventos con valores implícitos, layouts empaquetados.

Lo que el AST no alcanza a expresar, los muchos static inline y macros de libctru que no dejan un símbolo enlazable, lo cubre una capa delgada hecha a mano, igual que los usuarios de bindgen agregan un shim chico.

El demo

La app es una pieza compacta de realidad aumentada: el video de la cámara exterior de fondo, y un modelo glTF (un shiba low-poly, ~4,300 triángulos) dibujado encima para que se quede quieto en el mundo mientras inclinas la consola. Las dos mitades del stack de AR son de ReactVision. ViroCore, el motor del caso de estudio de ReactVision, hace el render: carga y maneja el .glb igual que en las plataformas donde ya corre, y en la 3DS lo único que cambia es el backend del renderizador, que aquí apunta al PICA200 vía citro3d. El tracking usa el motor de SLAM propio de ReactVision, el mismo que va a impulsar su Web AR sin las APIs de WebXR, alimentado en la 3DS por el giroscopio de la consola. La cámara se captura con doble buffer y se sube a una textura de la GPU en cada frame, y el modelo se ilumina y se dibuja encima. Corre en hardware real de 3DS.

Su lógica se reescribió después en Eskiu: el bucle principal, pasar del giroscopio a la rotación, la secuencia de render por frame, las matrices del modelo y el manejo de memoria están todos en Eskiu, llamando a libctru, citro3d y el backend de render de ViroCore directo para cada símbolo real, y pasando por el shim solo para las llamadas de GPU llenas de static inline. El build de Eskiu genera el mismo .3dsx, y también se verificó corriendo en la consola.

El dogfooding mejoró el toolchain

Manejar hardware real es el tipo de presión que saca cosas a la luz. Cada uno de los tres bloqueos de arranque terminó siendo una capacidad del compilador que va más allá de la 3DS: ARM de 32 bits es un target soportado, y el modelo de reubicación, el CPU y las features se pueden elegir. El trabajo de cámara, hecho primero en C sobre la consola, dejó una referencia reutilizable y ya depurada: un bug en el tamaño de la unidad de transferencia, una ruta de error de buffer sin manejar, y una interacción de escritura de profundidad entre el fondo 2D y el modelo 3D, todo arreglado ahí antes del port.

El port también sacó dos huecos del lenguaje, y los dos ya están arreglados en el compilador. extern cubría funciones pero no símbolos de datos, así que un global de C como un handle de libctru no se podía usar directo; se agregaron las variables extern, y ahora un extern int x; enlaza un global de C. Y los literales float de Eskiu son double, algo que la frontera con C rechazaba al enlazar; ahora el compilador convierte el argumento float al tipo exacto del parámetro en la llamada. Restricciones chicas y concretas, de las que solo se aprenden subiendo código a un dispositivo real.


Así, el mismo lenguaje que arranca un kernel bare-metal en ARM64, incrusta un módulo de memoria ajustada en un motor de renderizado en C++ (ve el caso de estudio de ReactVision) y decodifica un formato criptográfico real sobre HTTP (ve el caso de estudio del INE) ahora compila al ARM11 de una consola de hace 12 años y maneja su cámara y su GPU, corriendo el mismo motor ViroCore sobre citro3d.