Diferencias
Muestra las diferencias entre dos versiones de la página.
Ambos lados, revisión anterior Revisión previa Próxima revisión | Revisión previa | ||
cursos:ensamblador:gfx3_sprites_lowres [08-01-2024 06:08] – sromero | cursos:ensamblador:gfx3_sprites_lowres [19-01-2024 08:21] (actual) – sromero | ||
---|---|---|---|
Línea 2: | Línea 2: | ||
En este capítulo crearemos rutinas específica de impresión de sprites de 8x8 píxeles en posiciones exáctas de carácter (gráficos de bloques) y la extenderemos a la impresión de sprites de 16x16 píxeles (2x2 bloques). Además de estas rutinas de tamaños específicos, | En este capítulo crearemos rutinas específica de impresión de sprites de 8x8 píxeles en posiciones exáctas de carácter (gráficos de bloques) y la extenderemos a la impresión de sprites de 16x16 píxeles (2x2 bloques). Además de estas rutinas de tamaños específicos, | ||
+ | |||
+ | | ||
El capítulo hará uso intensivo de los algoritmos de cálculo de direcciones de memoria a partir de coordenadas y del movimiento relativo descritos en 2 anteriores capítulos, aunque las rutinas mostradas a continuación podrían ser directamente utilizadas incluso sin conocimientos sobre la organización de la videomemoria del Spectrum. | El capítulo hará uso intensivo de los algoritmos de cálculo de direcciones de memoria a partir de coordenadas y del movimiento relativo descritos en 2 anteriores capítulos, aunque las rutinas mostradas a continuación podrían ser directamente utilizadas incluso sin conocimientos sobre la organización de la videomemoria del Spectrum. | ||
Línea 123: | Línea 125: | ||
\\ | \\ | ||
* El cálculo de posición en memoria de las coordenadas (c,f) en las que vamos a dibujar el Sprite. | * El cálculo de posición en memoria de las coordenadas (c,f) en las que vamos a dibujar el Sprite. | ||
- | * El dibujado de cada scanline del sprite en pantalla, ya sea con LD/LDIR o con operaciones lógicas tipo OR/XOR. | + | * El dibujado de cada scanline del sprite en pantalla, ya sea con LD/ldir o con operaciones lógicas tipo OR/XOR. |
* El avance a través del sprite para acceder a otros scanlines del mismo. | * El avance a través del sprite para acceder a otros scanlines del mismo. | ||
* El avance diferencial en pantalla para movernos hacia la derecha (por cada bloque de anchura del sprite), y hacia abajo (por cada scanline de cada bloque y por cada bloque de altura del sprite). | * El avance diferencial en pantalla para movernos hacia la derecha (por cada bloque de anchura del sprite), y hacia abajo (por cada scanline de cada bloque y por cada bloque de altura del sprite). | ||
Línea 140: | Línea 142: | ||
En este sentido, en alguna de las rutinas utilizaremos variables en memoria para alojar datos de entrada o datos temporales o intermedios. Aunque acceder a la memoria es " | En este sentido, en alguna de las rutinas utilizaremos variables en memoria para alojar datos de entrada o datos temporales o intermedios. Aunque acceder a la memoria es " | ||
- | Las instrucciones | + | Las instrucciones |
\\ | \\ | ||
|< 50% 30% 20% >| | |< 50% 30% 20% >| | ||
^ Instrucción ^ Tiempo en t-estados ^ | ^ Instrucción ^ Tiempo en t-estados ^ | ||
- | | PUSH rr | 11 | | + | | push rr | 11 | |
- | | PUSH IX o PUSH IY | 16 | | + | | push ix o push iy | 16 | |
- | | POP rr | 10 | | + | | pop rr | 10 | |
- | | POP IX o POP IY | 14 | | + | | pop ix o pop iy | 14 | |
- | | LD (NN), A | 13 | | + | | ld (NN), a | 13 | |
- | | LD A, (NN) | 13 | | + | | ld a, (NN) | 13 | |
- | | LD rr, (NN) | 20 | | + | | ld rr, (NN) | 20 | |
- | | LD (NN), rr | 20 | | + | | ld (NN), rr | 20 | |
- | | LD (NN), HL | 16 | | + | | ld (NN), hl | 16 | |
\\ | \\ | ||
- | | + | |
En nuestro caso utilizaremos algunas variables de memoria para facilitar la lectura de las rutinas: serán más sencillas de seguir y más intuitivas a costa de algunos ciclos de reloj. No deja de ser cierto también que los programadores en ocasiones nos obsesionamos por utilizar sólo registros y acabamos realizando combinaciones de intercambios de valores en registros y PUSHes/POPs que acaban teniendo más coste que la utilización de variables de memoria. | En nuestro caso utilizaremos algunas variables de memoria para facilitar la lectura de las rutinas: serán más sencillas de seguir y más intuitivas a costa de algunos ciclos de reloj. No deja de ser cierto también que los programadores en ocasiones nos obsesionamos por utilizar sólo registros y acabamos realizando combinaciones de intercambios de valores en registros y PUSHes/POPs que acaban teniendo más coste que la utilización de variables de memoria. | ||
Línea 162: | Línea 164: | ||
El programador profesional tendrá que adaptar cada rutina a cada caso específico de su programa y en este proceso de optimización podrá (o no) sustituir dichas variables de memoria por combinaciones de código que eviten su uso, aunque no siempre es posible dado el reducido juego de registros del Z80A. | El programador profesional tendrá que adaptar cada rutina a cada caso específico de su programa y en este proceso de optimización podrá (o no) sustituir dichas variables de memoria por combinaciones de código que eviten su uso, aunque no siempre es posible dado el reducido juego de registros del Z80A. | ||
- | | + | |
Línea 536: | Línea 538: | ||
| | ||
+ | |||
+ | |||
+ | \\ | ||
+ | \\ | ||
+ | **INCBIN vs INCLUDE vs DB:** | ||
+ | |||
+ | En este y los siguientes capítulos, para incluir los gráficos en nuestros ejemplos los hemos exportado como secuencias de bytes DEFB con el propio SevenuP. El ASM exportado por SevenuP puede ser " | ||
+ | |||
+ | No obstante, si tenemos los gráficos y atributos por separado en formato BINARIO (no como ristras de números en un fichero de texto), podríamos incluirlo con '' | ||
+ | |||
+ | <code z80> | ||
+ | bicho_gfx: | ||
+ | INCBIN " | ||
+ | |||
+ | bicho_attr: | ||
+ | INCBIN " | ||
+ | </ | ||
+ | |||
+ | Es exactamente equivalente a incluir los bytes como números con DB, pero nos evita tener que convertir el fichero binario a texto. Eso implica que si estamos editando constantemente por ejemplo, un fichero gráfico, y lo editamos en un editor que trabaje directamente con el formato binario, no tenemos que volver a convertir a texto su contenido. | ||
+ | |||
+ | SevenuP permite exportar también como binario en "File > Export Data > Raw Binary (*.BIN)" | ||
+ | |||
+ | |||
+ | Esto se puede hacer también con las pantallas completas o las fuentes de texto, entre otros. | ||
+ | |||
\\ | \\ | ||
===== Paso de parámetros a las rutinas ===== | ===== Paso de parámetros a las rutinas ===== | ||
- | Al programar esta rutina, y cualquiera de las que veremos en este capítulo, debemos decidir cómo pasar los parámetros de entrada a la misma, ya que en estas rutinas manejaremos bastantes parámetros y en alguno de los casos no tendremos suficientes registros libres para establecerlos antes del CALL. | + | Al programar esta rutina, y cualquiera de las que veremos en este capítulo, debemos decidir cómo pasar los parámetros de entrada a la misma, ya que en estas rutinas manejaremos bastantes parámetros y en alguno de los casos no tendremos suficientes registros libres para establecerlos antes del call. |
En este sentido, podemos utilizar para el paso de los parámetros, | En este sentido, podemos utilizar para el paso de los parámetros, | ||
Línea 547: | Línea 574: | ||
* **Registros de 8 y 16 bits**, allá donde sea posible, especialmente en rutinas con pocos parámetros de entrada y que sean llamadas en el programa en momentos críticos o gran cantidad de veces. | * **Registros de 8 y 16 bits**, allá donde sea posible, especialmente en rutinas con pocos parámetros de entrada y que sean llamadas en el programa en momentos críticos o gran cantidad de veces. | ||
- | * **La Pila**: realizando PUSH de los parámetros de entrada en un orden concreto. La rutina, en su punto inicial, hará POP de dichos valores en los registros adecuados. Tiene la ventaja de que podemos ir recuperando los valores conforme los vayamos necesitando y tras haber realizado cálculos con los parámetros anteriores que nos hayan dejado libres registros para los siguientes cálculos. | + | * **La Pila**: realizando |
- | * **El Stack del Calculador**: | + | * **El Stack del Calculador**: |
* **Variables de memoria**: podemos establecer los valores de entrada en variables de memoria con LD y recuperarlos dentro de la rutina también con instrucciones de carga LD. Esta técnica tiene una desventaja: la " | * **Variables de memoria**: podemos establecer los valores de entrada en variables de memoria con LD y recuperarlos dentro de la rutina también con instrucciones de carga LD. Esta técnica tiene una desventaja: la " | ||
* Los registros quedan libres para realizar todo tipo de operaciones. | * Los registros quedan libres para realizar todo tipo de operaciones. | ||
* No tenemos que preservar los valores de los parámetros de entrada al realizar operaciones con los registros, y podemos recuperarlos en cualquier otro punto de la rutina para realizar nuevos cálculos. | * No tenemos que preservar los valores de los parámetros de entrada al realizar operaciones con los registros, y podemos recuperarlos en cualquier otro punto de la rutina para realizar nuevos cálculos. | ||
- | * Nos permite llamar a las rutinas desde BASIC (si usamos posiciones de memoria de direcciones fijas y conocidas), estableciendo los parámetros con POKE y después realizando el RANDOMIZE USR direccion_rutina. | + | * Nos permite llamar a las rutinas desde BASIC (si usamos posiciones de memoria de direcciones fijas y conocidas), estableciendo los parámetros con POKE y después realizando el '' |
\\ | \\ | ||
Línea 637: | Línea 664: | ||
; Bajar a siguiente scanline en pantalla (HL). | ; Bajar a siguiente scanline en pantalla (HL). | ||
- | ; Si base_atributos == 0 -> RET | + | ; Si base_atributos == 0 -> ret |
; Calcular posicion origen de los atributos array_attr+NUM_SPRITE en HL. | ; Calcular posicion origen de los atributos array_attr+NUM_SPRITE en HL. | ||
; Calcular posicion destino en area de atributos en DE. | ; Calcular posicion destino en area de atributos en DE. | ||
Línea 663: | Línea 690: | ||
La misma rutina que vamos a crear servirá para dibujar gráficos con atributos o sin atributos. Nos puede interesar el dibujado de gráficos sin atributos en juegos monocolor donde los atributos de fondo ya están establecidos en pantalla y no sea necesario re-escribirlos, | La misma rutina que vamos a crear servirá para dibujar gráficos con atributos o sin atributos. Nos puede interesar el dibujado de gráficos sin atributos en juegos monocolor donde los atributos de fondo ya están establecidos en pantalla y no sea necesario re-escribirlos, | ||
- | En lugar de crear 2 rutinas diferentes, una que imprima un sprite con atributos y otra que lo haga sin ellos, vamos a utilizar en este capítulo una única rutina indicando en el parámetro de la dirección de atributos si queremos dibujarlos o no. La rutina comprobará si la dirección de atributos es 0 (basta con comprobar su parte alta) y si es así, saldrá con un RET sin realizar los cálculos de atributos (dirección origen, destino, y dibujado). | + | En lugar de crear 2 rutinas diferentes, una que imprima un sprite con atributos y otra que lo haga sin ellos, vamos a utilizar en este capítulo una única rutina indicando en el parámetro de la dirección de atributos si queremos dibujarlos o no. La rutina comprobará si la dirección de atributos es 0 (basta con comprobar su parte alta) y si es así, saldrá con un ret sin realizar los cálculos de atributos (dirección origen, destino, y dibujado). |
| | ||
Línea 697: | Línea 724: | ||
; Guardamos en BC la pareja (x,y) -> B=COORD_Y y C=COORD_X | ; Guardamos en BC la pareja (x,y) -> B=COORD_Y y C=COORD_X | ||
- | | + | |
;;; Calculamos las coordenadas destino de pantalla en DE: | ;;; Calculamos las coordenadas destino de pantalla en DE: | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
; DE contiene ahora la direccion destino. | ; DE contiene ahora la direccion destino. | ||
Línea 716: | Línea 743: | ||
;;; | ;;; | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
; HL contiene la direccion de inicio en el sprite | ; HL contiene la direccion de inicio en el sprite | ||
- | | + | |
;;; Dibujar 8 scanlines (DE) -> (HL) y bajar scanline | ;;; Dibujar 8 scanlines (DE) -> (HL) y bajar scanline | ||
;;; Incrementar scanline del sprite (DE) | ;;; Incrementar scanline del sprite (DE) | ||
- | | + | |
drawsp8x8_loopLD: | drawsp8x8_loopLD: | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
;;; En este punto, los 8 scanlines del sprite estan dibujados. | ;;; En este punto, los 8 scanlines del sprite estan dibujados. | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | ;;; Considerar el dibujado de los atributos (Si DS_ATTRIBS=0 -> RET) | + | ;;; Considerar el dibujado de los atributos (Si DS_ATTRIBS=0 -> ret) |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
;;; Calcular posicion destino en area de atributos en DE. | ;;; Calcular posicion destino en area de atributos en DE. | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
;;; Copiar (HL) en (DE) -> Copiar atributo de sprite a pantalla | ;;; Copiar (HL) en (DE) -> Copiar atributo de sprite a pantalla | ||
- | | + | |
- | | + | |
- | | + | |
</ | </ | ||
Al respecto del código de la rutina, caben destacar las siguientes consideraciones: | Al respecto del código de la rutina, caben destacar las siguientes consideraciones: | ||
- | * Nótese que en la rutina se emplean las subrutinas | + | * Nótese que en la rutina se emplean las subrutinas |
- | * Para realizar la transferencia de datos entre el sprite (apuntado por DE) y la pantalla (apuntada por HL) hemos utilizado 2 instrucciones de transferencia LD usando A como registro intermedio en lugar de utilizar una instrucción | + | * Para realizar la transferencia de datos entre el sprite (apuntado por DE) y la pantalla (apuntada por HL) hemos utilizado 2 instrucciones de transferencia LD usando A como registro intermedio en lugar de utilizar una instrucción |
- | * Como ya vimos en el capítulo anterior, para avanzar o retroceder el puntero HL en pantalla, en lugar de utilizar | + | * Como ya vimos en el capítulo anterior, para avanzar o retroceder el puntero HL en pantalla, en lugar de utilizar |
- | * La rutina que hemos visto, por simplicar el código, utiliza un bucle de 8 iteraciones para dibujar los 8 scanlines. Esto ahorra espacio (ocupación de la rutina) pero implica un testeo del contador y salto por cada iteración (excepto en la última). En una rutina crítica, si tenemos suficiente espacio libre, y si conocemos de antemano el número de iteraciones exacto de un bucle, lo óptimo sería **desenrollar el bucle**, es decir, repetir 8 veces el código de impresión. De este modo evitamos el **LD B, 8** y el **DJNZ | + | * La rutina que hemos visto, por simplicar el código, utiliza un bucle de 8 iteraciones para dibujar los 8 scanlines. Esto ahorra espacio (ocupación de la rutina) pero implica un testeo del contador y salto por cada iteración (excepto en la última). En una rutina crítica, si tenemos suficiente espacio libre, y si conocemos de antemano el número de iteraciones exacto de un bucle, lo óptimo sería **desenrollar el bucle**, es decir, repetir 8 veces el código de impresión. De este modo evitamos el '' |
En el caso de nuestra rutina de ejemplo, cambiaríamos... | En el caso de nuestra rutina de ejemplo, cambiaríamos... | ||
<code z80> | <code z80> | ||
- | | + | |
drawsp8x8_loopLD: | drawsp8x8_loopLD: | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
</ | </ | ||
Línea 800: | Línea 827: | ||
<code z80> | <code z80> | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | ;;;INC H ; no es necesario el ultimo | + | ;;;inc h ; no es necesario el ultimo |
</ | </ | ||
- | | + | |
- | Al no ser necesario el **INC H**, en la versión desenrollada del bucle tenemos que cambiar la resta de HL - 8 por HL - 7: | + | Al no ser necesario el '' |
<code z80> | <code z80> | ||
;;; En este punto, los 8 scanlines del sprite estan dibujados. | ;;; En este punto, los 8 scanlines del sprite estan dibujados. | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
</ | </ | ||
+ | |||
+ | | ||
+ | |||
+ | <code z80> | ||
+ | REPT 8 | ||
+ | ld a, (de) ; Tomamos el dato del sprite | ||
+ | ld (hl), a ; Establecemos el valor en videomemoria | ||
+ | inc de ; Incrementamos puntero en sprite | ||
+ | inc h ; Incrementamos puntero en pantalla (scanline+=1) | ||
+ | ENDM | ||
+ | </ | ||
+ | |||
+ | O, si queremos ahorrarnos el '' | ||
Lo normal es desenrollar sólo aquellas rutinas lo suficiente críticas e importantes como para compensar el mayor espacio en memoria con un menor tiempo de ejecución. En esta rutina evitaríamos la pérdida de ciclos de reloj en el establecimiento del contador, en el testeo de condición de salida y en el salto, a cambio de una mayor ocupación de espacio tras el ensamblado. | Lo normal es desenrollar sólo aquellas rutinas lo suficiente críticas e importantes como para compensar el mayor espacio en memoria con un menor tiempo de ejecución. En esta rutina evitaríamos la pérdida de ciclos de reloj en el establecimiento del contador, en el testeo de condición de salida y en el salto, a cambio de una mayor ocupación de espacio tras el ensamblado. | ||
Línea 861: | Línea 901: | ||
<code z80> | <code z80> | ||
; Ejemplo impresion sprites 8x8 | ; Ejemplo impresion sprites 8x8 | ||
- | ORG 35000 | + | |
- | | + | |
; Establecemos los parametros de entrada a la rutina | ; Establecemos los parametros de entrada a la rutina | ||
; Los 2 primeros se pueden establecer una unica vez | ; Los 2 primeros se pueden establecer una unica vez | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
loop: | loop: | ||
- | | + | |
- | | + | |
; Variables que usaremos como parámetros | ; Variables que usaremos como parámetros | ||
Línea 896: | Línea 936: | ||
; | ; | ||
ClearScreen_Pattern: | ClearScreen_Pattern: | ||
- | | + | |
cs_line_loop: | cs_line_loop: | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
cs_es_par: | cs_es_par: | ||
- | | + | |
cs_pintar: | cs_pintar: | ||
- | | + | |
- | | + | |
cs_x_loop: | cs_x_loop: | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
; | ; | ||
Línea 943: | Línea 983: | ||
;;; codigo de la rutina... | ;;; codigo de la rutina... | ||
- | END 35000 | + | |
</ | </ | ||
Línea 962: | Línea 1002: | ||
</ | </ | ||
- | Así, podríamos establecer la coordenada X e Y con **POKE 65003, x** y **POKE 65004, y** y después hacer el randomize USR a nuestra rutina de impresión del Sprite ya cargada en memoria. | + | Así, podríamos establecer la coordenada X e Y con '' |
En nuestro caso, como el esqueleto del programa está en ensamblador, | En nuestro caso, como el esqueleto del programa está en ensamblador, | ||
\\ | \\ | ||
- | **Transferencia por LDI vs LD+INC** | + | **Transferencia por ldi vs LD+INC** |
\\ | \\ | ||
En nuestra rutina de impresión hemos utilizado instrucciones de carga entre memoria para transferir bytes desde la dirección apuntada por DE (el origen; el Sprite) a la dirección apuntada por HL (el destino; la pantalla). | En nuestra rutina de impresión hemos utilizado instrucciones de carga entre memoria para transferir bytes desde la dirección apuntada por DE (el origen; el Sprite) a la dirección apuntada por HL (el destino; la pantalla). | ||
- | Para trazar los píxeles en pantalla podríamos haber utilizado la instrucción LDI, que con 16 t-estados realiza una transferencia de 1 byte entre la dirección de memoria apuntada por HL (origen) y la apuntada por DE (destino), y además incrementa HL y DE. | + | Para trazar los píxeles en pantalla podríamos haber utilizado la instrucción |
El bucle principal de impresión de nuestro programa es el siguiente: | El bucle principal de impresión de nuestro programa es el siguiente: | ||
Línea 978: | Línea 1018: | ||
<code z80> | <code z80> | ||
drawsp8x8_loop: | drawsp8x8_loop: | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
</ | </ | ||
- | Las instrucciones antes del DJNZ tienen un coste de ejecución de 7, 7, 6 y 4 t-estados respectivamente (empezando por el **LD A, (DE)** y acabando por el **INC H**). Esto suma un total de 24 t-estados por cada byte transferido. | + | Las instrucciones antes del '' |
Si invertimos el uso de los punteros y utilizamos HL como puntero al Sprite (origen) y DE como puntero a pantalla (destino), el bucle anterior podría haberse reescrito de la siguiente forma: | Si invertimos el uso de los punteros y utilizamos HL como puntero al Sprite (origen) y DE como puntero a pantalla (destino), el bucle anterior podría haberse reescrito de la siguiente forma: | ||
Línea 991: | Línea 1031: | ||
<code z80> | <code z80> | ||
drawsp8x8_loop: | drawsp8x8_loop: | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
</ | </ | ||
| | ||
- | Entre las 2 posibles formas de realizar la impresión (LD+INC vs LDI), utilizaremos la primera porque, como veremos a continuación, | + | Entre las 2 posibles formas de realizar la impresión ('' |
- | | + | |
Línea 1013: | Línea 1053: | ||
\\ | \\ | ||
- | | + | |
< | < | ||
Línea 1020: | Línea 1060: | ||
Valor en Videomemoria (HL): | Valor en Videomemoria (HL): | ||
Valor en el sprite - reg. A: 00111110 | Valor en el sprite - reg. A: 00111110 | ||
- | Operación: | + | Operación: |
Resultado en VRAM: 00111110 | Resultado en VRAM: 00111110 | ||
</ | </ | ||
- | La primera de las soluciones a este problema es la de escribir los píxeles con una operación lógica OR entre el scanline y el valor actual en memoria. Con la operación OR mezclaremos los bits de ambos elementos: | + | La primera de las soluciones a este problema es la de escribir los píxeles con una operación lógica |
< | < | ||
Línea 1035: | Línea 1075: | ||
</ | </ | ||
- | La operación OR nos permitirá respetar el fondo en aquellos juegos en que los sprites no tengan zonas a 0 dentro del contorno del mismo (que, como veremos más adelante, no es el caso de nuestro pequeño sprite). | + | La operación |
- | Para modificar la rutina de impresión de Sprites de forma que utilice OR (u otra operación lógica con otras aplicaciones como XOR), sólo necesitamos añadir la correspondiente instrucción lógica entre A y el contenido de la memoria: | + | Para modificar la rutina de impresión de Sprites de forma que utilice |
| | ||
<code z80> | <code z80> | ||
- | | + | |
drawsp8x8_loopLD: | drawsp8x8_loopLD: | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
</ | </ | ||
Línea 1055: | Línea 1095: | ||
<code z80> | <code z80> | ||
- | | + | |
drawsp8x8_loop_or: | drawsp8x8_loop_or: | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
</ | </ | ||
- | A continuación, | + | A continuación, |
<code z80> | <code z80> | ||
Línea 1084: | Línea 1124: | ||
; Guardamos en BC la pareja (x,y) -> B=COORD_Y y C=COORD_X | ; Guardamos en BC la pareja (x,y) -> B=COORD_Y y C=COORD_X | ||
- | | + | |
;;; Calculamos las coordenadas destino de pantalla en DE: | ;;; Calculamos las coordenadas destino de pantalla en DE: | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
; DE contiene ahora la direccion destino. | ; DE contiene ahora la direccion destino. | ||
Línea 1103: | Línea 1143: | ||
;;; | ;;; | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
; HL contiene la direccion de inicio en el sprite | ; HL contiene la direccion de inicio en el sprite | ||
- | | + | |
;;; Dibujar 8 scanlines (DE) -> (HL) y bajar scanline | ;;; Dibujar 8 scanlines (DE) -> (HL) y bajar scanline | ||
;;; Incrementar scanline del sprite (DE) | ;;; Incrementar scanline del sprite (DE) | ||
- | | + | |
drawsp8x8_loop_or: | drawsp8x8_loop_or: | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
;;; En este punto, los 8 scanlines del sprite estan dibujados. | ;;; En este punto, los 8 scanlines del sprite estan dibujados. | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | ;;; Considerar el dibujado de los atributos (Si DS_ATTRIBS=0 -> RET) | + | ;;; Considerar el dibujado de los atributos (Si DS_ATTRIBS=0 -> ret) |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
;;; Calcular posicion destino en area de atributos en DE. | ;;; Calcular posicion destino en area de atributos en DE. | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
;;; Copiar (HL) en (DE) -> Copiar atributo de sprite a pantalla | ;;; Copiar (HL) en (DE) -> Copiar atributo de sprite a pantalla | ||
- | | + | |
- | | + | |
- | | + | |
</ | </ | ||
Línea 1176: | Línea 1216: | ||
\\ | \\ | ||
- | | + | |
En tal caso, ¿qué hacemos para imprimir nuestro sprite respetando el fondo pero que a su vez podamos disponer de zonas que no sean transparentes? | En tal caso, ¿qué hacemos para imprimir nuestro sprite respetando el fondo pero que a su vez podamos disponer de zonas que no sean transparentes? | ||
Línea 1185: | Línea 1225: | ||
==== Impresión 8x8 usándo máscaras ==== | ==== Impresión 8x8 usándo máscaras ==== | ||
- | Como hemos visto, las operaciones con OR nos permiten respetar el fondo pero a su vez provocan zonas transparentes en nuestro sprite. En sistemas más modernos se utiliza un "color transparente" | + | Como hemos visto, las operaciones con '' |
La solución para respetar el fondo y sólo tener transparencias en los sprites en las zonas que nosotros deseemos es la utilización de máscaras. | La solución para respetar el fondo y sólo tener transparencias en los sprites en las zonas que nosotros deseemos es la utilización de máscaras. | ||
Línea 1191: | Línea 1231: | ||
La máscara es un bitmap del mismo tamaño que el sprite al que está asociada. Tiene un contorno similar al del sprite, donde colocamos a 1 todos los pixeles del fondo que necesitamos respetar (zonas transparentes) y a 0 todos los píxeles que se transferirán desde el sprite (píxeles opacos o píxeles del sprite) y que deben de ser borrados del fondo. | La máscara es un bitmap del mismo tamaño que el sprite al que está asociada. Tiene un contorno similar al del sprite, donde colocamos a 1 todos los pixeles del fondo que necesitamos respetar (zonas transparentes) y a 0 todos los píxeles que se transferirán desde el sprite (píxeles opacos o píxeles del sprite) y que deben de ser borrados del fondo. | ||
- | A la hora de dibujar el sprite en pantalla, se realiza un AND entre el valor del pixel y el valor del fondo (de los 8 píxeles, en el caso del Spectrum), con lo cual " | + | A la hora de dibujar el sprite en pantalla, se realiza un AND entre el valor del pixel y el valor del fondo (de los 8 píxeles, en el caso del Spectrum), con lo cual " |
De esta forma, podemos respetar todos los píxeles del fondo alrededor de la figura del personaje, así como algunas zonas de mismo que podamos querer que sean transparentes, | De esta forma, podemos respetar todos los píxeles del fondo alrededor de la figura del personaje, así como algunas zonas de mismo que podamos querer que sean transparentes, | ||
- | | + | |
\\ | \\ | ||
Línea 1201: | Línea 1241: | ||
\\ | \\ | ||
- | El resultado es que los ojos y la boca del sprite, que en la máscara están a 0, son borrados del fondo y por lo tanto no se produce el efecto de " | + | El resultado es que los ojos y la boca del sprite, que en la máscara están a 0, son borrados del fondo y por lo tanto no se produce el efecto de " |
No obstante, nuestro sprite sigue teniendo un problema relacionado con el sistema de atributos del Spectrum, y es que los píxeles de nuestro personaje se " | No obstante, nuestro sprite sigue teniendo un problema relacionado con el sistema de atributos del Spectrum, y es que los píxeles de nuestro personaje se " | ||
Línea 1231: | Línea 1271: | ||
La rutina de impresión de máscaras tiene ahora que recoger un dato extra por cada byte del sprite: la máscara que se corresponde con ese byte. Para no utilizar un puntero de memoria adicional en un array de máscara, lo ideal es exportar cada byte de máscara junto al byte del sprite correspondiente, | La rutina de impresión de máscaras tiene ahora que recoger un dato extra por cada byte del sprite: la máscara que se corresponde con ese byte. Para no utilizar un puntero de memoria adicional en un array de máscara, lo ideal es exportar cada byte de máscara junto al byte del sprite correspondiente, | ||
- | El pseudocódigo de la nueva rutina | + | El pseudocódigo de la nueva rutina |
< | < | ||
Línea 1267: | Línea 1307: | ||
; Guardamos en BC la pareja (x,y) -> B=COORD_Y y C=COORD_X | ; Guardamos en BC la pareja (x,y) -> B=COORD_Y y C=COORD_X | ||
- | | + | |
;;; Calculamos las coordenadas destino de pantalla en DE: | ;;; Calculamos las coordenadas destino de pantalla en DE: | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
; DE contiene ahora la direccion destino. | ; DE contiene ahora la direccion destino. | ||
Línea 1286: | Línea 1326: | ||
;;; | ;;; | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
; HL contiene la direccion de inicio en el sprite | ; HL contiene la direccion de inicio en el sprite | ||
- | | + | |
;;; Dibujar 8 scanlines (DE) -> (HL) + bajar scanline y avanzar en SPR | ;;; Dibujar 8 scanlines (DE) -> (HL) + bajar scanline y avanzar en SPR | ||
- | | + | |
drawspr8x8m_loop: | drawspr8x8m_loop: | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
;;; En este punto, los 8 scanlines del sprite estan dibujados. | ;;; En este punto, los 8 scanlines del sprite estan dibujados. | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | ;;; Considerar el dibujado de los atributos (Si DS_ATTRIBS=0 -> RET) | + | ;;; Considerar el dibujado de los atributos (Si DS_ATTRIBS=0 -> ret) |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
;;; Calcular posicion destino en area de atributos en DE. | ;;; Calcular posicion destino en area de atributos en DE. | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
;;; Copiar (HL) en (DE) -> Copiar atributo de sprite a pantalla | ;;; Copiar (HL) en (DE) -> Copiar atributo de sprite a pantalla | ||
- | | + | |
- | | + | |
- | | + | |
</ | </ | ||
Línea 1362: | Línea 1402: | ||
</ | </ | ||
- | El código para el cálculo agrega un "**ADD HL, HL**" | + | El código para el cálculo agrega un '' |
<code z80> | <code z80> | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
; HL contiene la direccion de inicio en el sprite | ; HL contiene la direccion de inicio en el sprite | ||
</ | </ | ||
Línea 1380: | Línea 1420: | ||
<code z80> | <code z80> | ||
- | LD B, 8 | + | ld b, 8 |
drawspr8x8m_loop: | drawspr8x8m_loop: | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
</ | </ | ||
Línea 1441: | Línea 1481: | ||
| | ||
- | Un apunte final sobre los ejemplos que hemos visto: nótese que estamos llamando a todas las rutinas asignando DS_NUMSPR=0, | + | Un apunte final sobre los ejemplos que hemos visto: nótese que estamos llamando a todas las rutinas asignando |
Línea 1458: | Línea 1498: | ||
\\ | \\ | ||
- | | + | * El cálculo de la dirección origen en el sprite cambia, ya que ahora cada scanline ocupa 2 bytes y no 1, y tenemos 2 bloques de altura en el sprite y no uno. Antes calculábamos la dirección origen como BASE+(DS_NUMSPR*8), |
- | * Para multiplicar DS_NUMSPR por 32 vamos a utilizar desplazamientos a la derecha de un pseudo-registro de 16 bits formado por A y L en lugar de utilizar sumas sucesivas **ADD HL, HL**. Esta técnica requiere menos ciclos de reloj para su ejecución. | + | |
- | * La impresión de datos debe imprimir todo un scanline horizontal del Sprite (2 bytes) antes de avanzar al siguiente scanline de pantalla. | + | * Para multiplicar DS_NUMSPR por 32 vamos a utilizar desplazamientos a la derecha de un pseudo-registro de 16 bits formado por A y L en lugar de utilizar sumas sucesivas **add hl, hl**. Esta técnica requiere menos ciclos de reloj para su ejecución. |
- | * El avance al siguiente scanline cambia ligeramente, | + | |
- | * El bucle vertical es de 16 iteraciones en lugar de 8, ya que el sprite tiene 2 caracteres verticales. | + | * La impresión de datos debe imprimir todo un scanline horizontal del Sprite (2 bytes) antes de avanzar al siguiente scanline de pantalla. |
- | * El cálculo de la dirección origen en los atributos cambia, ya que ahora tenemos 4 bytes de atributos por cada sprite. Así, la posición ya no se calcula como BASE+(DS_NUMSPR*1) sino como BASE+(DS_NUMSPR*4). | + | |
- | * La impresión de los atributos también cambia, ya que ahora hay que imprimir 4 atributos (2x2 bloques) en lugar de sólo 1. | + | * El avance al siguiente scanline cambia ligeramente, |
+ | |||
+ | * El bucle vertical es de 16 iteraciones en lugar de 8, ya que el sprite tiene 2 caracteres verticales. | ||
+ | |||
+ | * El cálculo de la dirección origen en los atributos cambia, ya que ahora tenemos 4 bytes de atributos por cada sprite. Así, la posición ya no se calcula como BASE+(DS_NUMSPR*1) sino como BASE+(DS_NUMSPR*4). | ||
+ | |||
+ | * La impresión de los atributos también cambia, ya que ahora hay que imprimir 4 atributos (2x2 bloques) en lugar de sólo 1. | ||
Línea 1480: | Línea 1526: | ||
; Dibujar byte (DE) -> (HL), trazando el scanline del 2o bloque | ; Dibujar byte (DE) -> (HL), trazando el scanline del 2o bloque | ||
; Incrementar DE | ; Incrementar DE | ||
- | ; Bajar a siguiente scanline en pantalla (HL), sumando 256 (INC H/DEC L). | + | ; Bajar a siguiente scanline en pantalla (HL), sumando 256 (inc h/dec l). |
; Avanzar puntero de pantalla (HL) a la posicion de la segunda | ; Avanzar puntero de pantalla (HL) a la posicion de la segunda | ||
Línea 1490: | Línea 1536: | ||
; Dibujar byte (DE) -> (HL), trazando el scanline del 2o bloque | ; Dibujar byte (DE) -> (HL), trazando el scanline del 2o bloque | ||
; Incrementar DE | ; Incrementar DE | ||
- | ; Bajar a siguiente scanline en pantalla (HL), sumando 256 (INC H/DEC L). | + | ; Bajar a siguiente scanline en pantalla (HL), sumando 256 (inc h/dec l). |
- | ; Si base_atributos == 0 -> RET | + | ; Si base_atributos == 0 -> ret |
; Calcular posicion origen de los atributos array_attr+(NUM_SPRITE*4) en HL. | ; Calcular posicion origen de los atributos array_attr+(NUM_SPRITE*4) en HL. | ||
; Calcular posicion destino en area de atributos en DE. | ; Calcular posicion destino en area de atributos en DE. | ||
Línea 1526: | Línea 1572: | ||
; Guardamos en BC la pareja (x,y) -> B=COORD_Y y C=COORD_X | ; Guardamos en BC la pareja (x,y) -> B=COORD_Y y C=COORD_X | ||
- | | + | |
;;; Calculamos las coordenadas destino de pantalla en DE: | ;;; Calculamos las coordenadas destino de pantalla en DE: | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
; calcular la direccion del atributo | ; calcular la direccion del atributo | ||
Línea 1547: | Línea 1593: | ||
;;; | ;;; | ||
;;; Multiplicamos con desplazamientos, | ;;; Multiplicamos con desplazamientos, | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
; HL contiene la direccion de inicio en el sprite | ; HL contiene la direccion de inicio en el sprite | ||
- | | + | |
;;; Repetir 8 veces (primeros 2 bloques horizontales): | ;;; Repetir 8 veces (primeros 2 bloques horizontales): | ||
- | | + | |
drawsp16x16_loop1: | drawsp16x16_loop1: | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
; Avanzamos HL 1 scanline (codigo de incremento de HL en 1 scanline) | ; Avanzamos HL 1 scanline (codigo de incremento de HL en 1 scanline) | ||
; desde el septimo scanline de la fila Y+1 al primero de la Y+2 | ; desde el septimo scanline de la fila Y+1 al primero de la Y+2 | ||
- | ;;;INC H ; No hay que hacer INC H, lo hizo en el bucle | + | ;;;inc h ; No hay que hacer inc h, lo hizo en el bucle |
- | ;;;LD A, H ; No hay que hacer esta prueba, sabemos que | + | ;;;ld a, h ; No hay que hacer esta prueba, sabemos que |
- | ;;;AND 7 ; no hay salto (es un cambio de bloque) | + | ;;;and 7 ; no hay salto (es un cambio de bloque) |
- | ;;;JR NZ, drawsp16_nofix_abajop | + | ;;;jr nz, drawsp16_nofix_abajop |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
drawsp16_nofix_abajop: | drawsp16_nofix_abajop: | ||
;;; Repetir 8 veces (segundos 2 bloques horizontales): | ;;; Repetir 8 veces (segundos 2 bloques horizontales): | ||
- | | + | |
drawsp16x16_loop2: | drawsp16x16_loop2: | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
;;; En este punto, los 16 scanlines del sprite estan dibujados. | ;;; En este punto, los 16 scanlines del sprite estan dibujados. | ||
- | | + | |
- | ;;; Considerar el dibujado de los atributos (Si DS_ATTRIBS=0 -> RET) | + | ;;; Considerar el dibujado de los atributos (Si DS_ATTRIBS=0 -> ret) |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
;;; Calcular posicion destino en area de atributos en DE. | ;;; Calcular posicion destino en area de atributos en DE. | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
;;; Avance diferencial a la siguiente linea de atributos | ;;; Avance diferencial a la siguiente linea de atributos | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
drawsp16x16_attrab_noinc: | drawsp16x16_attrab_noinc: | ||
- | | + | |
- | | + | |
- | | + | |
</ | </ | ||
\\ | \\ | ||
- | Lo primero que nos llama la atención de la rutina es la forma de multiplicar por 32 el valor de DS_NUMSPR. Una primera aproximación de multiplicación de HL = NUM_SPR * 32 podría ser aumentar el número de sumas **ADD HL, HL** tal y como se realizan en las rutinas de 8x8: | + | Lo primero que nos llama la atención de la rutina es la forma de multiplicar por 32 el valor de '' |
<code z80> | <code z80> | ||
;;; Multiplicar DS_SPRITES por 32 con sumas | ;;; Multiplicar DS_SPRITES por 32 con sumas | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
</ | </ | ||
- | Esta porción de código tarda 11 t-estados por cada ADD de 16 bits, más 7 t-estados de **LD H, 0**, más 4 de **LD L, A**, lo que da un total de 77 t-estados para realizar la multiplicación. | + | Esta porción de código tarda 11 t-estados por cada '' |
La técnica empleada en el listado, proporcionada por **metalbrain**, | La técnica empleada en el listado, proporcionada por **metalbrain**, | ||
Línea 1682: | Línea 1728: | ||
<code z80> | <code z80> | ||
;;; Multiplicar DS_SPRITES por 32 con desplazamientos >> | ;;; Multiplicar DS_SPRITES por 32 con desplazamientos >> | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
; HL contiene la direccion de inicio en el sprite | ; HL contiene la direccion de inicio en el sprite | ||
</ | </ | ||
- | Esta porción de código tiene un coste de 11 (ADD) + 8 + 8 + 8 (RR) + 4 + 4 + 4 (RRA) + 7 + 4 (LD) = 58 t-estados, 29 ciclos de reloj menos que la rutina con ADD. | + | Esta porción de código tiene un coste de 11 ('' |
- | Si tuvieramos que multiplicar por 64 (realizar un RRA/RL menos), el coste sería todavía menor: 12 t-estados menos con un total de 46 t-estados. En el caso de las rutinas de 8x8, resultaba más rápido realizar la multiplicación por medio de sumas que por desplazamientos, | + | Si tuvieramos que multiplicar por 64 (realizar un '' |
- | Otra parte interesante de la rutina de dibujado de sprites está en la impresión de los datos gráficos. En esta ocasión hay que imprimir 2 bytes horizontales en cada scanline, y sumar 256 para avanzar a la siguiente línea de pantalla. Esto nos obliga a decrementar HL en 1 unidad (con **DEC L**) para compensar el avance horizontal utilizado para posicionarnos en el lugar de dibujado del segundo bloque del sprite. Tras esto, ya podemos hacer el avance de scanline con un simple | + | Otra parte interesante de la rutina de dibujado de sprites está en la impresión de los datos gráficos. En esta ocasión hay que imprimir 2 bytes horizontales en cada scanline, y sumar 256 para avanzar a la siguiente línea de pantalla. Esto nos obliga a decrementar HL en 1 unidad (con '' |
- | Una vez finalizado el bucle de 8 iteraciones que imprime los datos de los 2 bloques de la fila 1 del sprite, debemos avanzar al siguiente scanline de pantalla (8 más abajo de la posicion Y inicial) para trazar los 2 bloques restantes (los bloques "de abajo" | + | Una vez finalizado el bucle de 8 iteraciones que imprime los datos de los 2 bloques de la fila 1 del sprite, debemos avanzar al siguiente scanline de pantalla (8 más abajo de la posicion Y inicial) para trazar los 2 bloques restantes (los bloques "de abajo" |
Tras ajustar HL tenemos que dibujar los 2 últimos bloques del sprite, con un bucle similar al que dibujó los 2 primeros. | Tras ajustar HL tenemos que dibujar los 2 últimos bloques del sprite, con un bucle similar al que dibujó los 2 primeros. | ||
Línea 1711: | Línea 1757: | ||
<code z80> | <code z80> | ||
- | | + | |
drawsp16x16_loop: | drawsp16x16_loop: | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
; Avanzamos HL 1 scanline (codigo de incremento de HL en 1 scanline) | ; Avanzamos HL 1 scanline (codigo de incremento de HL en 1 scanline) | ||
- | ;;;INC H ; No hay que hacer INC H, lo hizo en el bucle | + | ;;;inc h ; No hay que hacer inc h, lo hizo en el bucle |
- | ;;;LD A, H ; No hay que hacer esta prueba, sabemos que | + | ;;;ld a, h ; No hay que hacer esta prueba, sabemos que |
- | ;;;AND 7 ; no hay salto (es un cambio de bloque) | + | ;;;and 7 ; no hay salto (es un cambio de bloque) |
- | ;;;JR NZ, drawsp16_nofix_abajop | + | ;;;jr nz, drawsp16_nofix_abajop |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
drawsp16_nofix_abajop: | drawsp16_nofix_abajop: | ||
- | | + | |
</ | </ | ||
- | Este código es más pequeño en tamaño que el uso de 2 bucles, pero estamos efectuando un JR innecesario en 14 de las 16 iteraciones, | + | Este código es más pequeño en tamaño que el uso de 2 bucles, pero estamos efectuando un '' |
- | | + | |
| | ||
Línea 1769: | Línea 1815: | ||
<code z80> | <code z80> | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
</ | </ | ||
Línea 1798: | Línea 1844: | ||
==== Impresión 16x16 usándo operaciones lógicas ==== | ==== Impresión 16x16 usándo operaciones lógicas ==== | ||
- | | + | |
| | ||
<code z80> | <code z80> | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
</ | </ | ||
Línea 1812: | Línea 1858: | ||
<code z80> | <code z80> | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
</ | </ | ||
- | El resto de la rutina es esencialmente idéntico a la versión con transferencias de datos LD. | + | El resto de la rutina es esencialmente idéntico a la versión con transferencias de datos '' |
Línea 1829: | Línea 1875: | ||
\\ | \\ | ||
- | * El cálculo de la dirección origen en el sprite cambia, ya que ahora cada scanline ocupa 4 bytes (2 del sprite y 2 de la máscara) y no 2, y tenemos 2 bloques de altura en el sprite y no uno. Como tenemos el doble de datos gráficos por cada sprite, si antes habíamos calculado la dirección origen como BASE+(DS_NUMSPR*32), | + | * El cálculo de la dirección origen en el sprite cambia, ya que ahora cada scanline ocupa 4 bytes (2 del sprite y 2 de la máscara) y no 2, y tenemos 2 bloques de altura en el sprite y no uno. Como tenemos el doble de datos gráficos por cada sprite, si antes habíamos calculado la dirección origen como BASE+(DS_NUMSPR*32), |
\\ | \\ | ||
Línea 1851: | Línea 1897: | ||
; Guardamos en BC la pareja (x,y) -> B=COORD_Y y C=COORD_X | ; Guardamos en BC la pareja (x,y) -> B=COORD_Y y C=COORD_X | ||
- | | + | |
;;; Calculamos las coordenadas destino de pantalla en DE: | ;;; Calculamos las coordenadas destino de pantalla en DE: | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
; calcular la direccion del atributo | ; calcular la direccion del atributo | ||
Línea 1872: | Línea 1918: | ||
;;; | ;;; | ||
;;; Multiplicamos con desplazamientos, | ;;; Multiplicamos con desplazamientos, | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
; HL contiene la direccion de inicio en el sprite | ; HL contiene la direccion de inicio en el sprite | ||
- | | + | |
;;; Dibujar 8 scanlines (DE) -> (HL) + bajar scanline y avanzar en SPR | ;;; Dibujar 8 scanlines (DE) -> (HL) + bajar scanline y avanzar en SPR | ||
- | | + | |
drawspr16m_loop1: | drawspr16m_loop1: | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
; Avanzamos HL 1 scanline (codigo de incremento de HL en 1 scanline) | ; Avanzamos HL 1 scanline (codigo de incremento de HL en 1 scanline) | ||
; desde el septimo scanline de la fila Y+1 al primero de la Y+2 | ; desde el septimo scanline de la fila Y+1 al primero de la Y+2 | ||
- | ;;;INC H ; No hay que hacer INC H, lo hizo en el bucle | + | ;;;inc h ; No hay que hacer inc h, lo hizo en el bucle |
- | ;;;LD A, H ; No hay que hacer esta prueba, sabemos que | + | ;;;ld a, h ; No hay que hacer esta prueba, sabemos que |
- | ;;;AND 7 ; no hay salto (es un cambio de bloque) | + | ;;;and 7 ; no hay salto (es un cambio de bloque) |
- | ;;;JR NZ, drawsp16_nofix_abajop | + | ;;;jr nz, drawsp16_nofix_abajop |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
drawsp16m_nofix_abajop: | drawsp16m_nofix_abajop: | ||
;;; Repetir 8 veces (segundos 2 bloques horizontales): | ;;; Repetir 8 veces (segundos 2 bloques horizontales): | ||
- | | + | |
drawspr16m_loop2: | drawspr16m_loop2: | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
;;; En este punto, los 16 scanlines del sprite estan dibujados. | ;;; En este punto, los 16 scanlines del sprite estan dibujados. | ||
- | | + | |
- | ;;; Considerar el dibujado de los atributos (Si DS_ATTRIBS=0 -> RET) | + | ;;; Considerar el dibujado de los atributos (Si DS_ATTRIBS=0 -> ret) |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
;;; Calcular posicion destino en area de atributos en DE. | ;;; Calcular posicion destino en area de atributos en DE. | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
;;; Avance diferencial a la siguiente linea de atributos | ;;; Avance diferencial a la siguiente linea de atributos | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
drawsp16m_attrab_noinc: | drawsp16m_attrab_noinc: | ||
- | | + | |
- | | + | |
- | | + | |
</ | </ | ||
Datos a destacar sobre el código: | Datos a destacar sobre el código: | ||
- | * Al igual que en el caso de la rutina con LD y con OR, el bucle de 16 iteraciones de la rutina de impresión con máscaras, se debería desenrollar si el trazado de sprites es una rutina prioritaria en nuestro programa (que suele ser el caso, especialmente en juegos). No obstante, hay que tener en cuenta que desenrollar estos 2 bucles supone añadir a la rutina 14 veces el tamaño de cada iteración (hay 16 iteraciones, | + | * Al igual que en el caso de la rutina con '' |
| | ||
Línea 2079: | Línea 2125: | ||
\\ | \\ | ||
\\ | \\ | ||
- | **Impresión con transferencia (LD/LDI):** | + | **Impresión con transferencia (LD/ldi):** |
La impresión por transferencia directa de datos (sprite -> pantalla) es adecuada para juegos con movimiento " | La impresión por transferencia directa de datos (sprite -> pantalla) es adecuada para juegos con movimiento " | ||
Línea 2096: | Línea 2142: | ||
**Impresión por operación lógica OR:** | **Impresión por operación lógica OR:** | ||
- | La impresión por OR es adecuada en juegos donde el personaje no tenga pixeles a cero dentro de su contorno, de forma que no se vean píxeles del fondo en el interior de nuestro personaje, alterando el sprite. | + | La impresión por '' |
La segunda opción (y la más habitual) es su uso en juegos donde (sea como sea el personaje del jugador) el fondo sea plano (sin tramas) y el color del mismo sea idéntico al color de PAPER de los sprites del juego. De esta forma nos podemos mover en el juego sin causar colisión de atributos con el fondo (ya que no tiene pixeles activos y el PAPER del fondo es igual al PAPER de nuestro Sprite) y podemos ubicar en la pantalla objetos multicolor en posiciones de carácter que no provoquen colisión de atributos con nuestro personaje o si la provocan sea imperceptible (elemento muy ajustado a tamaño de carácter) o durante un período de tiempo mínimo (recogida de un objeto). | La segunda opción (y la más habitual) es su uso en juegos donde (sea como sea el personaje del jugador) el fondo sea plano (sin tramas) y el color del mismo sea idéntico al color de PAPER de los sprites del juego. De esta forma nos podemos mover en el juego sin causar colisión de atributos con el fondo (ya que no tiene pixeles activos y el PAPER del fondo es igual al PAPER de nuestro Sprite) y podemos ubicar en la pantalla objetos multicolor en posiciones de carácter que no provoquen colisión de atributos con nuestro personaje o si la provocan sea imperceptible (elemento muy ajustado a tamaño de carácter) o durante un período de tiempo mínimo (recogida de un objeto). | ||
Línea 2111: | Línea 2157: | ||
**Impresión por operación lógica XOR:** | **Impresión por operación lógica XOR:** | ||
- | Más que juegos adecuados para esta técnica, podemos hablar más bien de sprites adecuados para ella. Nos referimos a los cursores, que suelen ser dibujados con XOR para no confundirse con el fondo (permitiendo una visión " | + | Más que juegos adecuados para esta técnica, podemos hablar más bien de sprites adecuados para ella. Nos referimos a los cursores, que suelen ser dibujados con '' |
\\ | \\ | ||
Línea 2144: | Línea 2190: | ||
La rutina, al ser genérica, y para que pueda ser legible por el lector, no es especialmente eficiente: debido a la necesidad de realizar multiplicaciones de 16 bits, se utilizan los registros HL, DE y BC y nos vemos obligados a hacer continuos PUSHes y POPs de estos registros, así como a apoyarnos en variables de memoria. La rutina podría ser optimizada en cuanto a algoritmo y/o en cuanto a reordenación de código y uso de shadow-registers para tratar de ganar todos los t-estados posibles. | La rutina, al ser genérica, y para que pueda ser legible por el lector, no es especialmente eficiente: debido a la necesidad de realizar multiplicaciones de 16 bits, se utilizan los registros HL, DE y BC y nos vemos obligados a hacer continuos PUSHes y POPs de estos registros, así como a apoyarnos en variables de memoria. La rutina podría ser optimizada en cuanto a algoritmo y/o en cuanto a reordenación de código y uso de shadow-registers para tratar de ganar todos los t-estados posibles. | ||
- | | + | |
<code z80> | <code z80> | ||
Línea 2169: | Línea 2215: | ||
;;;; Multiplicamos ancho por alto (en bloques) | ;;;; Multiplicamos ancho por alto (en bloques) | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
;;; Multiplicamos Ancho_bloques * Alto_pixeles: | ;;; Multiplicamos Ancho_bloques * Alto_pixeles: | ||
- | | + | |
- | | + | |
drawsp_mul1: | drawsp_mul1: | ||
- | | + | |
- | | + | |
; Ahora A = Ancho*Alto (maximo 255!!!) | ; Ahora A = Ancho*Alto (maximo 255!!!) | ||
;;; Multiplicamos DS_NUMSPR por (Ancho_bloques*Alto_pixeles) | ;;; Multiplicamos DS_NUMSPR por (Ancho_bloques*Alto_pixeles) | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
drawsp_mul2: | drawsp_mul2: | ||
- | | + | |
- | | + | |
; guardamos el valor de ancho*alto_pixeles*NUMSPR | ; guardamos el valor de ancho*alto_pixeles*NUMSPR | ||
- | | + | |
;;; Calculamos direccion origen copia en el sprite | ;;; Calculamos direccion origen copia en el sprite | ||
- | | + | |
- | | + | |
; HL contiene la direccion de inicio en el sprite | ; HL contiene la direccion de inicio en el sprite | ||
;;; Calculamos las coordenadas destino de pantalla en DE: | ;;; Calculamos las coordenadas destino de pantalla en DE: | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
; calcular la direccion del atributo | ; calcular la direccion del atributo | ||
- | | + | |
;;; Bucle de impresión vertical | ;;; Bucle de impresión vertical | ||
; Recogemos de nuevo la altura en pixeles | ; Recogemos de nuevo la altura en pixeles | ||
- | | + | |
- | | + | |
drawsp_yloop: | drawsp_yloop: | ||
- | | + | |
;;; Bucle de impresion horizontal | ;;; Bucle de impresion horizontal | ||
- | | + | |
- | | + | |
- | | + | |
; para poder volver a el luego | ; para poder volver a el luego | ||
drawsp_xloop: | drawsp_xloop: | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
;;; Avanzamos al siguiente scanline de pantalla | ;;; Avanzamos al siguiente scanline de pantalla | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
drawspNM_nofix: | drawspNM_nofix: | ||
- | | + | |
- | | + | |
;;; Aqui hemos dibujado todo el sprite, vamos a los attributos | ;;; Aqui hemos dibujado todo el sprite, vamos a los attributos | ||
- | | + | |
- | ;;; Considerar el dibujado de atributos (Si DS_ATTRIBS=0 -> RET) | + | ;;; Considerar el dibujado de atributos (Si DS_ATTRIBS=0 -> ret) |
- | | + | |
- | | + | |
- | | + | |
;;; Calcular posicion destino en area de atributos en DE. | ;;; Calcular posicion destino en area de atributos en DE. | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
; Recuperamos el valor de ancho_caracteres * alto_en_pixeles * NUMSPR | ; Recuperamos el valor de ancho_caracteres * alto_en_pixeles * NUMSPR | ||
; para ahorrarnos repetir otra vez dos multiplicaciones: | ; para ahorrarnos repetir otra vez dos multiplicaciones: | ||
- | | + | |
;;; HL = ANCHO_BLOQUES*ALTO_PIXELES*NUMSPR | ;;; HL = ANCHO_BLOQUES*ALTO_PIXELES*NUMSPR | ||
;;; El Alto lo necesitamos en BLOQUES, no en píxeles-> | ;;; El Alto lo necesitamos en BLOQUES, no en píxeles-> | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
;;;; HL = ANCHO_BLOQUES*ALTO_BLOQUES*NUMSPR | ;;;; HL = ANCHO_BLOQUES*ALTO_BLOQUES*NUMSPR | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
;;; Bucle impresion vertical de atributos | ;;; Bucle impresion vertical de atributos | ||
drawsp_attyloop: | drawsp_attyloop: | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
;;; Bucle impresion horizontal de atributos | ;;; Bucle impresion horizontal de atributos | ||
drawsp_attxloop: | drawsp_attxloop: | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
;;; Avance diferencial a la siguiente linea de atributos | ;;; Avance diferencial a la siguiente linea de atributos | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
drawsp_attrab_noinc: | drawsp_attrab_noinc: | ||
- | | + | |
- | | + | |
- | | + | |
drawsp_height | drawsp_height | ||
Línea 2345: | Línea 2391: | ||
<code z80> | <code z80> | ||
- | ORG 35000 | + | |
- | LD HL, bicho_gfx | + | |
- | LD (DS_SPRITES), | + | |
- | LD HL, bicho_attrib | + | |
- | LD (DS_ATTRIBS), | + | |
- | LD A, 13 | + | |
- | LD (DS_COORD_X), | + | |
- | LD A, 8 | + | |
- | LD (DS_COORD_Y), | + | |
- | LD A, 2 | + | |
- | LD (DS_WIDTH), A | + | |
- | LD A, 2 | + | |
- | LD (DS_HEIGHT), | + | |
- | XOR A | + | |
- | LD (DS_NUMSPR), | + | |
- | | + | |
- | | + | ld (DS_SPRITES), |
+ | ld hl, bicho_attrib | ||
+ | ld (DS_ATTRIBS), | ||
+ | ld a, 13 | ||
+ | ld (DS_COORD_X), | ||
+ | ld a, 8 | ||
+ | ld (DS_COORD_Y), | ||
+ | ld a, 2 | ||
+ | ld (DS_WIDTH), a | ||
+ | ld a, 2 | ||
+ | ld (DS_HEIGHT), | ||
+ | xor a | ||
+ | ld (DS_NUMSPR), | ||
+ | |||
+ | call DrawSprite_MxN_LD | ||
+ | | ||
; Variables que usaremos como parámetros | ; Variables que usaremos como parámetros | ||
Línea 2374: | Línea 2421: | ||
</ | </ | ||
- | La rutina anterior trabaja mediante transferencia de datos sprite -> pantalla. Para realizar una operación lógica, bastará modificar el bucle horizontal de impresión y añadir el correspondiente OR / XOR contra (HL). | + | La rutina anterior trabaja mediante transferencia de datos sprite -> pantalla. Para realizar una operación lógica, bastará modificar el bucle horizontal de impresión y añadir el correspondiente |
La rutina para trabajar con máscaras implicar modificar el bucle principal para gestionar los datos de la máscara y modificar también las multiplicaciones iniciales de parámetros (*2 en el caso de los datos gráficos, sin modificación en los atributos). | La rutina para trabajar con máscaras implicar modificar el bucle principal para gestionar los datos de la máscara y modificar también las multiplicaciones iniciales de parámetros (*2 en el caso de los datos gráficos, sin modificación en los atributos). | ||
Línea 2413: | Línea 2460: | ||
- cuando vayas a imprimir un caracter consulta primero su entrada en el buffer. | - cuando vayas a imprimir un caracter consulta primero su entrada en el buffer. | ||
- | Si está libre puedes volcar el gráfico directamente con LD (HL),A... porque es | + | Si está libre puedes volcar el gráfico directamente con ld (hl),a... porque es |
más rápido y sabes *seguro* que puedes machacar lo que haya en pantalla. Si por | más rápido y sabes *seguro* que puedes machacar lo que haya en pantalla. Si por | ||
el contrario está ocupado (o sea, hay otro sprite en esa posición) sabes que tienes | el contrario está ocupado (o sea, hay otro sprite en esa posición) sabes que tienes | ||
Línea 2443: | Línea 2490: | ||
**Borrado (e impresión) con XOR** | **Borrado (e impresión) con XOR** | ||
- | | + | |
- | | + | |
|< 30% >| | |< 30% >| | ||
Línea 2454: | Línea 2501: | ||
| 1 | 1 | 0 | | | 1 | 1 | 0 | | ||
- | A continuación tenemos un ejemplo de cómo restaurar el fondo gracias a XOR: | + | A continuación tenemos un ejemplo de cómo restaurar el fondo gracias a '' |
< | < | ||
Línea 2471: | Línea 2518: | ||
</ | </ | ||
- | La impresión de un sprite con XOR produce una especie de " | + | La impresión de un sprite con '' |
Línea 2504: | Línea 2551: | ||
;;; Recogida de datos y parametros | ;;; Recogida de datos y parametros | ||
(...) | (...) | ||
- | | + | |
(...) | (...) | ||
;;; Bucle de impresion del Sprite: | ;;; Bucle de impresion del Sprite: | ||
- | | + | |
drawsp8x8_loopLD: | drawsp8x8_loopLD: | ||
- | | + | |
- | | + | |
- | | + | |
; Ya podemos imprimir el sprite | ; Ya podemos imprimir el sprite | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
(...) | (...) | ||
;;; Impresion de atributos | ;;; Impresion de atributos | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
DS_TEMPBUF | DS_TEMPBUF | ||
</ | </ | ||
- | El registro IX no es especialmente rápido (10 t-estados el **INC IX** y 19 t-estados el **LD (IX), A** o **LD (IX+0), A**, pero es más rápido utilizarlo que escribir y ejecutar una rutina adicional para almacenar el fondo. | + | El registro IX no es especialmente rápido (10 t-estados el '' |
| | ||
Línea 2560: | Línea 2607: | ||
Por otra parte, si no tenemos intención de llamar a estas funciones desde BASIC, lo más óptimo sería eliminar el paso de parámetros por variables de memoria y utilizar registros y/o pila allá donde sea posible, puesto que estas operaciones son más rápidas que los acceso a memoria. | Por otra parte, si no tenemos intención de llamar a estas funciones desde BASIC, lo más óptimo sería eliminar el paso de parámetros por variables de memoria y utilizar registros y/o pila allá donde sea posible, puesto que estas operaciones son más rápidas que los acceso a memoria. | ||
- | No obstante, nótese que las 2 variables que más tiempo cuesta de establecer y de leer (DS_SPRITES y DS_ATTRIBS) se pueden establecer como constantes (y no como variables) directamente dentro de la rutina de impresión. Eso quiere decir que si tenemos un único tileset, podríamos cambiar partes de la rutina, como: | + | No obstante, nótese que las 2 variables que más tiempo cuesta de establecer y de leer ('' |
<code z80> | <code z80> | ||
- | | + | |
</ | </ | ||
Línea 2569: | Línea 2616: | ||
<code z80> | <code z80> | ||
- | | + | |
</ | </ | ||
- | | + | |
Línea 2578: | Línea 2625: | ||
**Deshabilitar las interrupciones** | **Deshabilitar las interrupciones** | ||
- | Si nuestra rutina de dibujado de interrupciones es crítica, puede que nos resulte necesario en algunas circunstancias realizar un **DI** (Disable Interrupts) al principio de la misma y un **EI** (Enable Interrupts) al acabar, para evitar que una interrupción del Z80 sea lanzada durante la ejecución de la misma. | + | Si nuestra rutina de dibujado de interrupciones es crítica, puede que nos resulte necesario en algunas circunstancias realizar un '' |
| | ||
- | No obstante, si no estamos sincronizados con las interrupciones o si estamos usando | + | No obstante, si no estamos sincronizados con las interrupciones o si estamos usando |
| | ||
Línea 2591: | Línea 2638: | ||
**Uso de la pila para acceso a datos** | **Uso de la pila para acceso a datos** | ||
- | Una opción realmente ingeniosa para optimizar el acceso al sprite sería la de utilizar la pila para realizar operaciones de lectura de 2 bytes del sprite con una única instrucción (POP). | + | Una opción realmente ingeniosa para optimizar el acceso al sprite sería la de utilizar la pila para realizar operaciones de lectura de 2 bytes del sprite con una única instrucción ('' |
- | | + | |
- | Como desventaja, debemos deshabilitar las interrupciones con **DI** al principio del programa y habilitarlas de nuevo antes del RET con **EI** ya que no podemos permitir la ejecución de una ISR estando SP apuntando al Sprite. Un "RST" | + | Como desventaja, debemos deshabilitar las interrupciones con **di** al principio del programa y habilitarlas de nuevo antes del ret con '' |
| | ||
< | < | ||
- | ; DI | + | ; di |
; Calcular dirección destino en pantalla | ; Calcular dirección destino en pantalla | ||
; Calcular dirección origen de sprite | ; Calcular dirección origen de sprite | ||
Línea 2612: | Línea 2659: | ||
; Recuperar puntero de pila | ; Recuperar puntero de pila | ||
- | ; EI | + | ; ei |
- | ; RET | + | ; ret |
</ | </ | ||
- | A continuación vamos a ver una versión " | + | A continuación vamos a ver una versión " |
- | En la rutina guardaremos el valor de SP en una variable temporal, antes de modificarlo, | + | En la rutina guardaremos el valor de SP en una variable temporal, antes de modificarlo, |
<code z80> | <code z80> | ||
Línea 2632: | Línea 2679: | ||
;;; Deshabilitar interrupciones (vamos a cambiar SP). | ;;; Deshabilitar interrupciones (vamos a cambiar SP). | ||
- | | + | |
; Guardamos en BC la pareja (x,y) -> B=COORD_Y y C=COORD_X | ; Guardamos en BC la pareja (x,y) -> B=COORD_Y y C=COORD_X | ||
- | | + | |
;;; Calculamos las coordenadas destino de pantalla en DE: | ;;; Calculamos las coordenadas destino de pantalla en DE: | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
;;; Guardamos el valor actual y correcto de SP | ;;; Guardamos el valor actual y correcto de SP | ||
- | | + | |
;;; Calcular posicion origen (array sprites) en HL: | ;;; Calcular posicion origen (array sprites) en HL: | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
; HL contiene la direccion de inicio en el sprite | ; HL contiene la direccion de inicio en el sprite | ||
;;; Establecemos el valor de SP apuntando al sprite | ;;; Establecemos el valor de SP apuntando al sprite | ||
- | | + | |
- | | + | |
;;; Repetir 8 veces (primeros 2 bloques horizontales): | ;;; Repetir 8 veces (primeros 2 bloques horizontales): | ||
- | | + | |
drawsp16x16_stack_loop: | drawsp16x16_stack_loop: | ||
- | | + | |
; Y ademas no hace falta sumar 2 a DE | ; Y ademas no hace falta sumar 2 a DE | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
; Avanzamos HL 1 scanline (codigo de incremento de HL en 1 scanline) | ; Avanzamos HL 1 scanline (codigo de incremento de HL en 1 scanline) | ||
; desde el septimo scanline de la fila Y+1 al primero de la Y+2 | ; desde el septimo scanline de la fila Y+1 al primero de la Y+2 | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
drawsp16_nofix_abajop: | drawsp16_nofix_abajop: | ||
- | | + | |
;;; En este punto, los 16 scanlines del sprite estan dibujados. | ;;; En este punto, los 16 scanlines del sprite estan dibujados. | ||
;;; Recuperamos el valor de SP | ;;; Recuperamos el valor de SP | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
</ | </ | ||
- | Al igual que en el caso de las rutinas anteriores, si desenrollamos el bucle de 16 iteraciones en 2 bucles de 8, podemos utilizar **INC H** dentro de los bucles y el código de // | + | Al igual que en el caso de las rutinas anteriores, si desenrollamos el bucle de 16 iteraciones en 2 bucles de 8, podemos utilizar **inc h** dentro de los bucles y el código de // |
<code z80> | <code z80> | ||
drawsp16x16_stack_loop1: | drawsp16x16_stack_loop1: | ||
- | | + | |
; Y ademas no hace falta sumar 2 a DE | ; Y ademas no hace falta sumar 2 a DE | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
; Avanzamos HL 1 scanline (codigo de incremento de HL en 1 scanline) | ; Avanzamos HL 1 scanline (codigo de incremento de HL en 1 scanline) | ||
; desde el septimo scanline de la fila Y+1 al primero de la Y+2 | ; desde el septimo scanline de la fila Y+1 al primero de la Y+2 | ||
; No hay que comprobar si (H & $07) == 0 porque sabemos que lo es | ; No hay que comprobar si (H & $07) == 0 porque sabemos que lo es | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
drawsp16_nofix_abajop: | drawsp16_nofix_abajop: | ||
- | LD B,8 | + | ld b,8 |
drawsp16x16_stack_loop2: | drawsp16x16_stack_loop2: | ||
- | | + | |
; Y ademas no hace falta sumar 2 a DE | ; Y ademas no hace falta sumar 2 a DE | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
</ | </ | ||
- | La opción inversa sería utilizar PUSH para escribir en la VRAM (en vez de POP para leer el dato a escribir), pero tiene la pega de que sólo podríamos hacer escritura, y no por ejemplo un OR o XOR del dato en la pantalla. | + | La opción inversa sería utilizar |
+ | |||
+ | \\ | ||
+ | \\ | ||
+ | **Organización de los datos gráficos** | ||
+ | |||
+ | A la hora de renderizar gráficos, es muy importante decidir cómo queremos alojar esos gráficos en el código (y por tanto, en memoria), y pensar las rutinas de forma que sean más eficientes gracias a esa organización. | ||
+ | |||
+ | Por ejemplo, si tenemos un personaje de un juego de 24x24, estará formado por 9 bloques de 8x8: | ||
+ | |||
+ | < | ||
+ | ABC | ||
+ | DEF | ||
+ | GHI | ||
+ | </ | ||
+ | |||
+ | A la hora de almacenar los datos en memoria, tenemos que decidir cómo poner los datos de los scanlines. Podríamos guardarlos tal y como aparecen los pixeles, es decir: | ||
+ | |||
+ | < | ||
+ | Linea 0: 8_pixeles_linea_0_A | ||
+ | Linea 1: 8_pixeles_linea_1_A | ||
+ | ... | ||
+ | Linea 8: 8_pixeles_linea_0_D | ||
+ | Linea 9: 8_pixeles_linea_1_D | ||
+ | ... | ||
+ | Linea 22: 8_pixeles_linea_6_G | ||
+ | Linea 23: 8_pixeles_linea_7_G | ||
+ | </ | ||
+ | |||
+ | Y después de los datos de cada sprites, tener los datos de la máscara de dicho sprite. | ||
+ | |||
+ | Pero eso nos obligaría a tener un puntero para las máscaras. | ||
+ | |||
+ | Podría ser más interesante alojar la máscara junto al mismo sprite, estando cada byte precedido de su byte de máscara, para leerlos con un | ||
+ | |||
+ | <code z80> | ||
+ | ; SP apunta a nuestro sprite | ||
+ | ; HL apunta ya a la pantalla, lugar de dibujado | ||
+ | ld b, ANCHO | ||
+ | |||
+ | LOOP: | ||
+ | pop de ; Leemos mascara y datos graficos | ||
+ | ld a, (hl) ; Leemos dato en la memoria | ||
+ | and e | ||
+ | or d | ||
+ | ld (hl), a ; Aplicamos la mascara y guardamos en pantalla | ||
+ | inc hl ; Avanzamos puntero de dibujado en pantalla | ||
+ | djnz LOOP | ||
+ | </ | ||
+ | |||
+ | De la misma forma, tenemos que pensar cómo queremos organizar los atributos para después renderizarlos. Según cómo creemos la rutina, puede interesaros tener los datos de atributos de un sprite después de cada Sprite, o en un tabla aparte. Incluso puede darse el caso de que si los tenemos guardados en orden inverso se nos ocurra una rutina de impresión más eficiente. | ||
+ | |||
+ | Recordemos que imprimir los gráficos del juego es una de las tareas que más recursos de la CPU (tiempo de proceso) consumirá por lo que este es un caso especial en el que se recomienda optimizar al extremo. | ||
\\ | \\ |