Uno de los programadores mas prolíficos y que mas jugo le están sacando al C64 a un ritmo vertiginoso y cada vez con mas exquisitez es nuestro invitado Dr MortalWombat, conocido por su amplio catálogo videojueguil y seguramente mas por el compilador de C para nuestro sistema, el Oscar64, que no para de optimizar, actualizar y que cada vez mas gente usa para sus propios proyectos. Hablar de DMW es hacerlo de un programador nato, que desde muy joven tuvo claro lo que le gustaba y su historia, sus inicios y como muchas veces nuestra profesión va ligada a nuestros hobbys.
–Os dejamos con el propio DrMortalWombat:
–Mis primeros recuerdos de programación son escribiendo ensamblador en tarjetas perforadas para un sistema IBM /360. Nada complejo, debía de estar en la escuela primaria por aquel entonces, así que era el equivalente a BASIC de una línea, pero con una impresora de cinta de acero como salida. Mi padre era el administrador de un centro informático local, así que tuve acceso a él.
En el 82 conseguí mi propio VIC20 y, como era de esperar, quedé asombrado por los gráficos y el sonido, pero completamente decepcionado por el lento BASIC. Así que, naturalmente, escribí mi propio ensamblador en BASIC para aprovechar la «potencia» del lenguaje de máquina del 6502. Esta pequeña CPU me impresionó más que el gran mainframe. La cantidad de potencia de cálculo en este pequeño chip era y es simplemente asombrosa; Vendí varios juegos pequeños a revistas de informática y utilicé el dinero para actualizarlo a un C128 (que todavía tengo y utilizo) a finales del 85.
Por aquel entonces, mi cerebro funcionaba de forma nativa con el ensamblador 6502 y no con mi lenguaje natural, así que siempre me inquietaba ver cómo mis amigos tenían dificultades con el ensamblador, pero no tenían grandes problemas con BASIC. Así que empecé a escribir compiladores de BASIC cada vez más potentes. También hice algunos pequeños trabajos en ensamblador x86 para la tienda de mi padre en aquella época, y esto inició un odio eterno hacia todo lo que tuviera una herencia 8080.
Durante mi época en el ejército, a partir del 88, conocí Turbo Pascal y me enamoré inmediatamente de él. En mi primer semestre universitario aprendimos Modula2 y tuvimos el compilador MacMETH en un Mac. Por aquel entonces tenía un Amiga, que programaba en ensamblador de 68K. Me encantaba el lenguaje Modula2 y la velocidad del compilador, pero el código compilado no era lo suficientemente bueno para mí - así que usé este compilador para arrancar mi propio compilador Modula2 e IDE integrado - para el Amiga. Esto lo convertí en un producto comercial.
Entré en contacto con GVP (uno de los mayores fabricantes estadounidenses de periféricos para Amiga) y empecé a trabajar para ellos durante mis años universitarios. Muchos antiguos ingenieros de Commodore trabajaban allí, así que tuve el placer de conocer y trabajar codo con codo con algunos de los tipos que desarrollaron el hardware que todos amamos.
En el 95, con el fin de la era Amiga, algunos de mis amigos y algunas de las personas principales de GVP establecieron nuevas empresas que ahora hacen productos de vídeo digital para el PC. Aquí es donde tuve que abandonar dos de mis plataformas amadas 68k y Modula2 y trabajar en el lado oscuro de x86 y C++ - y esto no ha cambiado desde entonces.
Obtuve mi doctorado en informática en 2000 y trabajo como autónomo desde 2005. Actualmente me dedico sobre todo a la ingeniería de rendimiento y al código DSP, pero he construido sistemas de entretenimiento de consumo integrados, sistemas de IA, motores de juegos de ordenador, plataformas de servidores, compiladores, software de análisis de datos y un largo etcétera.
Durante la pandemia empecé a limpiar el sótano y encontré mi viejo C128 de nuevo... bueno y el resto es historia.
Construí los dos primeros juegos nuevos («Plekthora» y «Gates of the Ancient») en ensamblador con el IDE C64Studio, pero decidí utilizar algo más potente para el siguiente juego («Shallow Domains»).
Los compiladores existentes parecían problemáticos, KickC no era realmente C y el código generado por CC65 no estaba a la altura del rendimiento y tamaño que necesitaría para mi juego planeado. Así que decidí construir mi propio compilador para el 6502. Estuve dudando entre Modula2 y C durante un tiempo, pero luego me decidí por C porque era el lenguaje de más bajo nivel.
![]() |
Gates of the Ancient |
Rebusqué en viejos proyectos y encontré dos que podían servirme de base para el compilador: un analizador de Javascript y el backend x64 de un compilador en el que había trabajado hace unos 15 años.
En ese momento yo estaba bajo la suposición que el 6502 no sería un buen objetivo para cualquier lenguaje de nivel superior, debido a la falta de una pila de valores suficientes y la falta de instrucciones de 16 bits. La idea era tener dos generadores de código, uno generando un código byte que sería interpretado para las partes no críticas en tiempo, para ahorrar tamaño de código y un generador nativo para las zonas calientes.
El proyecto se inició el 21 de agosto de 2021 y los primeros programas compilados en C64 comenzaron a ejecutarse el 29 de agosto. Con el compilador y el interprete de código byte funcionando, comencé con el generador de código nativo el 7 de septiembre y alcancé el mismo nivel funcional el 11 de septiembre. Al principio, la ventaja en tamaño de código del código byte era bastante significativa, pero cuanto más tiempo dedicaba a mejorar el optimizador de código nativo más se acercaban los dos - y finalmente el generador de código nativo superó al de código byte.
Así que, por el momento, el generador de código byte sólo se utiliza con fines de validación y ya no tiene ninguna aplicación en el mundo real.
Resulta que el 6502 es un gran objetivo para un compilador de C, si el optimizador y el generador de código están diseñados de principio a fin para trabajar con sus puntos fuertes y no con sus debilidades. Las tres mayores mejoras en la calidad del código vinieron de:
- El análisis estático del gráfico de llamadas se utiliza para eliminar la pila de llamadas siempre que sea posible y utilizar la página cero o el direccionamiento absoluto para las variables locales y los parámetros. Sólo los punteros de función y la recursividad pueden acabar utilizando la pila de valores.
- El análisis estático del rango de enteros permite simplificar muchas operaciones de 16 bits a operaciones de 8 bits. El compilador puede simplemente probar que los valores no superan el rango más pequeño.
- Conversión de direccionamiento indirecto a direccionamiento indexado cuando la aritmética de punteros o la indexación de matrices cabe en un único registro de índice.
![]() |
Portal Buster |
Con cada nuevo juego que escribo, el compilador sigue mejorando. Dedico alrededor del 25% de mi tiempo de desarrollo para cada juego a conseguir que el compilador genere código más rápido o más pequeño.
Intento evitar cualquier tipo de ensamblador en mis juegos (aunque el compilador soporta una integración fluida de ensamblador en línea y código C). Siempre que siento la necesidad de añadir una sección de ensamblador, mejoro el compilador para no tener que añadirla - y esta política ha funcionado muy bien hasta ahora.
Oscar64 es un compilador y enlazador combinado. Compila, optimiza, enlaza y genera un archivo ejecutable (ya sea un .prg o un cartucho .crt) con una simple invocación, sin necesidad de pasos adicionales de compilación. Así que supongamos que tenemos un archivo fuente en C «hola.c» llamando al compilador con «oscar64 hola.c» generará un «hola.prg» que puede ser iniciado con, por ejemplo, el Vice. Esta compilación de todo el programa no sólo es conveniente al eliminar la necesidad de un makefile, sino que también permite al compilador invocar optimizaciones más complejas. Esta es una de las ventajas de la compilación cruzada en un ordenador que es órdenes de magnitud más potente en velocidad y tamaño de memoria que el sistema de destino.
Programar juegos para un microordenador como el C64 implica frecuentes accesos directos a la memoria o a los registros IO. En BASIC esto se haría con PEEK o POKE, en ensamblador con direccionamiento absoluto. En C la forma natural es utilizar punteros:
static char * const Screen = (char *)0x400; for(char i=0; i<40; i++) Screen[i] = 81;
Este bucle llenará la primera línea de memoria de pantalla con círculos.
Para los registros IO es más fácil usar las estructuras predefinidas en la librería como por ejemplo:
#include <c64/vic.h> vic.color_back = 2; vic.color_border = 3;
Frecuentemente hay tres formas de acceder a los componentes de hardware con Oscar64, se puede meter directamente con un puntero, se pueden utilizar los registros definidos en los archivos de cabecera o utilizar funciones de nivel superior definidas en esos archivos de cabecera, para por ejemplo la manipulación de sprites:
void vic_sprxy(byte s, int x, int y);
Existen métodos de biblioteca similares para leer la entrada del usuario desde el joystick, el teclado, los paddles o el ratón.
La siguiente cuestión importante es cómo introducir recursos, como fuentes, gráficos o sprites en el programa. Oscar64 admite el comando de preprocesador #embed, que traduce los archivos binarios a números separados por comas, que pueden utilizarse fácilmente como inicializadores literales constantes:
const char MilitaryFont[] = { #embed "../Resources/militaryfont.bin" }
Oscar64 va incluso más allá proporcionando compresión y analizador sintáctico (parsers) para recursos comunes de C64 como archivos Charpad o Spritepad.
static const char BeltChars[] = { #embed ctm_chars lzo "belt.ctm" };
Otro elemento frecuente en la programación de juegos de C64 son las interrupciones del barrido de pantalla (raster). En Oscar64 se puede declarar una función como «interrupt», lo que asegurará que guarde todas las localizaciones de página cero que utilice para el proceso. Un concepto de nivel superior se implementa en la biblioteca rasterirq, que proporciona una especie de copper. La librería hace la clasificación y el manejo de las interrupciones y el usuario de la librería simplemente tiene que especificar las líneas raster y los registros IO a cambiar. Esta librería es también la base de la librería sprite multiplexer.
El último tema que quiero tocar aquí es la biblioteca de ejemplos/tutoriales específicos de juegos en github: https://github.com/drmortalwombat/OscarTutorials
Además de los archivos .prg o .crt Oscar64 genera otros archivos durante la compilación que ayudan con la depuración y la creación de perfiles. El archivo .map proporciona un desglose del tamaño y ubicación de todas las funciones y variables globales para obtener una visión general del uso de memoria. Un listado más detallado, mostrando el tamaño del código de cada línea fuente se genera como un archivo .csz con la opción -gp. Este archivo asigna a cada línea de código fuente de su programa una dirección de memoria y un tamaño en bytes.
A la inversa, el archivo .asm asigna las direcciones de memoria al código fuente e incluye referencias al número de línea si se genera con las opciones -g o -gp. Hacer uso de este archivo requiere algún conocimiento del 6502 para entender qué y por qué el compilador generó varias secuencias de código.
Cuando se utiliza Vice para emulación y depuración, el archivo .lbl será muy útil, ya que proporciona símbolos para el programa y permite así cierto nivel de depuración simbólica. Puede cargarse en Vice con el argumento de línea de comandos -moncommands.
El compilador intenta generar un código óptimo, pero está limitado por las reglas del lenguaje C, el 6502 y el código fuente de tu juego. Si crees que el código generado no está a la altura, hay varias formas de mejorarlo. El primer paso es comprobar los niveles del optimizador. El nivel por defecto es O1 que ya hace muchas optimizaciones locales, pero está limitado en la cantidad de intercalado de código ("inlining") automático que realiza. El nivel principal del optimizador O2 intercala el código ("inline") con la mayoría de las funciones que parecen ser beneficiosas en tamaño y velocidad. También realiza muchas optimizaciones entre procedimientos. El modo más agresivo O3 desenrolla muchos bucles y también intercala el código ("inlines") de funciones, aunque el tamaño del código aumente.
Se puede conseguir aún más ayudando al compilador. Elegir el tipo de dato correcto ayuda mucho - si un valor cabe en un byte, usa un char en lugar de un int. El compilador hace un análisis estático del rango de valores para predecir el rango potencial de valores de cada expresión entera, pero no puede saberlo cuando carga un valor de la memoria o lo obtiene como argumento de una función. La sentencia __assume puede ayudar al compilador a conocer el rango potencial de un valor, por ejemplo para coordenadas de pantalla:
static const char BeltChars[] = { #embed ctm_chars lzo "belt.ctm" };
Otro uso potencial de __assume es marcar código inalcanzable con __assume(false) para evitar que se genere código para él.
Otra sugerencia de optimización controlada por el programador es el desenrollado de bucles. Esto puede ser controlado con un #pragma para desenrollar completamente (también conocido como speedcode), desenrollar en trozos de n ó desenrollar de manera que la variable de índice se convierta en un solo byte y por lo tanto quepa en el registro x o y.
El último tema que quiero tratar aquí es la disposición de la memoria. La disposición común de un array de structs coloca un struct completo tras otro en la memoria. Esto tiene dos desventajas cuando se accede a elementos individuales del array, primero calcular la dirección requiere una multiplicación y segundo acceder necesita direccionamiento indirecto y aritmética de punteros. Por otro lado, una estructura de matrices cortas sólo necesitaría un direccionamiento indexado absoluto. Oscar64 soporta la traducción fácil y transparente de un array de structs como un struct de arrays con el calificador de almacenamiento __striped.
–Para finalizar y a modo de despedida, le preguntamos a DrMortalwombat las razones por las que usar Oscar64. Su respuesta fue:
–Entonces, ¿por qué utilizar C para escribir juegos para el C64?
- Si es nuevo en el C64 pero un veterano en C tienes una entrada menos dolorosa
- Si es nuevo en el C64 y nuevo en C esta es la entrada ideal a uno de los principales ancestros de la mayoría de los lenguajes modernos
- Si es un veterano en el C64 y un gran hacker del ensamblador 6502, puede ahorrar mucho tiempo de desarrollo utilizando un lenguaje de más alto nivel
- Si es un desarrollador competente de C64 pero un desastre con el ensamblador 6502 puede dejar que el compilador haga la parte complicada.
–¡Muchas gracias DrMortalwombat por responder a nuestras preguntas!
Enlaces:
- Podéis ir al Itch.io de DMW en ESTE enlace
- Podéis visitar su Ghitub en ESTE enlace
- Podéis visitar nuestro TYOLJ dedicado a él en ESTE enlace
- English version: Oscar64, with DrMortalWombat [EN]
7 Comentarios
¡¡Lectura imprescindible!!!
ResponderEliminarArtículo muy interesante. Enhorabuena!
ResponderEliminarFelicidades por el articulazo!
ResponderEliminarQue gran artículo. Enhorabuena! A ver si promueve el uso de esta excelente herramienta de DMW.
ResponderEliminar¡Excelente entrevista Bieno y entrevistado! Da gusto leer estas historias de los genios que siguen creando juegos para nuestro querido C64 y en el caso de él aportando a la comunidad con el fabuloso compiladore Oscar 64.👌🕹️👾
ResponderEliminarDeja de usar seuck y haz algo en asm, que en msx no usamos makers
EliminarSuper interesante Bieno, felicidades. Increible como nuestro C64 nos sigue sorprendiendo.
ResponderEliminar