viernes, 1 de julio de 2016

VOLCADO DEL BUFFER CON LA PILA (IV): RUTINA DE VOLCADO II







En esta entrada voy a explicar la subrutina VULVU.
Antes que nada, decir que es una "libre adaptación" de la que usa Mike Lamb en algunos de sus juegos, como Batman: The Movie o WEC Le Mans. Yo la uso en Knightmare 2 ZX.

En el .GIF de abajo puedes ver la forma en la que vuelco el buffer en la pantalla real. Va volcando las líneas de derecha a izquierda y primero vuelca un doce caracteres en horizontal y luego los otros doce.

Volcado del buffer en la pantalla real

Para la explicación, voy a tomar como base que volcamos desde la dirección 8004h del buffer a la 4090h de la pantalla real.

IX va a ser el puntero que lleva las direcciones del buffer, por ello lo cargamos con 8000h (inicio del buffer) + 4 que son los caracteres que dejamos de marco a la izquierda.
HL es el puntero de la pantalla real. Vamos a imprimir a partir del cuarto caracter vertical, dejando un marco de 4 caracteres a la izquierda: 4000h+80h+4=4084h. ¿Por qué lo cargamos con 4090h?, pues porque hay que sumarle 12 (0Ch). 4084h+0Ch=4090h. Ahora entenderás por qué.

 

Guardamos SP y lo cargamos con IX que es el inicio del buffer que queremos volcar (8004h).
Salvamos los registros. El que nos interesa es HL que lleva las direcciones de la pantalla real.
Cargamos 6 registros con las primeras 12 direcciones de la primera línea a volcar:

HL'=contenido de las direcciones 8004h,8005h
DE'=contenido de las direcciones 8006h,8007h
BC'=contenido de las direcciones 8008h,8009h
AF= contenido de las direcciones 800Ah,800Bh
DE= contenido de las direcciones 800Ch,800Dh
BC= contenido de las direcciones 800Eh,800Fh

Ahora cargamos SP con HL (SP=4090h) y hacemos PUSH con todos los registros que antes hemos cargado. Ya he comentado que al hacer POP, SP se incrementa, pero al hacer PUSH, primero se decrementa y luego se mete el dato en la dirección donde apunta SP. Por eso hay que sumarle 12 a HL. Al hacer PUSHes, éste se va decrementado. Así que los registros se "PUSHEAN" en orden inverso a como se "POPARON"

El valor de BC se mete en 408Fh,408Eh
El valor de DE se mete en 408Dh,408Ch
El valor de AF se mete en 408Bh,408Ah

En este momento incrementamos H para pasar a la siguiente línea de la pantalla (4190h)
y salvamos los registros.

El valor de BC' se mete en 4089h,4088h
El valor de DE' se mete en 4087h,4086h
El valor de HL' se mete en 4085h,4084h

Como puedes ver, cada byte se ha colocado en su sitio y de la forma como se ve en el .GIF.

Ahora tenemos que pasar a la siguiente línea del buffer, por lo que hay que incrementar el byte alto de IX. Hacer algo así como INC Ix. Este nemónico como tal no existe, pero si ponemos el valor DDh antes de INC H, el Z80 lo interpreta como INC Ix.

Esto lo hacemos cuatro veces para volcar las cuatro primeras líneas.
Después del último volcado miro a ver si hemos llegado a la cuarta línea, con lo que repetimos el proceso, o si hemos acabado con el caracter en curso. Para eso se hace BIT 2,H. Si el resultado es uno, significa que estamos en la línea 4, por lo que repite.

Hemos acabado de imprimir el primer rectángulo de 1 caracter de alto por 12 de ancho.
¿Qué valor tiene IX?: Partíamos de 8004h. Hemos incrementado Ix 8 veces, por lo que IX=8804h.
¿Qué valor queremos que tenga ahora? Queremos que apunte a la posición de inicio (8004h) más los doce caracteres que ya hemos metido, osea 8004h+0Ch=8010h.
La cuenta es sencilla. Restamos 8 a Ix y sumamos 0Ch a iX. Osea, le restamos (800h-0Ch).
Para HL la cuenta es la misma.



Ya podemos volcar los otros 12 caracteres.

¿Cuánto vale ahora IX? Empezó en 8010h y hemos incrementado Ix 8 veces. IX=8810h.
¿Cuánto queremos que valga? Pues queremos que se nos posicione en el siguiente caracter en vertical de donde empezó en un principio. Empezó en 8004h y queremos que valga 8024h.
Podemos restar 8 a Ix, restar 0Ch a iX y sumarle 20h. (800h-20h=7E0h.)
Basta con restar a IX (7E0h+0Ch). 8810h-(7E0h+0Ch)=8024h.

Ya tenemos volcado el primer rectángulo de 1 caracter de alto por 24 de ancho. HL e IX apuntan al inicio del segundo rectángulo... siempre que no se cambie de tercio. Este problema ya se solucinó en la anterior entrada.


