domingo, 13 de marzo de 2011

Capitulo 3: Todo sobre los SPRITES del C64

 "El Vic II está limitado técnicamente a direccionar solo 16Kb, teniendo que conmutar a diferentes bancos de ese tamaño para poder acceder a toda la memoria del sistema."

En el anterior capitulo vimos la estructura interna de los sprites y los tipos que podemos crear: monocromos (alta resolución) y multicolores (baja resolución), vimos detallada información de los primeros y dimos un repaso general de los segundos. En el presente capítulo vamos a poner en práctica todos esos conocimientos consiguiendo mostrar en pantalla nuestro primer sprite.

¡Bienvenidos de nuevo!


Los punteros de los SPRITES

¿Dónde espera el chip gráfico encontrar las definiciones de los sprites? O dicho de otra forma: ¿En que lugar de la memoria tenemos que introducir los bytes del sprite?

Muy fácil: Cada uno de los 8 sprites tiene un puntero asignado, que indica en que lugar de la memoria se hallan sus definiciones.

Estos punteros ocupan un byte cada uno, y se encuentran al final de la memoria de video, no importa en que lugar la hayamos movido, los ultimos 8 bytes de donde esté situada la ram de video contendrán el indicador (o puntero) de cada sprite correspondiente, por supuesto por orden, empezando por el sprite 0 y acabando por la del sprite 7.

Aunque la información de cada sprite ocupe 63 bytes se añade un byte extra por motivos técnicos, como veremos seguidamente.Así que en memoria, en realidad ocupan 64 bytes, aunque hayamos definido un byte menos.

Sprites del Paperboy tal como están alojados en memoria.


Como cada sprite ocupa 64 bytes de forma fija, con un byte podemos especificar donde estará nuestro sprite dentro del banco de memoria actual expresándolo como un múltiplo de 64.

Es por esto que para que fuera múltiplo binario se optó por cuadrar el sistema este con ese byte extra. Éste último byte (que arrastra cada sprite) no se usa, aunque algunos programadores lo utilizan para guardar datos asociados útiles para verificar las colisiones, por ejemplo.

Veamos todo esto detenidamente:
Si estamos en el primer banco (el núm. 0) que es en el que estamos cuando encendemos el ordenador, tenemos localizada la memoria de video en la posición de memoria 1024 ($0400 en hexadecimal) asi pues los últimos 8 bytes de esa 1Kb de memoria contienen los punteros de los sprites. Estas 8 últimas posiciones de memoria corresponden a 2040, 2041, 2042, 2043, 2044, 2045, 2046 y 2047, y contienen los punteros del sprite 0 al 7 por el orden citado en la enumeración.

Si ponemos un 0 en la posición del puntero del sprite 0 (el primer sprite), esto es poner a cero la posición de memoria 2040, estaremos comunicándole al Vic-II que las definiciones de nuestro sprite se encuentran en la posición de memoria 0*64 = 0, lo cual si os fijáis no seria un buen sitio, pues la posición 0 es el inicio de la página cero del c64, una zona de la memoria muy usada por el sistema para operaciones internas del sistema operativo. Dicho esto, es necesario encontrar un buen lugar para colocar nuestros sprites. Y un lugar seguro y práctico para nuestras pruebas (si no vamos a utilizar el casette) es el área del buffer del casette, situada en la posición 896 que en múltiplo de 64 se expresa como 14*64.

Entonces ahora, si ponemos en 2040 el valor 14, el chip Vic-II pintará el sprite 0 con los valores encontrados a partir de la posición de memoria 896 (64 bytes)

Si cambiamos la memoria de video de lugar a otro banco deberíamos de tener en cuenta la siguiente fórmula para saber la dirección absoluta a la que apunta nuestro puntero:

Localización datos sprite = (Banco * 16384) + (Valor de puntero de sprite*64)

En nuestro caso como el banco era el 0, el resultado era simplemente el que habíamos dicho: 14 (valor del puntero del sprite) * 64 = 896

