Modo bitmap multicolor

En cierto modo, el modo bitmap estándar es bastante multicolor, ya que cada cuadradito de 8 x 8 admite dos colores, el de los bits activos y el de los bits inactivos. Ambos colores salen de la RAM de pantalla (nibble alto y nibble bajo). Pero, todavía más, los dos colores de un cuadrado de 8 x 8 pueden ser distintos de los dos colores del cuadrado de al lado. En definitiva, admite bastante colorido.

A pesar de esto, hay un modo bitmap multicolor. Y al igual que ocurría con los sprites multicolor y con el modo carácter multicolor, se consiguen cuatro colores a costa de dividir por la mitad la resolución horizontal, que pasa a ser de 200 x 160 pixels.

De este modo, en cada matriz de 8 x 8, los pixels / bits se agrupan de dos en dos en sentido horizontal. En función de lo que valgan esos dos bits así será el color:

  • %00: Se toma el color de fondo del registro $d021 (color de fondo 0).
  • %01: Se toma el color de la RAM de pantalla (nibble alto).
  • %10: Se toma el color de la RAM de pantalla (nibble bajo).
  • %11: Se toma el color de la RAM de color.

Es decir, en el modo bitmap multicolor, el diseño de la pantalla (pixels activos e inactivos) sale de un segmento de 8K de memoria, igual que en el modo bitmap estándar. Y el color sale de la RAM de pantalla, igual que en el modo bitmap estándar, y, además, del registro $d021 y la RAM de color. Ninguna de estas dos últimas fuentes de información se utiliza en el modo bitmap estándar.

El modo bitmap multicolor se activa activando a la vez el bit 5 del registro $d011 (igual que el modo bitmap estándar) y el bit 4 del registro $d016 (igual que el modo carácter multicolor).

Bitmap multicolor.PNG


Programa de ejemplo: Prog45

Modo bitmap estándar

En el modo bitmap, la pantalla no está organizada en caracteres, sino que está organizada en pixels individuales. En el fondo, la pantalla tiene la misma resolución que en el modo carácter, lo que pasa es que cada pixel se maneja independientemente de los demás.

Por tanto, si en el modo carácter la resolución es de 25 x 40, siendo los caracteres de 8 x 8 pixels, al eliminar las fronteras entre caracteres lo que tenemos son 200 x 320 pixels. Y cada uno de estos pixels se puede activar / desactivar de forma independiente de los demás, y sin formar parte de un carácter. Por supuesto, la información para ello sigue saliendo de la memoria RAM, pero ya no de la RAM de pantalla ($0400 – $07e7), como se verá enseguida.

Bitmap estándar.PNG

Así como en el modo carácter la RAM de pantalla ocupa 1.000 bytes, en el modo bitmap tenemos 200 x 320 = 64.000 pixels. Por tanto, necesitamos 64.000 bits o, lo que es lo mismo, 8.000 bytes. Es decir, en el modo bitmap la información que define la pantalla necesita mucha más memoria que la que cabe en la RAM de pantalla. Esto es totalmente lógico, ya que en el modo carácter despachamos cada matriz de 8 x 8 pixels con un byte (un código de pantalla), mientras que en el modo bitmap necesitamos 8 bytes, exactamente 8 veces más.

El color del bitmap sale de la RAM de pantalla (ojo, no la RAM de color). Cada bloque de 8 x 8 pixels toma sus colores del “carácter” que ocuparía la posición análoga en la RAM de pantalla. Los bits activos (los que están a 1) toman su color del nibble más significativo, y los bits inactivos (los que están a 0) toman su color del nibble menos significativo. Con un nibble se pueden codificar los 16 colores que soporta el C64.

El modo bitmap se activa activando el bit 5 del registro $d011. Este registro también se utiliza para activar el modo carácter con color de fondo extendido (bit 6) y para el bit más significativo del raster (bit 7).

Por último, quedan un par de cuestiones para nada triviales: cómo se le indica al VIC dónde están esos 64.000 bits / 8.000 bytes que definen la pantalla y, sobre todo, cuál es la correspondencia entre esos bits y los pixels de la pantalla.