Si he conseguido que entiendas esto, no te será muy complicado cambiar el tamaño del buffer que se vuelca. De todas formas he dejado preparada "alguna pista". Si queremos un buffer 4 caracteres más ancho, tenemos que volcar dos caracteres más por vuelta. Para eso hay que usar un registro doble más. Yo he elegido AF'. Si quitas los ";" delante de EX AF,AF' y de los PUSH AF y POP AF volcamos 4 datos más, por lo que el buffer volcado será de 28 caracteres de ancho. Pero recuerda darle a MITAN el valor de 14. Para centrar algo mejor el buffer puedes dar  a CHAR el valor de 2.
Si queremos menos caracteres verticales, tenemos que quitar VOLVUs de nuestra pila "artificial".

Como siempre te digo, experimenta, que es la mejor manera de entender las cosas.

La rutina de restablecimiento del fondo no tiene nada de especial.

lunes, 27 de junio de 2016

VOLCADO DEL BUFFER CON LA PILA (III): RUTINA DE VOLCADO I



Vamos a ir volcando el buffer en la pantalla real en bloques de un caracter de alto por 24 de ancho.
Para que quede centrada en la pantalla dejamos un marco de 4 caracteres a la izquierda.
Estos valores se establecen al principio de CONTENIDA.ASM. Así podrás cambiarlos para hacer tus pruebas sin comerte mucho la cabeza.




MITAN es el ancho en caracteres del recuadro que vamos a volcar partido por 2.
CHAR es el número de caracteres que dejamos en blanco.

EL corazón de la rutina es VOLVU que vuelca primero los 12 primeros caracteres y luego los otros 12  desde la dirección que le marca IX (buffer) hasta que le marca HL. Además, retorna con la dirección del siguiente caracter tanto en IX como en HL. Pero no comprueba si cambiamos de tercio, por lo que tendremos que comprobarlo nosotros "a mano".

Por esto es por lo que hay que llamar a VOLVU varias veces (tantas como caracteres verticales tenga el trozo de buffer que queremos imprimir). Esto supondría realizar 16 CALL VOLVU. Nosotros vamos a utilizar otro método:  construimos una "pila artificial" donde ponemos a qué dirección tiene que saltar cada vez que se ejecute un RET (en concreto con el que termina VOLVU).

Esta es nuestra pila:



Si hacemos LD SP,STACCR cuando se ejecuten los RETs, saltará a la dirección que indique SP:

RET: salta a VOLVU
RET: salta a VOLVU
RET: salta a VOLVU
RET: salta a VOLT
RET: salta a VOLVU
RET: salta a VOLVU
RET: salta a VOLVU
RET: salta a VOLT1
RET: salta a VOLVU
RET: salta a VOLVU
RET: salta a VOLVU
RET: salta a VOLTF
etc...
RET: salta a STACV que es el final de la rutina.
De esta manera nos ahorramos las llamadas.



Pues lo dicho, ponemos SP apuntando a STACCR (nuestra pila artificial).
HL va a llevar las direcciones de la pantalla real donde se va a volcar nuestro rectángulo e IX lleva las direcciones del buffer desde donde se vuelcan.
Los datos  se van cogiendo del buffer con POP (al hacer POP se coge el dato de la pila y ésta se incrementa) y se van metiendo en la pantalla real con PUSH (al hacer PUSH, se decrementa primero la pila y luego se mete el dato).

Por eso IX se carga con $8000+CHAR: inicio del buffer más el marco de 4 caracteres a la izquierda.
HL se carga con $4080+CHAR+MITAN: vamos a volcar a partir del 4 caracter horizontal (dirección $4080) más el marco (CHAR) más los doce primeros caracteres  horizontales a volcar. Ya he comentado que al hacer PUSH la pila se va decrementando, por eso a HL se le suma MITAN.

Comenté que VOLVU vuelca un rectángulo de un caracter de alto por 24 de ancho desde IX hasta HL y devuelve IX y HL con el valor para el siguiente caracter. Osea, que si cambiamos de tercio no vale.

Si te das cuenta, HL empieza en el cuarto caracter vertical. Podemos llamar a VOLVU cuatro veces hasta que llegue al cambio de tercio. Así que hacemos JP VOLVU (1 vez) y en nuestro stack tenemos VOLVU (2 veces),VOLVU (3 veces),VOLVU (4 veces) y VOLT.
En VOLT cargamos HL con la dirección del principio del segundo tercio $4800 más el marco más MITAN.

Pero los caracteres verticales también van pasando para el buffer. Empezó en $8000. Cuando VOLT se haya ejecutado 8 veces tenemos que cambiar de tercio a mano.
Hasta ahora VOLVU se ha ejecutado 5 veces (VOLT también salta a ella), así que en nuestro stack hemos puesto VOLVU (6 veces),VOLVU (7 veces),VOLVU (8 veces) y VOLT1.
En VOLT1 cargamos IX con el inicio del segundo tercio del buffer $8800 más el marco y saltamos otra vez a VOLVU. Despúes de cuatro VOLVUs tenemos que actualizar HL con la dirección del tercer tercio de la pantalla más el marco más MITAN.