Seguramente muchos os estareis preguntándo a estas alturas, ¿Por qué todo esto de los bancos? ¿Que son los bancos exactamente?
 El Vic II está limitado técnicamente a direccionar solo 16Kb, teniendo que conmutar a diferentes bancos de ese tamaño para poder acceder a toda la memoria del sistema. En total hay 4 bancos, y se empiezan a enumerarse (¡que casualidad!) desde el número 0. (banco #0, banco #1, etc). Como cada banco ocupa 16384 bytes la dirección (absoluta) de comienzo de cada banco se expresa mediante 16384 multiplicado por el numero de banco. De esta forma tenemos:

Banco 0 = 16384*0
Banco 1 = 16384*1
Banco 2 = 16384*2
Banco 3 = 16384*3

Inicialmente, como ahora sabéis, al principio el vic ya está "mirando" el banco 0, esto es, las primeros 16384 bytes de la RAM del c64.

Tras este pequeño pero importante inciso, volvemos al tema de los sprites. Para cargar los datos de nuestro sprite en Basic, la forma más eficiente es usando la instrucción DATA (seria muy tedioso y un desperdicio de memoria poner todos los bytes POKE a POKE). Aunque también podremos usar LOAD para cargarlo en memoria si lo tenemos archivado en formato de archivo (algo que veremos en otros capítulos).

Y por supuesto en código máquina podemos incluir los datos del sprite mediante las directivas .byte o .db (según que compilador utilicéis cambia el nombre de estas) ubicándolas en el pc = dirección de memoria que deseéis, o moviéndolas a donde queráis en la memoria con vuestras propias rutinas en código máquina (CM a partir de ahora) para mover bloques de datos.

Así un cargador de DATAs, en Basic, a modo de ejemplo seria algo así como:

10 FOR DIR=896 TO 959
20 READ BYTE
30 POKE DIR,BYTE
40 NEXT
50 END
60 DATA 24, 48, 67, 0, 0, 0, 32, 48, 56
Etc.
 (aquí pondríamos linea a linea, DATA a DATA todos los datos del sprite)

Los podéis organizar como queráis, siempre respetando el orden de la tabla 1.0. (publicada en el anterior capitulo). En el ejemplo se han puesto haciendo que cada linea de DATA represente a todo el ancho de un sprite, mediante los 3 grupos de 3 bytes.

Con esto habríamos cargado los 63 bytes que definen nuestro sprite en la DIR de memoria que corresponde al buffer del casette (896 ó $0380 en hexadecimal)

Faltaría añadir una línea al programa para indicarle al VIC-II donde va a estar el sprite #0, indicándoselo en la dirección del primer puntero, en la región de memoria que guarda y mantiene actualizados éstos, que en condiciones iniciales, como ya se ha dicho, comienza en la posición 2040 (8 bytes antes del final de la memoria de video).

Asi hariamos:

5 POKE 2040,14

Y si ejecutamos este programa, nos daremos cuenta de que en realidad, no sucede nada… ¿Por qué? ¿No debería de aparecer ya nuestro sprite?

Pues la cuestión es que aún no: nos falta activarlo, ya que por defecto, todos los sprites se encuentran desactivados, algo útil esto pues mantener los sprites activos consume recursos, aunque esto pueda parecer imperceptible a simple vista, podria llevar a desincronizaciones en programas que necesiten aprovechar hasta el último ciclo (aunque hay tecnicas que se benefician de tener los sprites activos, aunque no dibujen uno, para sincronizar con el escaneo de la pantalla).

Para activar los sprites (asi como para todas las demás operaciones que veremos) se emplean directamente los registros de sprites del Vic-II.

El registro denominado SPRITE ENABLE es el encargado de activar o desactivar cada uno de los sprites. Podemos comunicarnos con éste a través de la dirección 53269 ($D015, en hexadecimal).

Y esta es la estructura del registro:
$D015
Spr 7
128
Spr 6
64
Spr 5
32
Spr 4
16
Spr 3
8
Spr 2
4
Spr 1
2
Spr 0
 1

Tabla 1.1: Estructura del registro SPRITE ENABLE del Vic-II, los números de abajo indican el peso de la posición que ocupan en la parrilla de bits (ver sistema binario)

Esta posición de memoria contiene un byte, y cada uno de los 8 bits que lo componen se corresponden con la estructura de la tabla 1.1. Un bit puesto a 0 indica que el sprite estará desactivado (no se muestra en pantalla) y lógicamente, un bit puesto a 1 indica que ese sprite está activo (se mostrará en pantalla). Como se dijo anteriormente, por defecto todos los sprites están desactivados, esto es, el contenido del byte de este registro contiene un 0.
De forma análoga para activarlos todos pondríamos el valor 255 (equivale a poner todos los bits=1). ¿Véis ahora la utilidad de haber aprendido el sistema binario?

En nuestro ejemplo queriamos activar el sprite 0, para ello, pondríamos en la posición de memoria 53269 el valor 1 que es el peso (binario) que ocupa, según veis en la tabla.

Ahora sí que veremos a nuestro sprite (¡que ilusión! ¡Mi primer sprite en pantalla!)

Ampliando el programa anterior (editamos la linea 5):

5 POKE 2040,14 : POKE 53269, 1

Tened en cuenta que poniendo un 1 directamente en ese registro, estarmos desactivando automáticamente todos los demás. Si queremos activar uno pero no molestar a los demás, tendremos que usar el algebra de Bool (o lógica booleana) y aplicar máscaras binarias. En este caso si solo quisieramos afectar al sprite 0, tendriamos que haber hecho

5 POKE 2040,14 : POKE 53269, PEEK(53269) OR 1

 lo cual activaría solamente el sprite 0 (OR se utiliza para activar bits y AND para desactivarlos) y los demás mantendrían su estado actual.

Y tras ejecutarlo con RUN, aparecerá nuestro sprite en pantalla...

¿Cómo? ¿Todavía no aparece? Que sucede aquí… ¿que es lo que hemos hecho mal?

En realidad no hemos hecho nada mal, simplemente el sprite se ha dibujado fuera de la pantalla (en las coordenadas invisibles x=0, y=0) por eso todavía no podemos verlo correctamente…

Esto, casi por accidente, nos ha llevado a enseñar el registro que controla las coordenadas para posicionar a nuestro sprite en la pantalla. Tenemos 2 registros (en realidad 3, por que la coordenada X al ocupar más de 1 byte necesita un bit extra), uno controla la coordenada horizontal (X) y el otro la coordenada vertical (Y). Esta vez tenemos dichos registros por cada sprite de los 8 que hay y no todos codificados en un byte (no caben, seria imposible)

En resumen tendríamos:
  1. SPRITE X POSITION REGISTER
  2. SPRITE X (BIT más significativo)
  3. SPRITE Y POSITION REGISTER

Como se acaba de explicar, cada sprite tiene su propio registro de cada uno de estos apartados, con una excepción: El bit más significativo de la coordenada X. Que al ser un bit, quedan codificados todos en un byte, de forma similar a como sucedía con el registro que vimos hace unas pocas lineas arriba (ver Tabla. 1.1)

Dirección del registro
Descripción
53248
Posición X del SPRITE #0
53249
Posición Y del SPRITE #0
53250
Posición X del SPRITE #1
53251
Posición Y del SPRITE #1
53252
Posición X del SPRITE #2
53253
Posición Y del SPRITE #2
53254
Posición X del SPRITE #3
53255
Posición Y del SPRITE #3
53256
Posición X del SPRITE #4
53257
Posición Y del SPRITE #4
53258
Posición X del SPRITE #5
53259
Posición Y del SPRITE #5
53260
Posición X del SPRITE #6
53261
Posición Y del SPRITE #6
53262
Posición X del SPRITE #7
53263
Posición Y del SPRITE #7
53264
Bit más significativo de la Posición X (de los 8 sprites)

Tabla 1.2: Registros de posicionamiento de los SPRITES



53264
Spr 7 MSB Pos X
Spr 6
MSB Pos X
Spr 5
MSB Pos X
Spr 4
MSB Pos X
Spr 3
MSB Pos X
Spr 2
MSB Pos X
Spr 1
MSB Pos X
Spr 0
MSB Pos X

Tabla 1.3: Estructura del registro del Vic-II POS X MSB: Most significant Byte, "Byte mas significativo" de la posición X

Sabiendo esto, vamos a colocar nuestro sprite en las coordenadas 100, 100:

No hace falta que lo incluyáis en el programa, podéis teclearlo en modo directo:

POKE 53248,100
POKE 53249,100

Y ¡Tachán! Ahora sí … ahí tenéis vuestro sprite, ¡Por fin! ¿emocionante, verdad?

Si añadimos la siguiente linea a nuestro programa de ejemplo, veremos a nuestro sprite en acción con tan solo unos simples añadidos:

Sustituyendo la linea 50 por :

50 Y=Y+1: IF Y>255 THEN Y=0:POKE 53249,Y:GOTO 50

(para salir del bucle, simplemente pulsad RUN/STOP)


Ahora os invito a que practiquéis por vuestra cuenta con todo lo que habéis aprendido y vayáis palpando el tema con vuestras propias experiencias, que es como más se aprende.

Por ejemplo, como ejercicio, podríais diseñar 8 sprites con forma circular (o la que más os guste) y hacerlos que se muevan todos a la vez en direcciones aleatorias de la pantalla (con la función RND), o haciéndolas moverse de un lado para otro rebotando contra los márgenes de la pantalla. Si intentais esto último, con toda seguridad, que tarde o temprano os “chocareis” con el problemilla del BIT más significativo de la posición X. Tan solo deberéis activar ese bit o no, cuando la coordenada X sea mayor que 255 (el valor máximo de los 256 posibles que puede alcanzar cualquier byte) Pero no os preocupéis por esto, si os quedáis atascados, en los próximos capítulos, ésta y otras cuestiones quedarán perfectamente aclaradas…

¡Hasta el próximo capítulo, amigos… y felices sprites!

;)