La respuesta a la primera pregunta es actuando sobre el bit 3 del registro $d018, que también se utiliza para indicar dónde se ubican los juegos de caracteres personalizados (bits 1, 2 y 3) y la RAM de pantalla en el modo carácter (también bits 4, 5, 6 y 7). Con el bit 3 pueden indicarse dos valores:

  • %0: El bitmap se encuentra en el segmento $0000 – $1fff (0K – 8K).
  • %1: El bitmap se encuentra en el segmento $2000 – $3fff (8K – 16K).

Bitmaps.PNG

Por supuesto, el segmento de 8K elegido es relativo a la base del banco de 16K direccionado por el VIC. Igual ocurría con los juegos de caracteres personalizados.

Por último, queda explicar cómo es la correspondencia entre los bits de ese segmento de 8K y los pixels de la pantalla. Parecería que lo más intuitivo sería que el primer bit fuera el de la esquina superior izquierda, y así sucesivamente de izquierda a derecha y de arriba abajo, hasta llegar al último de los 64.000 bits, que sería el de la esquina inferior derecha. Pues bien, no es así.

La correspondencia entre los bits de memoria y los pixels de pantalla es bastante más compleja. Es como se indica en la siguiente figura:

Bitmaps2.PNG

Por tanto, como se puede ver en la figura anterior, aunque en el modo bitmap cada pixel es independiente de los demás, el sistema sigue teniendo cierto “tufillo” al modo carácter, ya que los pixels se agrupan y gestionan en bloques de 8 x 8, tanto en pantalla como en memoria. Los pixels activos / inactivos se definen mediante bloques de 8 bytes en memoria. Y su color se define mediante un byte (nibble superior para los bits activos; nibble inferior para los bits inactivos) en la RAM de pantalla.

Sería interesante disponer de alguna herramienta del entorno PC que, dado un diseño de 200 x 320 pixels, generara la definición de memoria anterior. Hasta donde yo sé no existe. O, al menos, yo no la he encontrado en CBM prg Studio.

Por último, las ecuaciones matemáticas trabajan con coordenadas (X, Y). Por tanto, para pintar curvas es interesante disponer de rutinas que, dado un pixel (X, Y), determinen la posición de memoria sobre la que hay que actuar. En el libro “Assembly Language Programming with the Commodore 64”, en sus ejemplos 11-5, 11-6 y 11-7, están explicadas esas rutinas. Además, en el ejemplo 11-8 se muestra cómo usarlas para pintar una especie de estrella.


Programa de ejemplo: Prog44

Mapas o pantallas con CBM prg Studio

Ahora que hemos revisado los tres modos carácter que tiene el C64, es buen momento para revisar la funcionalidad de diseño de pantallas de CBM prg Studio. Esta funcionalidad está disponible en el menú Tools > Screen Editor o añadiendo un nuevo fichero bajo la carpeta “Screen Designs” del árbol del proyecto.

Sea como fuere, se abrirá una ventana como esta:

Editor Pantallas

El editor tiene muchas opciones, pero resumiendo mucho lo que permite es pintar caracteres, ya sean estándar o personalizados (zona de la izquierda), en una o varias pantallas de 25 x 40 posiciones (zona de la derecha). También permite seleccionar caracteres, borrarlos, cambiar su color, escribir textos, diseñar caminos (listas de posiciones X, Y), pintar líneas, pintar cajas, etc.

Editor Pantallas - funciones

Es posible cambiar el modo de la pantalla entre los tres modos vistos: estándar, multicolor y con color de fondo extendido.

Editor Pantallas - modos.PNG

También es posible grabar los diseños en ficheros *.sdd para recuperarlos más adelante y seguir trabajando con ellos, o para compartirlos con otro programador.

Y, por último, permite exportar la definición de las pantallas en diferentes formatos para que puedan ser usadas desde programas en BASIC (sentencias PRINT) o en ensamblador (directivas BYTE o ficheros binarios *.bin). A la hora de exportar se ofrecen muchas opciones, como exportar todas las pantallas o sólo ciertos rangos, incluir la información de color o no, juntar todas las pantallas por un lado y los colores por otro, o cada pantalla con sus colores, generar o no información para filas vacías, etc.

En definitiva, una herramienta muy útil para diseñar pantallas y facilitar su programación, tanto en BASIC como en ensamblador.