Si quisiéramos posicionar el volcado del buffer en la pantalla real en otra dirección, por ejemplo a partir del segundo caracter vertical ($4040+CHAR+MITAN) deberíamos cambiar nuestra pila artificial. Si has entendido esto, deberías ser capaz de hacerlo.

martes, 21 de junio de 2016

VOLCADO DEL BUFFER CON LA PILA (II): GUARDANDO EL FONDO



Este programa es idéntico al VOLCADO DEL BUFFER CON LDI en lo concerniente al control de enemigos y prota. Pero cambia sustancialmente a la hora del guardado de fondo, impresión de sprites, volcado y restauración del fondo.





Esta primera parte de la rutina es muy parecida. Vamos metiendo los datos que necesitamos en la tabla de recuperación del sprite, calculamos la dirección del buffer donde  lo imprimimos y colocamos los valores que vamos a utilizar. Sólo cambia que el contador de sprites a imprimir va a ser el registro B, ya que el A se va a usar para hallar la dirección de pantalla inferior a una dada.







Con este código nos aseguramos de que después de sumar el ancho a la dirección donde se va guardando el trozo de buffer, (SUMLDR y SUMLDD), E no pase de FFh.  En esta rutina el tamaño máximo de los sprites lo he fijado en 3x3. El trozo a guardar puede ser 3x4, por lo que el tamaño máximo en bytes a guardar será de 3X4X8=96 bytes. Si al sumar la dirección donde voy a guardar el cacho más 96 tiene acarreo, significa que E ha pasado de 255. Así que lo ponemos a 0 e incrementamos D.







En esta rutina de guardado, va haciendo LDI para guardar el fondo. Al final tiene que decrementar L ya que con el último LDI se ha incrementado de más. Por eso, no podemos usar todo el ancho de la pantalla, ya que hay zonas en las que el LDI ha hecho que HL pase de 40FFh a 4100h.(por ejemplo). Si decrementamos sólo L, nos daría 41FFh y lo correcto sería 40FFh.

Incrementamos H para pasar a la siguiente línea del buffer. Como la coordenada es par, sabemos que no va a cambiar ni de caracter ni de tercio.
Sumamos a DE el ancho ya que ahora vamos a guardar de decrecha a izquierda con LDD. Incrementamos L ya que el último LDD nos lo ha decrementado de más y se vuelve a sumar el ancho a DE para volver a guardar de izquierda a derecha.

Ahora sí que tenemos que comprobar si al pasar a la siguiente línea cambia de caracter o de tercio.
Se repite el bucle y cuando se guarda todo el fondo del sprite se actualiza GFONDI+1 para el siguiente.

En cuanto a la rutina de impresión de sprites es muy parecida a la que uso en el VOLCADO DEL BUFFER CON LDI. Simplemente cambia el método de hallar la dirección de la línea inferior a una dada, ya que aquí estamos usando la estructura real de la pantalla.

jueves, 16 de junio de 2016

VOLCADO DEL BUFFER CON LA PILA (I)




En esta serie de entradas voy a explicar otra forma de usar un buffer de impresión. Es bastante más rápido que el anterior, y a mi modo de ver más elegante, si bien ocupa algo más de memoria.

Te puede bajar el código de AQUI:
He metido un fondo más aparente.

Este buffer que vamos a usar copia literalmente la estructura de la pantalla del spectrum. No es como en las entradas anteriores, que lo creábamos a nuestra conveniencia. En este caso, todas las operaciones a realizar serán las mismas que si estuviéramos imprimiendo directamente en la pantalla. Por ello, es muy importante que entiendas la estructuración de la misma
Parece muy enrevesada para nuestra mente decimal, pero si piensas en Hexadecimal, tiene toda su lógica.
Ya comenté que viene muy bien explicado en el curso de Romero. Por favor, échalo un vistazo, ya que si no dominas cómo funciona el asunto, estas entradas se te pueden hacer un poco duras.

De todas formas, voy a dar un repaso rápido, que nunca viene mal.

La pantalla del Spectrum tiene 192 líneas.
Está dividida en tres tercios de 24 líneas.
Cada tercio está dividido en 8 caracteres.
Cada caracter está dividido en 8 líneas.
Cada línea mide 32 caracteres de ancho.
La pantalla empieza en la dirección 4000h y termina en la 57ffh.


Los tres tercios tienen la misma estructura, por lo que nos fijaremos sólo en uno.

Seguro que has visto cargar una pantalla en el spectrum muchas veces, y no lo hace de forma secuencial.






Carga las 32 direcciones de memoria de la primera línea, y la siguiente dirección es la primera línea del segundo caracter. Carga las 32 direcciones y la siguiente será la primera línea del tercer caracter... Así hasta que ha cargado las 32 direcciones de memoria de la primera línea del octavo caracter, que es el último del primer tercio.

Las siguiente dirección será la segunda línea del primer caracter y todo vuelve a empezar pero esta vez con las segundas líneas de cada caracter. Así hasta que carga el tercio.