7 comentarios:

Rulas International dijo...

Bueno, Bueno, Bueno, me vas a matar!!!, indudablemente me tengo que hacer eco de todo esto en RGS, irá con
Fuente: Back2theRetro
Copyright 2011 Lobo Gris

Lobogris dijo...

Perfecto! a ver si asi se nos unen más artistas al equipo un dia con todo esto y le damos forma a Commodore en España por fin, como lo merece.

Toni dijo...

Muy interesante y una gran currada!

Lobogris dijo...

Me alegra ver que te resultó muy interesante Toni. Ya lo puedes decir, es una currada! ;) pero se hace con mucho cariño, y con el objetivo de dejar conocimientos del c64, por escrito y en español.

Paulina dijo...

Hola

Mi nombre es Paulina y soy administradora de un directorio y buscador de webs/blogs. Buen blog de informatica. Quisiera intercambiar enlaces. Puedo agregar tu pagina en mi directorio para que así mis visitantes puedan visitarla tambien.

Si te interesa, escribeme al mail: p.cortez80@gmail.com

Saludos
Pau

Josepzin dijo...

Muy claras tus explicaciones! De a ratos me puedo poner a mirar un poco estos temas y la verdad es que me gusta como estás desarrollando estos tutoriales.

Una duda que me surje cuando pones "un lugar seguro y práctico para nuestras pruebas (si no vamos a utilizar el casette) es el área del buffer del casette, situada en la posición 896".
La duda es: ¿donde poner las cosas en memoria? Supongamos que cargo un PRG que viene con su código y gráficos. Todo esto se carga en algun area de la memoria (seguro que esta información es fácil de obtener), luego los gráficos hay que moverlos a su lugar definitivo (¿banco 1? ¿2?, etc).
No sé si se entiende mi planteamiento... :P

Lobogris dijo...

¿Podrias reformular la pregunta por favor? No se si te entiendo del todo...

Publicar un comentario en la entrada