Programa de ejemplo: Prog43

Modo carácter con color de fondo extendido

El modo carácter con color de fondo extendido es una mezcla entre el modo estándar y el modo multicolor. A mi entender, se parece más al modo carácter estándar, y se entiende mejor si se compara con él.

En el modo carácter con color de fondo extendido, al igual que en el modo carácter estándar:

  • La pantalla tiene 25 filas por 40 columnas.
  • Los caracteres de la pantalla salen de la RAM de pantalla.
  • El color de los caracteres es único y sale de la RAM de color.

La principal diferencia es que cada carácter puede tener un color de fondo diferente, en vez de ser el color de fondo compartido entre todos los caracteres de la pantalla, como ocurre en el caso estándar.

Para determinar el color de fondo de cada carácter se utilizan los dos bits más significativos de su código de pantalla en la RAM de pantalla (ojo, no en la RAM de color). De este modo, si el código de pantalla empieza por:

  • %00: Se toma el color de fondo del registro $d021 (color de fondo 0).
  • %01: Se toma el color de fondo del registro $d022 (color de fondo 1).
  • %10: Se toma el color de fondo del registro $d023 (color de fondo 2).
  • %11: Se toma el color de fondo del registro $d024 (color de fondo 3).

Por otro lado, si los dos bits más significativos del código de pantalla se utilizan para determinar el color de fondo del carácter, de ello se deriva que sólo quedan 6 bits para determinar el carácter. Es decir, en este modo sólo se puede representar 2^6=64 caracteres, ya sean los primeros 64 caracteres del juego estándar o de un juego personalizado.

En definitiva, el modo carácter con color de fondo extendido nos da control sobre el color de los caracteres y el color de su fondo, que deja de ser común para todos, con el coste limitar el número de caracteres representables a 64.

Quedaría por explicar cómo se activa este modo, lo cual se consigue activando el bit 6 del registro $d011.

Color fondo extendido

A mi modesto entender, se trata de un modo un poco extraño, y con poca utilidad práctica. Normalmente el programador querrá escribir textos o hacer fondos de pantalla gráficos. Para lo primero, el modo carácter estándar permite controlar el color de los caracteres, e incluso su forma con juegos personalizados, y el color del fondo que es común a toda la pantalla. Para lo segundo, el modo carácter multicolor permite diseñar patrones y controlar sus colores (hasta cuatro). Más que suficiente. No veo claro un escenario de uso donde interese controlar el color de los caracteres y el color de su fondo individualmente, y menos si es a costa de reducir el número de caracteres representables.


Programa de ejemplo: Prog42

Modo carácter multicolor

El modo carácter multicolor comparte muchas características con el modo carácter estándar:

  • La pantalla tiene 25 filas por 40 columnas.
  • Los caracteres de la pantalla salen de la RAM de pantalla.
  • El color de los caracteres sale de la RAM de color (sólo en parte).

La principal diferencia es que cada carácter, en vez de tener un único color, puede tener varios colores (hasta cuatro).

Para que esto sea posible, de forma análoga a lo que ocurría con los sprites multicolor, los bits o pixels de la matriz de 8 x 8 se agrupan de dos en dos en sentido horizontal:

Editor caracteres multicolor.PNG

De este modo, si el grupo de 2 bits / pixels vale:

  • %00: Se toma el color del registro $d021 (color de fondo 0).
  • %01: Se toma el color del registro $d022 (color de fondo 1).
  • %10: Se toma el color del registro $d023 (color de fondo 2).
  • %11: Se toma el color del carácter de la RAM de color.

Es decir, tres colores (valores de los registros $d021, $d022 y $d023) tienen que ser compartidos entre todos los caracteres, mientras que el cuarto color es específico de cada carácter individual y sale de la RAM de color.

De lo anterior se deriva que:

  • Aunque se podría usar el juego de caracteres estándar en multicolor, no será habitual hacerlo, ya que este juego de caracteres requiere más resolución y no está pensando para el multicolor. Los caracteres estándar no se interpretan bien en multicolor, como puede apreciarse más arriba.
  • Suelen diseñarse juegos de caracteres a medida para el multicolor, aunque, más que caracteres propiamente dichos (letras, números y símbolos), suelen diseñarse “ladrillos” para componer fondos de pantalla.