Si estamos en la primera dirección de la primera línea del primer caracter (4000h) y queremos pasar a la dirección que está inmediatamente debajo de ésta (primera dirección de la segunda línea del primer caracter), ¿qué debemos hacer? He dicho antes que después de la dirección 32 de la primera línea del octavo caracter se pasa a la dirección 0 de la segunda línea del primer caracter. Osea que hemos avanzado 8 líneas*32 direcciones por línea=256 (100h) direcciones.  Sumamos 4000h+100h=4100h.
Osea, si el registro HL=4000h, nos basta con INC H y tendremos HL=4100h.

Para hallar la segunda línea, incrementamos H otra vez, y así con todas las líneas del caracter.
El problema es que si estamos en la última (4700h) e incrementamos H, nos da HL=4800h como dirección de la primera línea del segundo caracter. Y eso no es así. La primera línea del segundo caracter, como hemos visto, es 4000h+32 direcciones de memoria que ocupa la primera línea, o lo que es lo mismo en hexadecimal 4020h.

Esto nos supone ir comprobando cada vez que incrementemos H para pasar de línea, si nos hemos pasado al siguiente caracter. Esto se hace viendo si H es múltiplo de 8. Si lo hemos hecho, basta con restar 8 a H, y sumarle 20h a L.
48h-8=40h. 00h+20h=20h. Total que HL=4020h. Ya tenemos la dirección que buscábamos.

Así podemos hallar la dirección que está debajo de una dada dentro de un tercio, pero ¿qué pasa si nos pasamos de tercio? Con nuestro método, cuando estemos en el octavo caracter del primer tercio y en su última línea estaremos en la dirección 48E0h, H es múltiplo de 8, por lo que nos hemos pasado de caracter. Hacemos 48h-8=40h. E0h+20h=00. HL=4000h. Nos vuelve a la primera línea del primer caracter. Para cambiar de tercio basta sumarle 20h a L, sin restar 8 a H.
E0h+20h=00. HL=4800h, que es la primera dirección de la primera línea del segundo tercio.
4800h-4000h=800h. 800h no es otra cosa que lo que nos ocupa un tercio entero.

¿Cómo sabemos si al incrementar H nos hemos pasado de tercio además de caracter?
Pues porque al sumar 20h a L, nos va dando esto:

4700h     última línea primer caracter
4720h     última línea segundo caracter
4740h     última línea tercero caracter
4760h     última línea cuarto caracter
4780h     última línea quinro caracter
47A0h    última línea sexto caracter
47C0h    última línea séptimo caracter
47E0h    última línea octavo caracter

HL=47E0h. INC H para cambiar de línea. HL=48E0h. H es múltiplo de 8, por lo que hay que cambiar de caracter. Sumo 20h a L. HL=4800. Si te das cuenta L vuelve a ser igual a cero, por lo que en la operación de la suma ha habido acarreo (FLAG C=1). Esto significa que se nos ha acabado el tercio y no tenemos que restar 8 a H.

Todo esto lo hace la rutina SIGLIN de CONTENIDA.ASM, que ya medio expliqué, pero que seguro que ahora la entiendes perfectamente.





Como puedes ver, pensando en Hexadecimal, la distribución de la pantalla no es tan "rara" como parece.

En esta rutina se va a usar todo este planteamiento constantemente.

Como ya sabes, guardo los sprites en zigzag, así cuando termino de imprimir el último caracter del sprite, me basta con hallar la dirección que está debajo y volver de a imprimir de derecha a izquierda.

Esta rutina tiene alguna limitación. La coordenada Y tiene que ser siempre par. Esto es por lo siguiente: Sólo va a haber cambio de caracter al hallar la dirección que está debajo de una dada cuando la coordenada de partida sea impar (condición necesaria pero no suficiente). Si nuestra coordenada es siempre par, nos ahorramos una comprobación de cambio de caracter, ya que sabemos que nos basta con incrementar H para cambiar de línea.

Por la distribución de la pantalla, para pasar de una dirección a la contigua cuando imprimamos el sprite, sabemos que L nunca va a ser FFh ni 0. Por ello, todos estos incrementos y decrementos se pueden hacer con INC/DEC L en vez de con INC/DEC HL, con lo que ganamos mucha velocidad.
El problema de usar INC/DEC L cuando lo que queremos es INC/DEC HL ya lo hemos visto. Tenemos que estar seguros de que L nunca sea FFh al incrementar ni 0 al decrementar ya que éste pasaría a ser 0/FFh y H se quedaría con el mismo valor.

Esta ganancia en velocidad no es oro todo lo que reluce. En realidad, L sí que va a ser 0 o FFh, pero sólo en determinados puntos de los bordes de la pantalla. Por eso, la coordenada X nunca puede ser menor que 8 ni mayor que 248.
En este ejemplo volcamos 16 caracteres de alto por 24 de ancho.

Todo esto se verá  con el examen de la rutina.

Intenta comprender todo esto muy bien.

lunes, 13 de junio de 2016

VOLCADO DEL BUFFER CON LDI ( Y VIII): VOLCADO DEL BUFFER Y RESTAURACIÓN DEL FONDO



Esta rutina es muy sencilla: simplemente cogemos cada línea del buffer y la volcamos a la pantalla real con tantos LDIs como ancho es el buffer (ANPAN).

Lo único que tenemos que saber es la dirección de la pantalla real que se corresponde con el primer carácter de cada línea.
Para eso, en CONTENIDA.ASM creamos otra tabla:



Cargo IX con el incio de la tabla.
HL con la dirección de la pantalla real que corresponde con la coordenada (0,0), osea, con la esquina superior izquierda del cuadro donde voy a volcar el buffer.
B con el número de líneas.
En el bucle voy metiendo las direcciones en la tabla.
En SIGLIN hallo la dirección inmediatamente inferior a una dada en la pantalla real:



Si no estás familiarizado con la estructura de la pantalla del Spectrum, te recomiendo que eches un vistazo aquí.

Ahora vemos la rutina de volcado:



Cargamos SP con la tabla.
BC con el tamaño del buffer.
HL con el inicio del buffer.
Al hacer POP DE, DE tiene la dirección de la primera línea donde vamos a volcar.
REPT y ENDM no son nemónicos del Z80. Es una forma de crear un bucle en PASMO: repite la instrucción LDI ANPAN veces. Osea, ensambla ANPAN LDIs, que es la anchura del buffer.
Como ya hemos visto, se ejecuta JP PE hasta que BC=0 (tamaño del buffer).

No tiene más misterio.


RECUPERACION DEL FONDO DEL BUFFER

Este es el último paso de nuestra rutina. Vamos a ir restaurando los trozos de fondo del buffer donde hemos imprimido un sprite. Para eso usamos las tablas que hemos ido creando al guardar los cachos. Esta recuperación hay que hacerla en orden inverso a como se guardaron. Y precisamente, IY apunta a la tabla del último cacho.



En CSPIMR+1 se guardó en su momento el número de sprites.
Guardamos AF como contador.
Vamos cargando los distintos registros con los valores guardados en la tabla de recuperación.
Y el resto es muy parecido a la rutina que guarda los trozos del fondo.


Ahora es cuando tú puedes cambiar distintos parámetros, tales como sprites, tamaño del buffer, de la pantalla real donde se vuelca el buffer... en fin, todo lo que se te ocurra para que estés seguro de entender su funcionamiento.


miércoles, 8 de junio de 2016

VOLCADO DEL BUFFER CON LDI (VI): IMPRESIÓN DE SPRITES





Antes de proceder a estudiar la rutina, voy a intentar explicar algunos conceptos en el movimiento a pixeles (alta resolución).

A la hora de imprimir gráficos en el spectrum, lo que se hace es meter ciertos valores en las direcciones de pantalla (en nuestro caso en el buffer).
Si yo quiero imprimir el sprite del esqueleto en las coordenadas[(Y,X))] (0,0) tendré que empezar a meterlo en la dirección 8000h que es donde le corresponde (Gráfico sin desplazar). Para imprimirlo en la coordenada (0,2), lo meto también a partir de la dirección 8000h, pero desplazado 2 pixeles a la derecha. Y lo mismo en (0,4) y (0,6). Para imprimirlo en (0,8) se empezará a meter en la dirección 8001h sin desplazarlo y así sucesivamente.


8000h     8001h      8002h 



Gráfico sin desplazar (0,0)




Gráfico desplazado dos pixeles a la derecha (0,2)




Gráfico desplazado cuatro pixeles a la derecha (0,4)




Gráfico desplazado seis pixeles a la derecha (0,6)





Esto significa que si la coordenada X es cero o múltiplo de 8, el gráfico se mete sin desplazar y si no lo es, hay que desplazarlo el número de veces que la coordenada X exceda del múltiplo de 8 inmediatamente anterior.

Como podrás imaginar, para conseguir esto hay que hacer muchos desplazamientos y esto enlentece la rutina de impresión.

Yo uso un método explicado por Pablo Ariza en una Microhobby Especial.



Esta rutina nos crea una tabla de tal manera que:


  • En FANNh nos mete el número NN desplazado 2 veces a la derecha y en FBNN nos mete el sobrante de este desplazamiento.
  • En FCNNh nos mete el número NN desplazado 4 veces a la derecha y en FDNN nos mete el sobrante de este desplazamiento.
  • En FENNh nos mete el número NN desplazado 4 veces a la derecha y en FFNN nos mete el sobrante de este desplazamiento.
¿Qué quiero decir con sobrante?
Fíjate en este gráfico:

8000h                   8001h

Arriba tenemos un cuadrado sin desplazar que ocupa a partir de la posición 8000h. Abajo lo hemos desplazado dos pixeles a la derecha que nos ocupa la dirección 8000h y la 8001h. ¿Qué parte del gráfico nos ocupa la dirección 8001h? Pues lo que nos ha sobrado al desplazarlo dos pixeles. Aquí verás más claro lo que decía en la entrada anterior sobre que si la coordenada X no es cero o múltiplo de 8 (si hay desplazamiento) el cacho de buffer a guardar ocupa un caracter más de ancho que el sprite.