Además de diseñar un “juego de caracteres” adecuado para el multicolor, ubicarlo en RAM y activarlo como se explicó en la entrada anterior, hay que configurar el VIC para que funcione en modo multicolor. Esto se hace activando el bit 4 del registro $d016.

Por último, hay que utilizar la RAM de color ($d800 – $dbe7) para especificar el color de cada carácter almacenado en la RAM de pantalla ($0400 – $07e7). Una cuestión curiosa es que si el color de una posición:

  • Está entre 0 y 7: El carácter se mostrará en modo estándar o monocolor.
  • Está entre 8 y 15: El carácter se mostrará en modo multicolor.

Lo anterior permite mezclar caracteres monocolor y multicolor en la misma pantalla.

En la figura que sigue se muestra una pantalla llena de “pirámides” de tres colores: negro (fondo 1), amarillo (fondo 2), y verde claro. El color verde claro podría ser diferente en cada carácter, puesto que sale de la RAM de color.

Pirámides.PNG


Programa de ejemplo: Prog41

Juegos de caracteres personalizados

Los juegos de caracteres personalizados son una extensión del modo carácter estándar. La idea básica es diseñar un nuevo juego de caracteres, completo o parcial, almacenar su definición en RAM, y configurar el VIC para que lea los caracteres de RAM y no de ROM.

Para diseñar el nuevo juego de caracteres se puede utilizar el editor de caracteres de CBM prg Studio. Con este editor es posible hacer cosas como:

  • Diseñar matrices de 8 x 8.
  • Invertir los diseños.
  • Rotar los caracteres (90 grados, 180 grados, etc.).
  • Voltear los caracteres.
  • Cambiar los colores (en el caso multicolor).
  • Importar y exportar juegos de caracteres.
  • Etc.

Editor caracteres

Una vez diseñado el juego de caracteres, sus datos (8 bytes por carácter) se pueden exportar de varias maneras, por ejemplo, en formato listado de bytes que se inserta en el programa ensamblador (ej. BYTE    60,66,165,129,165,153,66,60) o en formato de fichero binario (*.bin) que luego se incluye en un programa con la directiva “incbin”.

Por último, queda decirle al VIC que tome los caracteres de RAM y no de ROM. Esto se hace actuando sobre los bits 1, 2 y 3 (recordemos que los bits se empiezan a numerar en 0) del registro $d018. Este mismo registro (bits 4, 5, 6 y 7) también vale para recolocar la RAM de pantalla.

Con 3 bits es posible generar 8 valores distintos. Y puesto que el VIC puede direccionar 16K, y que cada juego de caracteres completo ocupa 2K, de ello se concluye que los juegos de caracteres tienen que almacenarse en uno de los 8 bloques de 2K que caben en 16K.

Juegos de caracteres

Los 2K del juego de caracteres no pueden ocupar una posición arbitraria dentro de los 16K. Tienen que empezar en una de las fronteras de 2K: 0K – 2K, o 2K – 4K, o 4K – 6K, o …

Por otro lado, una cosa es la posición del juego de caracteres dentro del banco de 16K direccionable por el VIC, y otra cosa es la posición dentro del mapa de memoria global del C64. Para determinar la posición absoluta hay que sumar el segmento a la posición de base del banco direccionable por el VIC.

Por ejemplo, si el VIC está configurado para usar el banco 0 ($0000 – $3fff), y el segmento que utiliza el juego de caracteres es el que va desde 2K hasta 4K ($0800 – $0fff), entonces el juego de caracteres tendrá que empezar en la posición $0000 + $0800 = $0800. Pero si el VIC estuviera configurado para usar el banco 1 ($4000 – $7fff), entonces el juego de caracteres tendría que empezar en la posición $4000 + $0800 = $4800.

En el ejemplo que se adjunta se han redefinido los caracteres desde la A hasta la J, dejando todos los demás como el estándar. El nuevo juego de caracteres se ha colocado en el último segmento de 2K del banco 0 ($0000 – $3fff), es decir, en las posiciones $3800 – $3fff. Al ejecutar el programa la pantalla muta como se ve a continuación.

Pantalla mutada