Esta tabla se usa así: Supongamos que un byte del sprite es 11111111b (FFh) y queremos imprimirlo en la coordenada (0,2). Necesitamos desplazarlo 2 veces a la derecha. Cargamos H con FAh y L con FFh. Al hacer LD A,(HL) en A obtenemos 00111111b y lo metemos en 8000h. Ahora necesitamos la parte sobrante. Pues  cargamos H con FBh y L con FFh. Al hacer LD A,(HL) obtenemos 11000000h y lo metemos en 8001h. Si hacemos lo mismo con los 8 bytes del gráfico lo habremos imprimido en la coordenada X=2.

Este sistema es rápido, pero supone reservar desde FA00h hasta FFFFh y el movimiento mínimo horizontal va a ser de 2 pixeles. Creo que estas dos limitaciones merecen la pena.


MÁSCARAS

Como ya he comentado, esta rutina imprime sprites, osea que no alteran el fondo por donde pasa (para eso guardamos los trozos de buffer) y tiene partes transparentes, por lo que se ve el fondo que hay a través de ellos.
Para conseguir esto último se usa la máscara: a cada byte del gráfico le corresponde su máscara, que es otro byte en el cual los unos son las partes transparentes, y los ceros las opacas. Por eso los sprites ocupan el doble que los gráficos.

Aquí podemos ver un cuadrado con su máscara correspondiente:


GRÁFICO                                                              MASCARA

Como puedes, ver he puesto unos en la máscara en los pixeles que quiero que sean transparentes; es decir en los bordes y en el centro. Suelo dejar un pixel opaco entre la máscara y el gráfico para que éste se defina mejor la imprimirlo sobre el fondo.


En este gráfico de abajo he fusionado el gráfico con la máscara para que lo veas más claro. En amarillo está el gráfico y en negro la máscara. En blanco están los pixeles que dejo de borde entre la máscara y el gráfico.



¿Cómo se maneja esto a la hora de imprimir?: Pues cogemos el byte de la máscara y hacemos un AND con el byte del fondo y a esto le hacemos un OR con el byte del gráfico.

Para crear los sprites uso el editor SevenuP de Metalbrain, que funciona de maravilla. Te explico lo fundamental de su uso para lo que nos ocupa.

En File pulsas New y te pedirá el tamaño del gráfico a crear en pixeles. Te aparecerá una cuadrícula como la de estos ejemplos. Al pulsar la flecha blanca te cambia el modo del cursor. Pinta el gráfico que quieras y luego pulsa en Mask y activa Use mask y View mask. Aquí pintas la máscara.

Yo para mi rutina de sprites necesito la máscara invertida (ya lo explicaré) por lo que tienes que pulsar en el botón INV. Ya podemos salvar el sprite.

En File pulsas Output options y tienes que dejar las opciones como en esta figura:





El byte de la máscara va antes que el del gráfico y lo guardo en Horizontal Zig Zag osea, que se guarda la primera línea del gráfico de izquierda a derecha y después la segunda de derecha a izquierda, la tercera de izquierda a derecha y la cuarta de derecha a izquierda, etc. Esta forma de guardar los sprites nos supone que podamos programar una rutina de impresión más rápida.

Finalmente  en File, pulsamos en Export data y elegimos que nos lo grabe con extensión .BIN.


Con todo esto vamos a ver la rutina de impresión de sprites.




En el registro B metemos la altura del gráfico dividido por 2.
Cargamos DE con la dirección del buffer donde va el gráfico (este dato lo metimos en DIRPBUF+1 cuando lo hallamos en la rutina de guardar los fondos).
Cargamos HL con la dirección del gráfico a imprimir.
Guardamos el registro SP y lo cargamos con HL. Los datos del gráfico los vamos a ir cogiendo a base de POPs, como los datos de la cola de impresión de sprites.
Al hacer EX DE,HL, metemos en HL la dirección del buffer donde vamos a imprimir.
En A metemos la anchura del sprite y lo guardamos en las direcciones que nos hace falta para que la rutina sea más rápida.
Si la coordenada X es 0 o múltiplo de 8 significa que no hay rotaciones por lo que saltamos a IMPBUFCARACTERJUSTO, que es una rutina semejante pero sin rotar nada y más rápida.

Lo que nos ha sobrado de ser múltiplo de 8 se suma a F8h. Así ya sabemos qué tabla de rotaciones tenemos que usar y guardo este dato en la rutina: si X=12 (00001100b) y hacemos AND 7, nos da como resultado 4 (00000100b) osea que hay que usar la tabla de 4 desplazamientos. F8h+4=FCh que es donde está esta tabla. Lo guardo donde la voy a usar y lo incremento donde voy a usar los restos.
Cargo DE con el ancho del buffer, C con el ancho del sprite y con EXX paso a los registros alternativos.
Con POP DE cargo E con la máscara del primer byte del sprite y D con el gráfico.
En H meto el inicio de la tabla de desplazamientos. Al hacer "LD L,E",  en (HL) tengo la máscara rotada.
La cargo en A (LD A,(HL)) y la invierto con CPL, ya que las máscaras las salvamos invertidas desde SevenuP.

¿Por qué salvo la máscara invertida y luego la vuelvo a invertir en la rutina en vez de salvarla normal? Pues porque esta máscara que estoy invirtiendo es la original desplazada. Como usamos las tablas, a la izquierda nos ha metido ceros y eso no nos interesa ya que la parte desplazada ha de ser transparente. Por eso al hacer CPL nos convierte en unos los bits transparentes de la máscara y en ceros los opacos que es lo que nos hace falta.

Al hacer EXX, HL contiene la dirección del buffer, por lo que hacemos un AND (HL) como expliqué antes.
Volvemos a hacer EXX, por lo que H contiene la tabla de rotaciones. Con LD L,D conseguimos que en (HL) esté el gráfico rotado (D tenía el gráfico) y hacemos el OR.

Hasta este punto hemos imprimido el gráfico con la máscara desplazado. En la siguiente dirección de memoria (INC HL) meteremos el resto.
Para eso hacemos EXX  e incrementamos HL. Así H tiene el inicio de la tabla de los restos de los desplazamientos y hacemos lo mismo que antes.
Repetimos lo que nos ocupa de ancho el sprite y a partir de ANF1 nos ponemos a imprimir la siguiente línea del sprite. Para eso sumamos ANPAN (que está en DE) a la dirección del buffer donde terminó de imprimirse la primera línea del sprite y hacemos el mismo proceso pero de derecha a izquierda.

Por eso el dato de la tabla del sprite que guarda el alto del mismo se divide por la mitad: hacemos dos pasadas por cada iteración del bucle.

Recupero el Stack y repito todo el proceso con el siguiente sprite: guardo el fondo y lo imprimo.

Aquí he puesto un .GIF para que se vea más claro el proceso (espero).






Cuando la coordenada X es cero o múltiplo de 8, no hay rotaciones, así que saltamos a una rutina especial que imprime más rápido el sprite: IMPBUFCARACTERJUSTO




Una vez imprimidos todos los sprites, tenemos que volcar el buffer a la pantalla real para que se vea lo que hemos hecho.










lunes, 6 de junio de 2016

VOLCADO DEL BUFFER CON LDI (V): GUARDANDO EL FONDO DEL BUFFER



Una vez que hemos actualizado las coordenadas de todos los sprites vamos a proceder a su impresión. Esto requiere unos cuantos pasos:


  • Primero guardamos el fondo existente en el buffer donde vamos a meter el sprite para que no se modifique a su paso.
  • Luego imprimimos el sprite en el buffer.
  • Cuando hemos hecho esto con todos los sprites, se vuelca el buffer en la pantalla real.
  • Por último recuperamos todos los fondos del buffer que se modificaron al pintar el sprite. Así el buffer queda totalmente limpio para la próxima impresión de sprites.

En esta entrada voy a explicar cómo guardo el fondo del buffer donde voy a imprimir los sprites.

Lo primero que tenemos que saber es cómo hallar la dirección del buffer correspondiente a unas coordenadas dadas. Yo uso una tabla, donde meto la dirección del primer carácter de cada línea del buffer. Esta tabla se crea en CONTENIDA.ASM.

La tabla empieza en TABCOOR (F800h). Metemos el byte bajo de las direcciones del primer caracter de cada línea del buffer en F8nn y el byte bajo en F9nn.



Cargamos DE con PANTVIR, que es el inicio del buffer (8000h) y HL con TABCOOR (F800h). Esto corresponde con (coordY=0, coorX=0). Iniciamos B con el número de líneas que tiene el buffer.

En F800h metemos el byte bajo de la dirección del buffer (00h) incrementamos H (HL=F900h) y metemos el byte alto (80h). Decrementamos H, incrementamos L y le sumamos el ancho del buffer (ANPAN) para que HL tenga la dirección de la siguiente línea del buffer (coordY=1, coorX=0) y repetimos esto con todas las líneas del buffer.

Una vez que sabemos la dirección del primer carácter de la línea del buffer donde está el sprite, sólo tenemos que sumarle la coordenada X para hallar su dirección exacta. En realidad su coordenada X dividida por 8, ya que tenemos que sumarle caracteres y no pixeles.

Veamos un ejemplo práctico. Supongamos que un sprite está en las coordenadas Y=20h X=18h.
Primero vamos a hallar la dirección del primer carácter de la línea del buffer donde está el sprite.
Para eso hacemos lo siguiente;




El registro L se carga con la coordenada Y del sprite. L=20h. H se carga con la tabla que tiene los bytes altos de las direcciones del buffer. H=F9h. Por lo que HL=F920h Al hacer LD D,(HL) en D metemos el byte alto de la dirección del primer carácter del buffer correspondiente a la coordenada 20h.