Por supuesto, nada impide usar colores (RAM de color) con los caracteres personalizados. Pero igual que con los caracteres estándar, cada carácter tendrá un único color, aunque un carácter y el de al lado tengan colores distintos.


Programa de ejemplo: Prog40

Modo carácter estándar

En el modo carácter la pantalla del C64 tiene 25 líneas por 40 columnas. En cada una de esas 25 x 40 = 1.000 posiciones se puede pintar un carácter. De ahí el nombre: “modo carácter”.

Esas 1.000 posiciones de la pantalla lógicamente salen de la memoria RAM. La RAM de pantalla normalmente se ubica en el segmento $0400 – $07e7, aunque ya sabemos que el mapa de memoria del C64 en general, y el banco de 16K que usa el VIC en particular, son programables y esas direcciones pueden cambiar.

En función de los bytes (códigos de pantalla) almacenados en la RAM de pantalla, así serán los caracteres que se pintan en pantalla. El byte de la posición $0400 determina el carácter de la esquina superior izquierda, y así sucesivamente de izquierda a derecha y de arriba abajo, hasta llegar a la esquina inferior derecha, que sale de la posición $07e7.

Como los bytes pueden tomar 256 valores, desde el 0 hasta el 255, eso significa que en cada posición de pantalla se puede mostrar uno de entre 256 caracteres. Dicho de otro modo, los juegos de caracteres tienen 256 caracteres.

A su vez, cada carácter se define mediante una matriz de 8 x 8 pixels (o bits) o, lo que es lo mismo, mediante 8 bytes. Es decir, que un juego de caracteres completo requiere 8 bytes x 256 caracteres = 2.048 bytes o 2 Kbytes.

Letra A

El juego de caracteres estándar, el que viene con el C64, está en ROM. Pero el VIC también permite que el usuario defina sus propios juegos de caracteres. Esto se verá más adelante.

Todavía más, igual que ocurre con los sprites, los caracteres pueden ser monocolor o multicolor. El caso multicolor, más que para juegos de caracteres propiamente dichos (letras, números y símbolos), suele utilizarse para diseñar fondos de pantalla utilizando los caracteres como “ladrillos”.

En el modo carácter estándar (o monocolor), cada carácter es de un solo color, aunque un carácter y el de al lado pueden tener colores distintos gracias a la RAM color ($d800 – $dbe7). El color del fondo se puede elegir, pero es el mismo para toda la pantalla.

Modo carácter estándar

Modo carácter vs modo bitmap

Aunque también hemos mencionado algún tema diferente (nomenclatura, cargador BASIC, …) las últimas entradas las hemos dedicado mayoritariamente a los sprites, que son una de las características gráficas más interesantes del VIC.

Sin embargo, el VIC tiene otras características gráficas de interés. Por ejemplo, permite configurar la pantalla del C64 en modo carácter, en modo bitmap, o incluso una mezcla de ambos modos (usando las interrupciones raster).

A su vez, tanto el modo carácter como el modo bitmap tienen otras variantes, que son:

  • Modo carácter estándar.
  • Modo carácter multicolor.
  • Modo carácter con color de fondo extendido.
  • Modo bitmap estándar.
  • Modo bitmap multicolor.

Como se ve, las posibilidades son muchas. Y con todas ellas se pueden usar sprites.

En las entradas que siguen se describirán estos modos.

Cargador BASIC

Cuando desarrollamos un programa en ensamblador con CMB prg Studio podemos ensamblarlo y cargarlo con Ctrl + F5. Si el programa tiene errores de ensamblado habrá que corregirlos; si no los tiene el programa se cargará y quedará listo para ejecutar. Para ejecutarlo tendremos que ejecutar manualmente la sentencia BASIC “SYS 49152”, donde 49152 debe sustituirse por la dirección de comienzo del programa en ensamblador (ej. * = $c000).

El proceso puede automatizarse un poco más con un “cargador BASIC”. Un cargador BASIC es un programa en BASIC que carga y ejecuta el programa en ensamblador. Este programa en BASIC puede generarse automáticamente desde CBM prg Studio con Tools > Generate BASIC Loader.

Utilizando esta opción se genera automáticamente un programa en BASIC similar al que sigue:

Cargador BASIC