Ahora metemos en A la coordenada X del sprite. A=18h y la dividimos por 8. A=3, ya que la coordenada X son pixeles y a nosotros nos hace falta saber el número de caracteres a sumar a la dirección del primer caracter de la línea. Al hacer DEC H, HL=F820h que es donde está el byte bajo de la dirección del primer caracter de la línea 20h del fuffer. Se lo sumamos a la coordenada X en caracteres (teniendo cuidado de incrementar D si ha habido acarreo) y así lo metemos en E. De esta manera, DE tiene la dirección donde se va a imprimir el sprite.

Con este método, aunque ocupa algo de memoria, las direcciones de impresión en el buffer se hallan rápidamente.

Una vez guardados los "cachos" del buffer que ocupan los sprites hay que recuperarlos. Para eso se crea una tabla para cada cacho. Cada una consta de 6 bytes:

byte 0: Altura del cacho dividida por 2
byte 1: Anchura del cacho
byte 3 y 4: dirección del buffer donde se debe reponer el cacho
byte 5 y 6: dirección donde se ha guardado el cacho

Estos datos se empiezan a guardar en DATFON (al final de APRINCIPAL.ASM) y se manejan con el registro IY.


El cacho de buffer que se guarda de un sprite no ocupa siempre lo mismo. Debido a que en el Spectrum siempre se imprime en baja resolución, si queremos que su movimiento (byte 12 de la tabla) horizontal sea de menos que 8 pixeles (en este caso puede ser de 2,4 ó 6; siempre par), debido a que hay que desplazar los gráficos, el trozo a guardar ocupará un caracter más si su coordenada X no es cero o múltiplo de 8.
En resumen, un sprite que tenga n caracteres de ancho, si su coordenada X no es cero o múltipo de 8, ocupa n+1 caracteres.
Para saber si un número es múltiplo de 8, basta con hacerle un AND 7. Si este resultado es igual a cero, es que lo es y por lo tanto ocupa n caracteres. Eso es lo que se hace con la coordenada X.
De todas formas, el tema de las rotaciones de los gráficos lo explicaré en la rutina de impresión.


Una última consideración. Como ya he explicado, las tablas de los sprites que se tienen que imprimir se van metiendo en una cola que empieza en SPIMP. Para ir cogiendo estos datos uso la pila, que es un método muy rápido.

Supongamos que en la cola de impresión de sprites he metido: ENEM2,ENEM3,ENEM4,PROTA.
Primero cargo SP con SPIMP (LD SP,SPIMP). Para recuperar el primer dato hago POP IX con lo que IX=ENEM2. Una vez que termino con este sprite hago de nuevo POP IX con lo que ahora IX=ENEM3 y así sucesivamente hasta IX=PROTA.

Para usar la pila de esta manera hay que tener en cuenta que:

  • Las interrupciones han de estar deshabilitadas, ya que al saltar una interrupción se guarda la dirección de retorno en la pila, con lo que se nos machacarían los datos y a saber a dónde retorna.
  • No se puede usar ninguna instrucción que maneje la pila, tal como CALL, hacer PUSH para guardar registros, etc. Obviamente esto también nos machacaría los datos. 

Después de todo este rollo, vamos a ver la rutina en sí.




Cargamos en IY la dirección donde se van a ir guardando las tablas de los cachos del buffer-6.
Guardamos el registro de la pila, ya que lo vamos a usar para ir sacando las tablas de los sprites de la cola de impresión.
Metemos en GFONDI+1 el inicio de la zona donde vamos a guardar los cachos.
Apuntamos la pila a la cola de impresíón.
Aquí aparece CSPIM que es el número de sprites que hay en la cola y que se actualiza en METEIMPS cada vez que metemos uno.
Ya que está en A, lo metemos también en CSPIMR+1 que es el contador del bucle para restaurar los fondos.
Con POP IX cogemos el primer dato de la cola de impresión, osea, IX=tabla del primer sprite a imprimir.
Después hallamos la dirección del buffer a partir de las coordenadas como vimos antes, y la metemos en la tabla. Luego metemos en la tabla la dirección a partir de donde se guarda el cacho.
Miramos si la coordenada X es múltipo de 8. Si no lo es, el trozo a guardar tiene un caracter más que el ancho del sprite.
Antes comenté que para hallar la dirección inmediatamente inferior a una dada en el buffer había que sumarla ANPAN. Como al ir guardando líneas esta dirección se va a incrementar en el ancho del trozo a guardar, la dirección inmediatamente inferior a la  del inicio del trozo será de ANPAN-(ancho del cacho).
Guardamos la (altura/2) en la tabla .

Ponemos B=0 ya que el bucle en GF2 se repetirá hasta que BC=0  y volcamos la primera línea con LDI. Para ir a la segunda, se le suma ANPAN-(ancho del trozo).
Se vuelve a repetir el asunto (ya que el bucle dura  (altura del sprite/2)) y se guarda la dirección de inicio donde se va a meter el siguiente trozo.


Ya estamos preparados para imprimir el sprite.