Este programa es muy sencillo, y consta de:

  • Una variable llamada “SA” con la dirección de comienzo del programa en ensamblador.
  • Un bucle “FOR – NEXT” que recorre la sección DATA y carga el programa en ensamblador a partir de la dirección de comienzo.
  • Una llamada “SYS” para ejecutar el programa en ensamblador.
  • Una sección DATA con el programa en ensamblador ya ensamblado y codificado en decimal.

Este programa puede guardarse en un archivo *.bas y, a partir de ese momento, puede utilizarse para cargar y ejecutar el programa en ensamblador.

Otra alternativa es generar una llamada “SYS” e insertarla en el programa en ensamblador. Esto se hace en CBM prg Studio con Tools > Generate SYS Call.

Si utilizamos esta opción CBM prg Studio nos preguntará la dirección a la que llamar, y nos ofrecerá un par de opciones que no tienen mucha importancia (comentario y si la llamada será “elaborada” o “estándar”):

SYS Call

Elijamos lo que elijamos, CMB prg Studio generará e insertará en nuestro programa en ensamblador una secuencia de bytes como la que sigue:

SYS Call 2

Esa secuencia de bytes se carga a partir de la posición $0801, es decir, justo en el comienzo de la RAM de BASIC. Y es la forma en que BASIC codifica la sentencia “10 SYS 49152”.

Es decir, no es más que una forma indirecta de cargar en BASIC el programa “10 SYS 49152” (o “10 SYS DIRECCIÓN”), que vale para ejecutar nuestro programa en ensamblador.

De las dos opciones presentadas, cargador BASIC y llamada SYS, a mí me gusta más la segunda. Se genera menos BASIC y el grueso del trabajo se sigue haciendo en ensamblador. Además, con la segunda opción, aunque cambie el programa en ensamblador, el programa en BASIC no cambia (porque sólo es una llamada BASIC => ensamblador), mientras que con el cargador cada vez que cambie el programa en ensamblador habrá que regenerar el cargador.


Programa de ejemplo: Prog39

Interrupciones del VIC

Como ya hemos comentado en entradas anteriores, el C64 tiene un mecanismo muy interesante llamado “interrupciones”. Las “interrupciones” son, como su nombre indica, interrupciones en el flujo de ejecución normal de los programas que, de forma temporal, pasan a ejecutar una rutina de interrupción para luego continuar con el programa interrumpido.

Estas interrupciones se pueden generar por eventos en el software (ej. instrucción “brk”) o en el hardware (ej. eventos que ocurren en el VIC o en otros chips y que activan las patillas de interrupción del 6510).

Esta entrada está dedicada a las interrupciones que puede generar el VIC, es decir, las que guardan relación con la programación gráfica del C64.

El VIC puede generar interrupciones cuando se producen los siguientes eventos:

  • El raster llega a una determinada línea (registro $d012; ver entrada anterior).
  • Un sprite colisiona con el fondo.
  • Un sprite colisiona con otro sprite.

Por otro lado, el VIC tiene dos registros relacionados con interrupciones:

  • El registro $d019, que sirve para obtener información sobre las interrupciones generadas.
  • El registro $d01a que sirve para programar las interrupciones deseadas.

En ambos casos existe una correlación uno a uno entra la causa que genera la interrupción (o que se quiere que genere la interrupción), y los bits del registro $d019/$d01a:

  • Bit 0: El raster llega a la línea programada en el registro $d012.
  • Bit 1: Un sprite colisiona con el fondo.
  • Bit 2: Un sprite colisiona con otro sprite.

Además, el bit 3 sirve para consultar/generar interrupciones relativas al lápiz óptico (sin mucho interés para este blog), y el bit 7 para obtener información sobre si se ha generado una interrupción de cualquiera de los tipos.

En definitiva, para trabajar con interrupciones del VIC (ej. para detectar una colisión y restar vidas), hay que programar en el registro $d01a las interrupciones deseadas. Además, cuando se produzca una interrupción, y ya desde la rutina de interrupción, hay que averiguar de qué naturaleza es con el registro $d019 (esto sólo es necesario si pudieran ocurrir interrupciones de varias causas a la vez), y atender la interrupción ejecutando el código que sea necesario.

InterrupVIC


Programa de ejemplo: Prog38