cursos:ensamblador:gfx3_sprites_lowres

Diferencias

Muestra las diferencias entre dos versiones de la página.

Enlace a la vista de comparación

Ambos lados, revisión anteriorRevisión previa
Próxima revisión
Revisión previa
cursos:ensamblador:gfx3_sprites_lowres [20-11-2010 09:49] – [Organización de los sprites en memoria] sromerocursos: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, analizaremos una rutina más genérica que permita imprimir sprites de tamaños múltiples del anterior (16x16, 24x16, etc).  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, analizaremos una rutina más genérica que permita imprimir sprites de tamaños múltiples del anterior (16x16, 24x16, etc).
 +
 + Imprimir en posiciones exactas de carácter (de 0 a 31 para la coordenada X, y de 0 a 23 para la coordenada Y), coincidiendo con las posiciones posibles de la rejilla de 32x24 en pantalla es lo que se conoce como "gráficos en baja resolución". Los gráficos, aunque sean muy detallados, se mueven "carácter a carácter".
  
  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.
  
  Estudiaremos también los métodos de impresión sobre pantalla: transferencia directa, operaciones lógicas y uso de máscaras, y la generación de los datos gráficos y de atributos a partir de la imagen formada en un editor de sprites.  Estudiaremos también los métodos de impresión sobre pantalla: transferencia directa, operaciones lógicas y uso de máscaras, y la generación de los datos gráficos y de atributos a partir de la imagen formada en un editor de sprites.
- 
  
 \\  \\ 
Línea 47: Línea 48:
 {{ cursos:ensamblador:pacman_SimonOwen.png | Sprite set de Pacman (coloreado por Simon Owen) }} {{ cursos:ensamblador:pacman_SimonOwen.png | Sprite set de Pacman (coloreado por Simon Owen) }}
 ;#; ;#;
-//Sprite set de Pacman -(c) NAMCO- coloreado por Simon Owen\\ para la versión SAM. Gráficos en formato rectangular.\\ Las rutinas de impresión requieren\\ coordenadas (xorg,yorg).//+//Sprite set de Pacman -(c) NAMCO- coloreado por Simon Owen\\  para la versión SAM. Gráficos en formato rectangular.\\  Las rutinas de impresión requieren\\  coordenadas (xorg,yorg).//
 ;#; ;#;
 \\  \\ 
Línea 56: Línea 57:
 {{ cursos:ensamblador:gfx3_tilesetsk.png | Sprite set de Sokoban }} {{ cursos:ensamblador:gfx3_tilesetsk.png | Sprite set de Sokoban }}
 ;#; ;#;
-//Parte del sprite set de Sokoban: gráficos en formato\\ lineal vertical. Las rutinas de impresión\\ requieren un identificador de sprite (0-NumSprites).//+//Parte del sprite set de Sokoban: gráficos en formato\\  lineal vertical. Las rutinas de impresión\\  requieren un identificador de sprite (0-NumSprites).//
 ;#; ;#;
 \\  \\ 
Línea 63: Línea 64:
  
 \\  \\ 
- **Frame** (fotograma): El "sprite set" no sólo suele alojar los diferentes gráficos de cada personaje o enemigo de un juego, sino que además se suelen alojar todos los //frames// (fotogramas) de animación de cada personaje. En sistemas más modernos se suele tener un //frameset// (un array de frames) por cada personaje, y cada objeto del juego tiene asociado su frameset y su estado actual de animación y es capaz de dibujar el frame que le corresponde.\\ En el Spectrum, por limitaciones de memoria y código, lo normal es tener todo en un mismo spriteset, y tener almacenados los identificadores de animación de un personaje en lugar de su frameset. Así, sabremos que nuestro personaje andando hacia la derecha tiene una animación que consta de los frames (por ejemplo) 10, 11 y 12 dentro del spriteset.+ **Frame** (fotograma): El "sprite set" no sólo suele alojar los diferentes gráficos de cada personaje o enemigo de un juego, sino que además se suelen alojar todos los //frames// (fotogramas) de animación de cada personaje. En sistemas más modernos se suele tener un //frameset// (un array de frames) por cada personaje, y cada objeto del juego tiene asociado su frameset y su estado actual de animación y es capaz de dibujar el frame que le corresponde.\\  En el Spectrum, por limitaciones de memoria y código, lo normal es tener todo en un mismo spriteset, y tener almacenados los identificadores de animación de un personaje en lugar de su frameset. Así, sabremos que nuestro personaje andando hacia la derecha tiene una animación que consta de los frames (por ejemplo) 10, 11 y 12 dentro del spriteset.
  
 \\  \\ 
Línea 71: Línea 72:
 {{ cursos:ensamblador:gfx3_tilemap.png | Tilemaps }} {{ cursos:ensamblador:gfx3_tilemap.png | Tilemaps }}
 ;#; ;#;
-//Tilemap: componiendo un mapa en pantalla\\ a partir de tiles de un tileset/spriteset + mapa.//+//Tilemap: componiendo un mapa en pantalla\\  a partir de tiles de un tileset/spriteset + mapa.//
 ;#; ;#;
 \\  \\ 
Línea 98: Línea 99:
  
  A la hora de crear una rutina de impresión de sprites tenemos que tener en cuenta el formato del Sprite de Origen. Casi se podría decir que más bien, la rutina de impresión de sprites debemos escribirla o adaptarla al formato de sprites que vayamos a utilizar en el juego.  A la hora de crear una rutina de impresión de sprites tenemos que tener en cuenta el formato del Sprite de Origen. Casi se podría decir que más bien, la rutina de impresión de sprites debemos escribirla o adaptarla al formato de sprites que vayamos a utilizar en el juego.
- +
  Dicho formato puede ser:  Dicho formato puede ser:
  
Línea 124: 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 139: Línea 140:
  Aunque trataremos de optimizar las rutinas en la medida de lo posible, se va a intentar no realizar optimizaciones que hagan la rutina ilegible para el lector. Las rutinas genéricas que veremos hoy serán rápidas pero siempre podrán optimizarse más mediante trucos y técnicas al alcance de los programadores con más experiencia. Es labor del programador avanzado el adaptar estas rutinas a cada juego para optimizarlas al máximo en la medida de lo posible.  Aunque trataremos de optimizar las rutinas en la medida de lo posible, se va a intentar no realizar optimizaciones que hagan la rutina ilegible para el lector. Las rutinas genéricas que veremos hoy serán rápidas pero siempre podrán optimizarse más mediante trucos y técnicas al alcance de los programadores con más experiencia. Es labor del programador avanzado el adaptar estas rutinas a cada juego para optimizarlas al máximo en la medida de lo posible.
  
- 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 "lenta" comparada con tener los datos guardados en registros, cuando comenzamos a manejar muchos parámetros de entrada (y de trabajo) en una rutina y además hay que realizar cálculos con ellos, es habitual que agotemos los registros disponibles, más todavía teniendo en cuenta la necesidad de realizar dichos cálculos. En muchas ocasiones se acaba realizando uso de la pila con continuos PUSHes y POPs destinados a guardar valores y recuperarlos posteriormente a realizar los cálculos o en ciertos puntos de la rutina. + 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 "lenta" comparada con tener los datos guardados en registros, cuando comenzamos a manejar muchos parámetros de entrada (y de trabajo) en una rutina y además hay que realizar cálculos con ellos, es habitual que agotemos los registros disponibles, más todavía teniendo en cuenta la necesidad de realizar dichos cálculos. En muchas ocasiones se acaba realizando uso de la pila con continuos PUSHes y POPs destinados a guardar valores y recuperarlos posteriormente a realizar los cálculos o en ciertos puntos de la rutina.
  
- Las instrucciones **PUSH** **POP** toman 11 y 10 t-estados respectivamente, mientras que escribir o leer un valor de 8 bits en memoria (**LD (NN), A** **LD A, (NN)**) requiere 13 t-estados y escribir o leer un valor de 16 bits toma 20 t-estados (**LD (NN), rr** **LD rr, (NN)**) con la excepción de **LD (NN), HL** que cuesta 16 t-estados.+ Las instrucciones ''PUSH'' ''POP'' toman 11 y 10 t-estados respectivamente, mientras que escribir o leer un valor de 8 bits en memoria (''ld (NN), a'' ''ld a, (NN)'') requiere 13 t-estados y escribir o leer un valor de 16 bits toma 20 t-estados ''ld (NN), rr'' ''ld rr, (NN)'') con la excepción de ''ld (NN), hl'' que cuesta 16 t-estados.
  
 \\  \\ 
 +|< 50% 30% 20% >|
 ^ Instrucción ^ Tiempo en t-estados ^ ^ Instrucción ^ Tiempo en t-estados ^
-PUSH rr | 11 | +push rr | 11 | 
-PUSH IX PUSH IY | 16 | +push ix push iy | 16 | 
-POP rr | 10 | +pop rr | 10 | 
-POP IX POP IY | 14 | +pop ix pop iy | 14 | 
-LD (NN), | 13 | +ld (NN), | 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 |
 \\  \\ 
  
- Aunque es una diferencia apreciable, no siempre podemos obtener una "linealidad" de uso de la pila que requiera POP por cada PUSH, por lo que en ocasiones se hace realmente cómodo y útil el aprovechar variables de memoria para diseñar las rutinas.+ Aunque es una diferencia apreciable, no siempre podemos obtener una "linealidad" de uso de la pila que requiera un ''POP'' por cada ''PUSH'', por lo que en ocasiones se hace realmente cómodo y útil el aprovechar variables de memoria para diseñar las rutinas.
  
  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.
  
- 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.
  
- Finalmente, recordar que las rutinas que veremos en este capítulo pueden ser ubicadas en memoria y llamadas desde BASIC. Una vez ensambladas y POKEadas en memoria, podemos hacer uso de ellas utilizando POKE para establecer los parámetros de llamada y RANDOMIZE USR DIR_RUTINA para ejecutarlas. A lo largo de la vida de revistas como Microhobby se publicaron varios paquetes de rutinas de gestión de Sprites en ensamblador que utilizan este método y que estaban pensadas para ser utilizadas tanto desde código máquina como desde BASIC.+ Finalmente, recordar que las rutinas que veremos en este capítulo pueden ser ubicadas en memoria y llamadas desde BASIC. Una vez ensambladas y POKEadas en memoria, podemos hacer uso de ellas utilizando POKE para establecer los parámetros de llamada y ''RANDOMIZE USR DIR_RUTINA'' para ejecutarlas. A lo largo de la vida de revistas como Microhobby se publicaron varios paquetes de rutinas de gestión de Sprites en ensamblador que utilizan este método y que estaban pensadas para ser utilizadas tanto desde código máquina como desde BASIC.
  
  
Línea 179: Línea 181:
 \\  \\ 
  
- El **formato de organización del tileset** no debería requerir mucho tiempo de decisión: la organización del tileset en formato lineal es mucho más eficiente para las rutinas de impresión de sprites que el almacenamiento en una "imagen" rectangular. Teniendo todos los sprites (o tiles) en un único vector, podemos hacer referencia a cualquier bloque, tile, sprite o cuadro de animación mediante un identificador numérico. + El **formato de organización del tileset** no debería requerir mucho tiempo de decisión: la organización del tileset en formato lineal es mucho más eficiente para las rutinas de impresión de sprites que el almacenamiento en una "imagen" rectangular. Teniendo todos los sprites (o tiles) en un único vector, podemos hacer referencia a cualquier bloque, tile, sprite o cuadro de animación mediante un identificador numérico.
  
  De esta forma, el "bloque 0" puede ser un bloque vacío, el bloque "1" el primer fotograma de animación de nuestro personaje, etc.  De esta forma, el "bloque 0" puede ser un bloque vacío, el bloque "1" el primer fotograma de animación de nuestro personaje, etc.
Línea 228: Línea 230:
 <code> <code>
 Tabla_Sprites: Tabla_Sprites:
-  DB A1, B1, C1, D1, E1, F1, G1, H1, I1, J1, K1 +    DB A1, B1, C1, D1, E1, F1, G1, H1, I1, J1, K1 
-  DB L1, M1, N1, O1, P1, A2, B2, C2, D2, E2, F2 +    DB L1, M1, N1, O1, P1, A2, B2, C2, D2, E2, F2 
-  DB G2, H2, I2, J2, K2, L2, M2, N2, O2, P2 +    DB G2, H2, I2, J2, K2, L2, M2, N2, O2, P2 
-  +
 Tabla_Atributos: Tabla_Atributos:
-  DB S1_Attr1, S1_Attr2, S2_Attr1, S2_Attr2+    DB S1_Attr1, S1_Attr2, S2_Attr1, S2_Attr2
 </code> </code>
  
Línea 241: Línea 243:
 <code> <code>
 Tabla_Sprites: Tabla_Sprites:
-  DB A1, B1, C1, D1, E1, F1, G1, H1, I1, J1, K1 +    DB A1, B1, C1, D1, E1, F1, G1, H1, I1, J1, K1 
-  DB L1, M1, N1, O1, P1, S1_Attr1, S1_Attr2, A2 +    DB L1, M1, N1, O1, P1, S1_Attr1, S1_Attr2, A2 
-  DB B2, C2, D2, E2, F2, G2, H2, I2, J2, K2, L2 +    DB B2, C2, D2, E2, F2, G2, H2, I2, J2, K2, L2 
-  DB M2, N2, O2, P2, S2_Attr1, S2_Attr2+    DB M2, N2, O2, P2, S2_Attr1, S2_Attr2
 </code> </code>
  
- Finalmente, no debemos olvidarnos de que si utilizamos máscaras de sprite también deberemos incluirlas en nuestro "array de datos" (o sprite set). El dónde ubicar cada scanline de la máscara depende, de nuevo, de nuestra rutina de impresión. Una primera aproximación sería ubicar cada byte de máscara antes o después de cada dato del sprite, para que podamos realizar las pertinentes operaciones lógicas entre la máscara, el fondo y el sprite. + Finalmente, no debemos olvidarnos de que si utilizamos máscaras de sprite también deberemos incluirlas en nuestro "array de datos" (o sprite set). El dónde ubicar cada scanline de la máscara depende, de nuevo, de nuestra rutina de impresión. Una primera aproximación sería ubicar cada byte de máscara antes o después de cada dato del sprite, para que podamos realizar las pertinentes operaciones lógicas entre la máscara, el fondo y el sprite.
  
  Si denominamos "XX" a los datos de la máscara del sprite 1 y "YY" a los datos de máscara del sprite 2, nuestra tabla de datos en memoria quedaría de la siguiente forma:  Si denominamos "XX" a los datos de la máscara del sprite 1 y "YY" a los datos de máscara del sprite 2, nuestra tabla de datos en memoria quedaría de la siguiente forma:
Línea 254: Línea 256:
 ; Formato: Una única tabla: ; Formato: Una única tabla:
 Tabla_Sprites: Tabla_Sprites:
-  DB XX, A1, XX, B1, XX, C1, XX, D1, XX, E1, XX, F1, XX, G1 +    DB XX, A1, XX, B1, XX, C1, XX, D1, XX, E1, XX, F1, XX, G1 
-  DB XX, H1, XX, I1, XX, J1, XX, K1, XX, L1, XX, M1, XX, N1 +    DB XX, H1, XX, I1, XX, J1, XX, K1, XX, L1, XX, M1, XX, N1 
-  DB XX, O1, XX, P1, S1_Attr1, S1_Attr2 +    DB XX, O1, XX, P1, S1_Attr1, S1_Attr2 
-  DB YY, A2, YY, B2, YY, C2, YY, D2, YY, E2, YY, F2, YY, G2 +    DB YY, A2, YY, B2, YY, C2, YY, D2, YY, E2, YY, F2, YY, G2 
-  DB YY, H2, YY, I2, YY, J2, YY, K2, YY, L2, YY, M2, YY, N2 +    DB YY, H2, YY, I2, YY, J2, YY, K2, YY, L2, YY, M2, YY, N2 
-  DB YY, O2, YY, P2, S2_Attr1, S2_Attr2+    DB YY, O2, YY, P2, S2_Attr1, S2_Attr2
  
 ; Formato: Dos tablas: ; Formato: Dos tablas:
 Tabla_Sprites: Tabla_Sprites:
-  DB XX, A1, XX, B1, XX, C1, XX, D1, XX, E1, XX, F1, XX, G1 +    DB XX, A1, XX, B1, XX, C1, XX, D1, XX, E1, XX, F1, XX, G1 
-  DB XX, H1, XX, I1, XX, J1, XX, K1, XX, L1, XX, M1, XX, N1 +    DB XX, H1, XX, I1, XX, J1, XX, K1, XX, L1, XX, M1, XX, N1 
-  DB XX, O1, XX, P1, YY, A2, YY, B2, YY, C2, YY, D2, YY, E2 +    DB XX, O1, XX, P1, YY, A2, YY, B2, YY, C2, YY, D2, YY, E2 
-  DB YY, F2, YY, G2, YY, H2, YY, I2, YY, J2, YY, K2, YY, L2 +    DB YY, F2, YY, G2, YY, H2, YY, I2, YY, J2, YY, K2, YY, L2 
-  DB YY, M2, YY, N2, YY, O2, YY, P2+    DB YY, M2, YY, N2, YY, O2, YY, P2
  
 Tabla_Atributos: Tabla_Atributos:
-  DB S1_Attr1, S1_Attr2, S2_Attr1, S2_Attr2+    DB S1_Attr1, S1_Attr2, S2_Attr1, S2_Attr2
 </code> </code>
 \\  \\ 
Línea 312: Línea 314:
 Toggle Pixel/Select Zone Toggle Pixel/Select Zone
 El botón izquierdo cambia el valor de los pixels entre 0 y 1. El botón izquierdo cambia el valor de los pixels entre 0 y 1.
-El botón derecho controla la selección. Para seleccionar una zona, +El botón derecho controla la selección. Para seleccionar una zona,
 se hace click-derecho en una esquina, click-derecho en la opuesta y ya se hace click-derecho en una esquina, click-derecho en la opuesta y ya
 tenemos una porción seleccionada. La zona seleccionada será algo mas tenemos una porción seleccionada. La zona seleccionada será algo mas
Línea 320: Línea 322:
 patrón en el relleno con textura. Un tercer click-derecho quita la patrón en el relleno con textura. Un tercer click-derecho quita la
 selección. Atajo de teclado: 2 selección. Atajo de teclado: 2
- 
  
 Copy Copy
Línea 360: Línea 361:
    * **Interleave**: Permite definir la forma en que se intercalan gráficos y atributos en el sprite.    * **Interleave**: Permite definir la forma en que se intercalan gráficos y atributos en el sprite.
  
- Veamos las opciones que debemos especificar de forma predeterminada para exportar los datos de nuestro set de sprites en el formato adecuado para las rutinas utilizadas en este capítulo: + Veamos las opciones que debemos especificar de forma predeterminada para exportar los datos de nuestro set de sprites en el formato adecuado para las rutinas utilizadas en este capítulo:
  
 \\  \\ 
Línea 367: Línea 368:
      * Data Outputted: Gfx      * Data Outputted: Gfx
      * Mask: No      * Mask: No
-   * Múltiples sprites en formato vertical sin máscara y con atributos en 1 array: +   * Múltiples sprites en formato vertical sin máscara y con atributos en 1 array:
      * Sort Priorities: X char, Char line, Y char      * Sort Priorities: X char, Char line, Y char
      * Data Outputted: Gfx+Attr      * Data Outputted: Gfx+Attr
      * Mask: No      * Mask: No
-   * Múltiples sprites en formato vertical sin máscara y con atributos en 2 arrays: +   * Múltiples sprites en formato vertical sin máscara y con atributos en 2 arrays:
      * Sort Priorities: X char, Char line, Y char      * Sort Priorities: X char, Char line, Y char
      * Data Outputted: Primero exportamos Gfx y luego Attr      * Data Outputted: Primero exportamos Gfx y luego Attr
      * Mask: No      * Mask: No
-   * Múltiples sprites en formato vertical con máscara intercalada y con atributos: +   * Múltiples sprites en formato vertical con máscara intercalada y con atributos:
      * Sort Priorities: Mask, X char, Char line, Y char      * Sort Priorities: Mask, X char, Char line, Y char
      * Data Outputted: Gfx+Attr      * Data Outputted: Gfx+Attr
      * Mask: Yes, before graphic      * Mask: Yes, before graphic
-   * Múltiples sprites en formato vertical con máscara intercalada y con atributos en 2 arrays: +   * Múltiples sprites en formato vertical con máscara intercalada y con atributos en 2 arrays:
      * Sort Priorities: Mask, X char, Char line, Y char      * Sort Priorities: Mask, X char, Char line, Y char
      * Data Outputted: Primero exportamos Gfx y luego Attr      * Data Outputted: Primero exportamos Gfx y luego Attr
Línea 529: Línea 530:
 \\  \\ 
  
- Como puede verse, SevenuP nos permite realizar la exportación tal y como la necesitemos en + Como puede verse, SevenuP nos permite realizar la exportación tal y como la necesitemos en
 nuestras rutinas de impresión. Nosotros utilizaremos principalmente los 3 formatos que acabamos de ver, pero otras rutinas pueden necesitar de otra organización diferente, la cual podemos lograr alterando estos parámetros. nuestras rutinas de impresión. Nosotros utilizaremos principalmente los 3 formatos que acabamos de ver, pero otras rutinas pueden necesitar de otra organización diferente, la cual podemos lograr alterando estos parámetros.
  
Línea 537: Línea 538:
  
  Después, utilizaremos la opción de importación de SevenuP (//File -> Import//) para convertir la imagen de color "real" a un mapa de bits ya editable en SevenuP. Con la imagen importada en SevenuP, y tras realizar los retoques que consideremos oportunos, realizamos una exportación a código ASM con los parámetros de configuración de exportación correctos para nuestra rutina de impresión de Sprites.  Después, utilizaremos la opción de importación de SevenuP (//File -> Import//) para convertir la imagen de color "real" a un mapa de bits ya editable en SevenuP. Con la imagen importada en SevenuP, y tras realizar los retoques que consideremos oportunos, realizamos una exportación a código ASM con los parámetros de configuración de exportación correctos para nuestra rutina de impresión de Sprites.
 +
 +
 +\\ 
 +\\ 
 +**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 "agregado manualmente" directamente en el código de nuestro programa, o lo podemos incluir con ''INCLUDE grafico.asm'', lo cual incluiría también las etiquetas que van dentro del fichero ASM:
 +
 +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 ''INCBIN grafico.bin'', de la siguiente forma:
 +
 +<code z80>
 +bicho_gfx:
 +    INCBIN "gfx_bicho.bin"
 +
 +bicho_attr:
 +    INCBIN "attr_bicho.bin"
 +</code>
 +
 +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, las siguientes posibilidades:  En este sentido, podemos utilizar para el paso de los parámetros, las siguientes posibilidades:
  
 \\  \\ 
-   * 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. + 
-   * El Stack del Calculador: Este método permite que programa BASIC puedan llamar a nuestras subrutinas en ensamblador mediante //DEF FN//. Su principal desventaja es la lentitud y la no portabilidad a otros Sistemas. +   * **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. 
-   * 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 "lentitud" del acceso a memoria para lectura y escritura, pero también tiene sustanciales ventajas:+ 
 +   * **El Stack del Calculador**: Este método permite que programa BASIC puedan llamar a nuestras subrutinas en ensamblador mediante ''DEF FN''. Su principal desventaja es la lentitud y la no portabilidad a otros Sistemas. 
 + 
 +   * **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 "lentitud" del acceso a memoria para lectura y escritura, pero también tiene sustanciales ventajas:
      * 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, 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 ''RANDOMIZE USR'' direccion_rutina.
 \\  \\ 
  
- Lo normal en rutinas de este tipo sería utilizar en la medida de lo posible el paso mediante registros (programa en ASM puro) o mediante la pila (programas en C y ASM), pero en nuestros ejemplos utilizaremos el último método: paso de variables en direcciones de memoria, con el objetivo de que las rutinas puedan llamarse desde BASIC y para que sean lo suficientemente sencillas de leer para que cada programador pueda adaptar la entrada de una rutina concreta a las necesidades de su programa utilizando otro de los métodos de paso de parámetros descrito. + Lo normal en rutinas de este tipo sería utilizar en la medida de lo posible el paso mediante registros (programa en ASM puro) o mediante la pila (programas en C y ASM), pero en nuestros ejemplos utilizaremos el último método: paso de variables en direcciones de memoria, con el objetivo de que las rutinas puedan llamarse desde BASIC y para que sean lo suficientemente sencillas de leer para que cada programador pueda adaptar la entrada de una rutina concreta a las necesidades de su programa utilizando otro de los métodos de paso de parámetros descrito.
  
  Es habitual incluso que en los programas ni siquiera se le pase a las rutinas las direcciones origen de Sprites y Atributos sino que dichas direcciones estén "hardcodeadas" dentro del código de la rutina, utilizando un único array de tiles y otro de atributos con unas direcciones concretas y exactas. Este podría ser un paso básico de optimización que evitaría carga y paso de parámetros y nos permitiría recuperar direcciones de origen de los arrays en cualquier punto de la rutina.  Es habitual incluso que en los programas ni siquiera se le pase a las rutinas las direcciones origen de Sprites y Atributos sino que dichas direcciones estén "hardcodeadas" dentro del código de la rutina, utilizando un único array de tiles y otro de atributos con unas direcciones concretas y exactas. Este podría ser un paso básico de optimización que evitaría carga y paso de parámetros y nos permitiría recuperar direcciones de origen de los arrays en cualquier punto de la rutina.
  
  Por otra parte, el paso de parámetros es sólo un pequeño porcentaje del tiempo total de la rutina: el interés de optimización residirá en la impresión del sprite en sí, ya que esta recogida de parámetros se realiza una sola vez por sprite independientemente del número de bloques que lo compongan (en cuyo dibujado será donde realicemos el mayor gasto de tiempo).  Por otra parte, el paso de parámetros es sólo un pequeño porcentaje del tiempo total de la rutina: el interés de optimización residirá en la impresión del sprite en sí, ya que esta recogida de parámetros se realiza una sola vez por sprite independientemente del número de bloques que lo compongan (en cuyo dibujado será donde realicemos el mayor gasto de tiempo).
 +
  
 \\  \\ 
Línea 596: Línea 626:
 ;Sort Priorities: Char line ;Sort Priorities: Char line
  
-cara_gfx:   +cara_gfx: 
-  DEFB  28, 62,107,127, 93, 99, 62, 28+    DEFB  28, 62,107,127, 93, 99, 62, 28
  
 cara_attrib: cara_attrib:
-  DEFB  56+    DEFB  56
 </code> </code>
  
Línea 612: Línea 642:
  
 \\  \\ 
-Dirección ^ Parámetro ^ +|< 50% >| 
-50000 | Dirección de la tabla de Sprites | +Variable ^ Tamaño (Bytes) ^ Parámetro ^ 
-50002 | Dirección de la tabla de Atributos | +DS_SPRITES | 2 | Dirección de la tabla de Sprites | 
-50004 | Coordenada X en baja resolución | +DS_ATTRIBS | 2 | Dirección de la tabla de Atributos | 
-50005 | Coordenada Y en baja resolución | +DS_COORD_X | 1 | Coordenada X en baja resolución | 
-50006 | Numero de sprite a dibujar (0-N) |+DS_COORD_Y | 1 | Coordenada Y en baja resolución | 
 +DS_NUMSPR | 1 | Numero de sprite a dibujar (0-N) |
 \\  \\ 
  
Línea 633: 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 639: Línea 670:
 </code> </code>
  
- La rutina debe de comenzar calculando la direcciones origen y destino para las transferencias de datos. + La rutina debe de comenzar calculando la direcciones origen y destino para las transferencias de datos.
  
  La dirección origen es el primer scanline del sprite a dibujar. Dada una tabla de sprites 8x8 en formato vertical, y teniendo en cuenta que cada sprite 8x8 ocupa 8 bytes (1 byte por cada scanline, 8 scanlines), nos posicionaremos en el sprite correcto "saltando" 8 bytes por cada sprite que haya antes del sprite que buscamos:  La dirección origen es el primer scanline del sprite a dibujar. Dada una tabla de sprites 8x8 en formato vertical, y teniendo en cuenta que cada sprite 8x8 ocupa 8 bytes (1 byte por cada scanline, 8 scanlines), nos posicionaremos en el sprite correcto "saltando" 8 bytes por cada sprite que haya antes del sprite que buscamos:
Línea 659: 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, ahorrando ciclos de reloj al no realizar esta tarea sin efectos en pantalla. Nótese que aunque no dibujemos los atributos de un sprite, esto no quiere decir que no tendrá color en pantalla: si no modificamos los atributos de una posición (c,f), el sprite que dibujemos en esas coordenadas adoptará los colores de tinta y papel que ya tenía esa posición de pantalla.  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, ahorrando ciclos de reloj al no realizar esta tarea sin efectos en pantalla. Nótese que aunque no dibujemos los atributos de un sprite, esto no quiere decir que no tendrá color en pantalla: si no modificamos los atributos de una posición (c,f), el sprite que dibujemos en esas coordenadas adoptará los colores de tinta y papel que ya tenía esa posición de pantalla.
  
- 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).
  
- Utilizaremos este sistema de comprobación (DIR_ATTR==0) para evitar la necesidad de crear 2 rutinas diferentes, aunque en un juego lo normal será que adaptemos la rutina al caso concreto y exacto (SIN o CON atributos) para evitar la comprobación de DIR_ATTR=0 y cualquier otro código no necesario. + Utilizaremos este sistema de comprobación (DIR_ATTR==0) para evitar la necesidad de crear 2 rutinas diferentes, aunque en un juego lo normal será que adaptemos la rutina al caso concreto y exacto (SIN o CON atributos) para evitar la comprobación de DIR_ATTR=0 y cualquier otro código no necesario.
  
- Continuemos: Si DIR_ATRIBUTOS (50002) no es cero, se utilizará dicha dirección y el número del sprite para calcular la posición del único atributo que tenemos que trazar en pantalla (sprite 8x8 = 1 único atributo). Como cada sprite tiene un atributo de 1 byte, esta dirección origen será:+ Continuemos: Si DIR_ATRIBUTOS no es cero, se utilizará dicha dirección y el número del sprite para calcular la posición del único atributo que tenemos que trazar en pantalla (sprite 8x8 = 1 único atributo). Como cada sprite tiene un atributo de 1 byte, esta dirección origen será:
  
 <code> <code>
Línea 683: Línea 714:
 ; ;
 ; Entrada (paso por parametros en memoria): ; Entrada (paso por parametros en memoria):
-; Direccion   Parametro +; Direccion        Parametro 
-50000       Direccion de la tabla de Sprites +(DS_SPRITES)      Direccion de la tabla de Sprites 
-50002       Direccion de la tabla de Atribs  (0=no atributos) +(DS_ATTRIBS)      Direccion de la tabla de Atribs  (0=no atributos) 
-50004       Coordenada X en baja resolucion +(DS_COORD_X)      Coordenada X en baja resolucion 
-50005       Coordenada Y en baja resolucion +(DS_COORD_Y)      Coordenada Y en baja resolucion 
-50006       Numero de sprite a dibujar (0-N) +(DS_NUMSPR)       Numero de sprite a dibujar (0-N)
 ;------------------------------------------------------------- ;-------------------------------------------------------------
 DrawSprite_8x8_LD: DrawSprite_8x8_LD:
  
-   ; 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 
-   LD BC, (DS_COORD_X)+    ld bc, (DS_COORD_X)
  
-   ;;; Calculamos las coordenadas destino de pantalla en DE: +    ;;; Calculamos las coordenadas destino de pantalla en DE: 
-   LD AB +    ld ab 
-   AND $18 +    and $18 
-   ADD A, $40 +    add a, $40 
-   LD DA +    ld da           ; Ya tenemos la parte alta calculada (010TT000) 
-   LD AB +    ld ab           ; Ahora calculamos la parte baja 
-   AND +    and 
-   RRCA +    rrca 
-   RRCA +    rrca 
-   RRCA +    rrca              ; = NNN00000b 
-   ADD A, C +    add ac          ; Sumamos COLUMNA -> = NNNCCCCCb 
-   LD E, A           ; DE contiene ahora la direccion destino.+    ld e, a           ; Lo cargamos en la parte baja de la direccion 
 +                      ; DE contiene ahora la direccion destino.
  
-   PUSH DE           Lo guardamos para luego, lo usaremos para +    ;;; Calcular posicion origen (array sprites) en HL como: 
-                     calcular la direccion del atributo+    ;;    direccion = base_sprites + (NUM_SPRITE*8)
  
-   ;;; Calcular posicion origen (array sprites) en HL como: +    ld bc, (DS_SPRITES) 
-   ;;;     direccion = base_sprites + (NUM_SPRITE*8) +    ld a, (DS_NUMSPR) 
-   +    ld h, 0 
-   LD BC, (DS_SPRITES) +    ld l          ; HL = DS_NUMSPR 
-   LD A, (DS_NUMSPR) +    add hlhl        ; HL = HL * 2 
-   LD H, 0 +    add hlhl        ; HL = HL * 4 
-   LD L          ; HL = DS_NUMSPR +    add hlhl        ; HL = HL * 8 = DS_NUMSPR * 8 
-   ADD HLHL        ; HL = HL * 2 +    add hlbc        ; HL = BC + HL = DS_SPRITES + (DS_NUMSPR * 8) 
-   ADD HLHL        ; HL = HL * 4 +                      ; HL contiene la direccion de inicio en el sprite
-   ADD HLHL        ; HL = HL * 8 = DS_NUMSPR * 8 +
-   ADD HLBC        ; HL = BC + HL = DS_SPRITES + (DS_NUMSPR * 8) +
-                     ; HL contiene la direccion de inicio en el sprite+
  
-   EX DEHL         ; Intercambiamos DE y HL (DE=origen, HL=destino)+    ex dehl         ; Intercambiamos DE y HL (DE=origen, HL=destino)
  
-   ;;; 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) 
 + 
 +    ld b, 8          ; 8 scanlines -> 8 iteraciones
  
-   LD B, 8          ; 8 scanlines -> 8 iteraciones 
-  
 drawsp8x8_loopLD: drawsp8x8_loopLD:
-   LD A, (DE)       ; Tomamos el dato del sprite +    ld a, (de)       ; Tomamos el dato del sprite 
-   LD (HL),       ; Establecemos el valor en videomemoria +    ld (hl),       ; Establecemos el valor en videomemoria 
-   INC DE           ; Incrementamos puntero en sprite +    inc de           ; Incrementamos puntero en sprite 
-   INC H            ; Incrementamos puntero en pantalla (scanline+=1) +    inc h            ; Incrementamos puntero en pantalla (scanline+=1) 
-   DJNZ drawsp8x8_loopLD +    djnz drawsp8x8_loopLD
- +
-   ;;; En este punto, los 8 scanlines del sprite estan dibujados.+
  
-   POP BC             ; Recuperamos el offset del primer scanline+    ;;; En este punto, los 8 scanlines del sprite estan dibujados. 
 +    ld a, h 
 +    sub 8              ; Recuperamos la posicion de memoria del 
 +    ld b, a            ; scanline inicial donde empezamos a dibujar 
 +    ld c, l            ; BC = HL - 8
  
-   ;;; Considerar el dibujado de los atributos (Si DS_ATTRIBS=0 -> RET+    ;;; Considerar el dibujado de los atributos (Si DS_ATTRIBS=0 -> ret
-   LD HL, (DS_ATTRIBS)+    ld hl, (DS_ATTRIBS)
  
-   XOR A              ; A = 0 +    xor a              ; A = 0 
-   ADD A          ; A = 0 + H = H +    add a          ; A = 0 + H = H 
-   RET Z              ; Si H = 0, volver (no dibujar atributos)+    ret z              ; Si H = 0, volver (no dibujar atributos)
  
-   ;;; Calcular posicion destino en area de atributos en DE. +    ;;; Calcular posicion destino en area de atributos en DE. 
-   LD A           ; Codigo de Get_Attr_Offset_From_Image +    ld a           ; Codigo de Get_Attr_Offset_From_Image 
-   RRCA               ; Obtenemos dir de atributo a partir de +    rrca               ; Obtenemos dir de atributo a partir de 
-   RRCA               ; dir de zona de imagen. +    rrca               ; dir de zona de imagen. 
-   RRCA               ; Nos evita volver a obtener X e Y +    rrca               ; Nos evita volver a obtener X e Y 
-   AND 3              ; y hacer el calculo completo de la  +    and 3              ; y hacer el calculo completo de la 
-   OR $58             ; direccion en zona de atributos +    or $58             ; direccion en zona de atributos 
-   LD DA +    ld da 
-   LD E           ; DE tiene el offset del attr de HL+    ld e           ; DE tiene el offset del attr de HL
  
-   LD A, (DS_NUMSPR)  ; Cogemos el numero de sprite a dibujar +    ld a, (DS_NUMSPR)  ; Cogemos el numero de sprite a dibujar 
-   LD CA +    ld ca 
-   LD B, 0 +    ld b, 0 
-   ADD HLBC         ; HL = HL+DS_NUMSPR = Origen de atributo+    add hlbc         ; HL = HL+DS_NUMSPR = Origen de atributo
  
-   ;;; Copiar (HL) en (DE) -> Copiar atributo de sprite a pantalla +    ;;; Copiar (HL) en (DE) -> Copiar atributo de sprite a pantalla 
-   LD A, (HL+    ld a, (hl
-   LD (DE),         ; Mas rapido que LDI (7+7 vs 16 t-estados) +    ld (de),         ; Mas rapido que ldi (7+7 vs 16 t-estados) 
-   RET                ; porque no necesitamos incrementar HL y DE +    ret                ; porque no necesitamos incrementar HL y DE
 </code> </code>
  
  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 //Get_Char_Offset_LR// //Attr_Offset_From_Image// con el código de las mismas embebido dentro de la rutina principal. Esto se hace con el objetivo de evitar los correspondientes CALLs RET y para poder personalizarlas (en este caso, están modificadas para devolver la dirección calculada en DE en lugar de en HL, por requerimientos del código de DrawSprite_8x8).+  * Nótese que en la rutina se emplean las subrutinas ''Get_Char_Offset_LR'' ''Attr_Offset_From_Image'' con el código de las mismas embebido dentro de la rutina principal. Esto se hace con el objetivo de evitar los correspondientes calls ret y para poder personalizarlas (en este caso, están modificadas para devolver la dirección calculada en DE en lugar de en HL, por requerimientos del código de ''DrawSprite_8x8'').
  
-  * 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 **LDI**. Más adelante veremos el por qué de esta elecció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 ''LDI''. Más adelante veremos el por qué de esta elección.
  
-  * Como ya vimos en el capítulo anterior, para avanzar o retroceder el puntero HL en pantalla, en lugar de utilizar **DEC HL** **INC HL** (6 t-estados), realizamos un **DEC L** **INC L** (4 t-estados). Esto es posible porque dentro de un mismo scanline de pantalla no varía el valor del byte alto de la dirección. Esta pequeña optimización no podemos realizarla con el puntero de datos del Sprite porque no tenemos la certeza de que esté dentro de una página de 256 bytes y que, por lo tanto, alguno de los incrementos del puntero deba modificar la parte alta del mismo.+  * Como ya vimos en el capítulo anterior, para avanzar o retroceder el puntero HL en pantalla, en lugar de utilizar ''dec hl'' ''inc hl'' (6 t-estados), realizamos un ''dec l'' ''inc l'' (4 t-estados). Esto es posible porque dentro de un mismo scanline de pantalla no varía el valor del byte alto de la dirección. Esta pequeña optimización no podemos realizarla con el puntero de datos del Sprite porque no tenemos la certeza de que esté dentro de una página de 256 bytes y que, por lo tanto, alguno de los incrementos del puntero deba modificar la parte alta del mismo.
  
-  * 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 bucle**.+  * 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 bucle''.
  
  En el caso de nuestra rutina de ejemplo, cambiaríamos...  En el caso de nuestra rutina de ejemplo, cambiaríamos...
  
 <code z80> <code z80>
-   LD B, 8          ; 8 scanlines +    ld b, 8          ; 8 scanlines 
-   +
 drawsp8x8_loopLD: drawsp8x8_loopLD:
-   LD A, (DE)       ; Tomamos el dato del sprite +    ld a, (de)       ; Tomamos el dato del sprite 
-   LD (HL),       ; Establecemos el valor en videomemoria +    ld (hl),       ; Establecemos el valor en videomemoria 
-   INC DE           ; Incrementamos puntero en sprite +    inc de           ; Incrementamos puntero en sprite 
-   INC H            ; Incrementamos puntero en pantalla (scanline+=1) +    inc h            ; Incrementamos puntero en pantalla (scanline+=1) 
-   DJNZ drawsp8x8_loopLD+    djnz drawsp8x8_loopLD
 </code> </code>
  
Línea 796: Línea 827:
  
 <code z80> <code z80>
-   LD A, (DE)       ; Scanline 0 +    ld a, (de)       ; Scanline 0 
-   LD (HL), A +    ld (hl), a 
-   INC DE +    inc de 
-   INC H+    inc h
  
-   LD A, (DE)       ; Scanline 1 +    ld a, (de)       ; Scanline 1 
-   LD (HL), A +    ld (hl), a 
-   INC DE +    inc de 
-   INC H+    inc h
  
-   LD A, (DE)       ; Scanline 2 +    ld a, (de)       ; Scanline 2 
-   LD (HL), A +    ld (hl), a 
-   INC DE +    inc de 
-   INC H +    inc h
-    +
-   LD A, (DE)       ; Scanline 3 +
-   LD (HL), A +
-   INC DE +
-   INC H+
  
-   LD A, (DE)       ; Scanline 4 +    ld a, (de)       ; Scanline 3 
-   LD (HL), A +    ld (hl), a 
-   INC DE +    inc de 
-   INC H+    inc h
  
-   LD A, (DE)       ; Scanline 5 +    ld a, (de)       ; Scanline 4 
-   LD (HL), A +    ld (hl), a 
-   INC DE +    inc de 
-   INC H+    inc h
  
-   LD A, (DE)       ; Scanline 6 +    ld a, (de)       ; Scanline 5 
-   LD (HL), A +    ld (hl), a 
-   INC DE +    inc de 
-   INC H+    inc h
  
-   LD A, (DE)       ; Scanline 7 +    ld a, (de)       ; Scanline 6 
-   LD (HL), A +    ld (hl), a 
-   INC DE +    inc de 
-   INC H+    inc h 
 + 
 +    ld a, (de)       ; Scanline 7 
 +    ld (hl), a 
 +    inc de 
 +    ;;;inc h         ; no es necesario el ultimo inc h
 </code> </code>
 +
 + Nótese cómo al desenrollar el bucle ya no es necesario el último ''inc h'' para avanzar al siguiente scanline de pantalla. El ''inc de'' sí que es necesario ya que tenemos que avanzar en el sprite al primero de los atributos (aunque este INC también podría realizarse después del código de comprobación de la dirección de atributo, evitando hacerlo si no queremos imprimirlos).
 +
 + 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:
 +
 +<code z80>
 +    ;;; En este punto, los 8 scanlines del sprite estan dibujados.
 +    ld a, h
 +    sub 7              ; Recuperamos la posicion de memoria del
 +    ld b, a            ; scanline inicial donde empezamos a dibujar
 +    ld c, l            ; BC = HL - 7
 +</code>
 +
 + Recordemos que mediante las directivas ''REPT''/''ENDM'' de pasmo y ''REPT''/''ENDR'' de sjasmplus, podríamos escribir el bloque de 8 repeticiones con:
 + 
 +<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
 +</code>
 +
 + O, si queremos ahorrarnos el ''inc h'' final, podemos usar un ''REPT 7'' y volver a escribir las 4 instrucciones sin el ''inc h''. Este código es mucho más legible, hace crecer menos los ficheros, pero por las diferencias de directivas entre ensambladores nos ata a uno o a otro.
  
  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.
  
- Una rutina de impresión de sprites de tamaños fijos (8x8, 16x16, 8x16, etc) en la que conocemos el número de iteraciones verticales y horizontales para la impresión es uno de los casos típicos en los que usaremos esta técnica. Si la rutina fuera para sprites de tamaño variable (NxM), no podríamos aplicarla porque necesitamos los bucles de N y M iteraciones que no podemos sustituir de antemano.  + Una rutina de impresión de sprites de tamaños fijos (8x8, 16x16, 8x16, etc) en la que conocemos el número de iteraciones verticales y horizontales para la impresión es uno de los casos típicos en los que usaremos esta técnica. Si la rutina fuera para sprites de tamaño variable (NxM), no podríamos aplicarla porque necesitamos los bucles de N y M iteraciones que no podemos sustituir de antemano.
  
  El programa de ejemplo que veremos a continuación utiliza la rutina anterior (no incluída en el listado) para imprimir el sprite de ejemplo 8x8 que hemos visto:  El programa de ejemplo que veremos a continuación utiliza la rutina anterior (no incluída en el listado) para imprimir el sprite de ejemplo 8x8 que hemos visto:
  
 <code z80> <code z80>
-  ; Ejemplo impresion sprites 8x8 +; Ejemplo impresion sprites 8x8 
-  ORG 32768+    ORG 35000
  
-DS_SPRITES  EQU  50000 +    call ClearScreen_Pattern
-DS_ATTRIBS  EQU  50002 +
-DS_COORD_X  EQU  50004 +
-DS_COORD_Y  EQU  50005 +
-DS_NUMSPR   EQU  50006+
  
-  CALL ClearScreen_Pattern+    ; Establecemos los parametros de entrada a la rutina 
 +    ; Los 2 primeros se pueden establecer una unica vez 
 +    ld hl, cara_gfx 
 +    ld (DS_SPRITES), hl 
 +    ld hl, cara_attrib 
 +    ld (DS_ATTRIBS), hl 
 +    ld a, 15 
 +    ld (DS_COORD_X),
 +    ld a, 8 
 +    ld (DS_COORD_Y),
 +    xor a 
 +    ld (DS_NUMSPR), a
  
-  ; Establecemos los parametros de entrada a la rutina +    call DrawSprite_8x8_LD
-  ; Los 2 primeros se pueden establecer una unica vez +
-  LD HL, cara_gfx +
-  LD (DS_SPRITES), HL +
-  LD HL, cara_attrib +
-  LD (DS_ATTRIBS), HL +
-  LD A, 15 +
-  LD (DS_COORD_X),+
-  LD A, 8 +
-  LD (DS_COORD_Y),+
-  XOR A +
-  LD (DS_NUMSPR),+
- +
-  CALL DrawSprite_8x8_LD+
  
 loop: loop:
-  JR loop        +    jr loop 
-  RET+    ret 
 + 
 +; Variables que usaremos como parámetros 
 +DS_SPRITES  DEFW   0 
 +DS_ATTRIBS  DEFW   0 
 +DS_COORD_X  DEFB   0 
 +DS_COORD_Y  DEFB   0 
 +DS_NUMSPR   DEFB   0
  
 ;-------------------------------------------------------------------- ;--------------------------------------------------------------------
Línea 879: Línea 936:
 ;-------------------------------------------------------------------- ;--------------------------------------------------------------------
 ClearScreen_Pattern: ClearScreen_Pattern:
-   LD B, 191                   ; Numero de lineas a rellenar+    ld b, 191                   ; Numero de lineas a rellenar
  
 cs_line_loop: cs_line_loop:
-   LD C, 0 +    ld c, 0 
-   LD AB +    ld ab 
-   LD BA +    ld ba 
-   CALL $22B1                  ; ROM (Pixel-Address)+    call $22b1                  ; ROM (Pixel-Address)
  
-   LD AB +    ld ab 
-   AND +    and 
-   JR Z, cs_es_par +    jr z, cs_es_par 
-   LD A, 170 +    ld a, 170 
-   JR cs_pintar+    jr cs_pintar
  
 cs_es_par: cs_es_par:
-   LD A, 85+    ld a, 85
  
 cs_pintar: cs_pintar:
-   LD D                    ; Salvar el contador del bucle +    ld d                    ; Salvar el contador del bucle 
-   LD B, 32                    ; Imprimir 32 bytes+    ld b, 32                    ; Imprimir 32 bytes
  
 cs_x_loop: cs_x_loop:
-   LD (HL), A +    ld (hl), a 
-   INC HL +    inc hl 
-   DJNZ cs_x_loop +    djnz cs_x_loop
- +
-   LD B, D                     ; Recuperamos el contador externo +
-   DJNZ cs_line_loop           ; Repetimos 192 veces +
-   RET+
  
 +    ld b, d                     ; Recuperamos el contador externo
 +    djnz cs_line_loop           ; Repetimos 192 veces
 +    ret
  
 ;-------------------------------------------------------------------- ;--------------------------------------------------------------------
Línea 918: Línea 974:
  
 cara_gfx: cara_gfx:
-  DEFB  28, 62,107,127, 93, 99, 62, 28+    DEFB  28, 62,107,127, 93, 99, 62, 28
  
 cara_attrib: cara_attrib:
-  DEFB  56 +    DEFB  56
  
 ;-------------------------------------------------------------------- ;--------------------------------------------------------------------
 DrawSprite_8x8_LD: DrawSprite_8x8_LD:
-  ;;; codigo de la rutina...+    ;;; codigo de la rutina... 
 + 
 +    END 35000
 </code> </code>
  
Línea 932: Línea 989:
  
 \\  \\ 
-{{ :cursos:ensamblador:gfx3_sprite8x8_1.png | Salida del ejemplo anterior }}+{{ :cursos:ensamblador:gfx3_sprite8x8_1.png?640 | Salida del ejemplo anterior }}
 \\  \\ 
 +
 + Si quisiéramos llamar a esta rutina desde BASIC, en lugar de utilizar variables dentro de ensamblador, podríamos utilizar celdas de memoria para POKEar en ellas los parámetros de las funciones antes de hacer el USR y llamarlas. Para eso tendríamos que eliminar las variables "en memoria" del código (embebidas dentro del listado) y usar direcciones absolutas de memoria:
 +
 +<code z80>
 +DS_SPRITES  EQU 65000
 +DS_ATTRIBS  EQU 65002
 +DS_COORD_X  EQU 65003
 +DS_COORD_Y  EQU 65004
 +DS_NUMSPR   EQU 65005
 +</code>
 +
 +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.
 +
 +En nuestro caso, como el esqueleto del programa está en ensamblador, usaremos variables y de esta forma no necesitaremos mantener un listado de diferentes direcciones de memoria y su finalidad.
  
 \\  \\ 
-**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 ''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.
  
  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 947: Línea 1018:
 <code z80> <code z80>
 drawsp8x8_loop: drawsp8x8_loop:
-   LD A, (DE)         ; A = (DE) = leer dato del sprite +    ld a, (de)         ; A = (DE) = leer dato del sprite 
-   LD (HL),         ; (HL) = A = escribir dato a la pantalla +    ld (hl),         ; (HL) = A = escribir dato a la pantalla 
-   INC DE             ; Incrementamos DE (puntero sprite) +    inc de             ; Incrementamos DE (puntero sprite) 
-   INC H              ; Incrementamos scanline HL (HL+=256) +    inc h              ; Incrementamos scanline HL (HL+=256) 
-   DJNZ drawsp8x8_loop+    djnz drawsp8x8_loop
 </code> </code>
  
- 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 ''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.
  
  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 960: Línea 1031:
 <code z80> <code z80>
 drawsp8x8_loop: drawsp8x8_loop:
-   LDI                ; Copia (HL) en (DE) y HL++ DE++ +    ldi                ; Copia (HL) en (DE) y HL++ DE++ 
-   INC D              ; Sumamos 256 (+1=257) +    inc d              ; Sumamos 256 (+1=257) 
-   DEC E              ; Restamos 1 (+=256) +    dec e              ; Restamos 1 (+=256) 
-   DJNZ drawsp8x8_loop+    djnz drawsp8x8_loop
 </code> </code>
  
  Aunque es un formato más compacto, el coste de ejecución es el mismo (16+4+4 = 24 t-estados).  Aunque es un formato más compacto, el coste de ejecución es el mismo (16+4+4 = 24 t-estados).
  
- Entre las 2 posibles formas de realizar la impresión (LD+INC vs LDI), utilizaremos la primera porque, como veremos a continuación, la impresión de sprites mediante operaciones lógicas o mediante máscaras no permite el uso de LDI y utilizar la primera técnica hace todas las rutinas muy similares entre sí y por lo tanto podremos aplicar en todas cualquier mejora u optimización de una forma más rápida y sencilla.+ Entre las 2 posibles formas de realizar la impresión (''LD''+''INC'' vs ''LDI''), utilizaremos la primera porque, como veremos a continuación, la impresión de sprites mediante operaciones lógicas o mediante máscaras no permite el uso de ldi y utilizar la primera técnica hace todas las rutinas muy similares entre sí y por lo tanto podremos aplicar en todas cualquier mejora u optimización de una forma más rápida y sencilla
 + 
 + Además, ''LDI'' decrementa el registro BC tras las transferencia, por lo que si lo utilizamos en un bucle tenemos que tener en cuenta que cada ''LDI'' puede alterar el valor de BC y por tanto del contador de iteraciones del bucle, lo cual es otro motivo para elegir ''LD''+''INC'' vs ''LDI''.
  
  
Línea 980: Línea 1053:
 \\  \\ 
  
- Nótese cómo la impresión del sprite no ha respetado el fondo en los píxeles a cero del mismo: al establecer el valor del byte en pantalla con un LD, hemos establecido a cero en pantalla los bits que estaban a cero en el sprite sin respetar el valor que hubiera en videoram para dichos bits.+ Nótese cómo la impresión del sprite no ha respetado el fondo en los píxeles a cero del mismo: al establecer el valor del byte en pantalla con un ''LD'', hemos establecido a cero en pantalla los bits que estaban a cero en el sprite sin respetar el valor que hubiera en videoram para dichos bits.
  
 <code> <code>
Línea 987: Línea 1060:
 Valor en Videomemoria (HL):     10101010 Valor en Videomemoria (HL):     10101010
 Valor en el sprite - reg. A:    00111110 Valor en el sprite - reg. A:    00111110
-Operación:                      LD (HL), A+Operación:                      ld (hl), a
 Resultado en VRAM:              00111110 Resultado en VRAM:              00111110
 </code> </code>
  
- 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 ''OR'' entre el scanline y el valor actual en memoria. Con la operación OR mezclaremos los bits de ambos elementos:
  
 <code> <code>
Línea 1002: Línea 1075:
 </code> </code>
  
- 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 ''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).
  
- 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 ''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:
  
  Cambiamos el bucle de impresión...  Cambiamos el bucle de impresión...
  
 <code z80> <code z80>
-   LD B, 8             ; 8 scanlines -> 8 iteraciones+    ld b, 8          ; 8 scanlines -> 8 iteraciones
  
 drawsp8x8_loopLD: drawsp8x8_loopLD:
-   LD A, (DE)       ; Tomamos el dato del sprite +    ld a, (de)       ; Tomamos el dato del sprite 
-   LD (HL),       ; Establecemos el valor en videomemoria +    ld (hl),       ; Establecemos el valor en videomemoria 
-   INC DE           ; Incrementamos puntero en sprite +    inc de           ; Incrementamos puntero en sprite 
-   INC H            ; Incrementamos puntero en pantalla +    inc h            ; Incrementamos puntero en pantalla 
-   DJNZ drawsp8x8_loopLD+    djnz drawsp8x8_loopLD
 </code> </code>
  
Línea 1022: Línea 1095:
  
 <code z80> <code z80>
-   LD B, 8             ; 8 scanlines -> 8 iteraciones +    ld b, 8             ; 8 scanlines -> 8 iteraciones 
-  + 
-drawsp8x8_loop+drawsp8x8_loop_or
-   LD A, (DE)          ; Tomamos el dato del sprite +    ld a, (de)          ; Tomamos el dato del sprite 
-   OR (HL)             ; NUEVO: Hacemos un OR del scanline con el fondo +    or (hl)             ; NUEVO: Hacemos un OR del scanline con el fondo 
-   LD (HL),          ; Establecemos el valor del OR en videomemoria +    ld (hl),          ; Establecemos el valor del OR en videomemoria 
-   INC DE              ; Incrementamos puntero en sprite (DE+=1) +    inc de              ; Incrementamos puntero en sprite (DE+=1) 
-   INC H               ; Incrementamos puntero en pantalla (HL+=256) +    inc h               ; Incrementamos puntero en pantalla (HL+=256) 
-   DJNZ drawsp8x8_loop+    djnz drawsp8x8_loop_or
 </code> </code>
  
- A continuación, el código fuente completo de la rutina de impresión de 8x8 con operación lógica OR:+ A continuación, el código fuente completo de la rutina de impresión de 8x8 con operación lógica ''OR'':
  
 <code z80> <code z80>
Línea 1041: Línea 1114:
 ; ;
 ; Entrada (paso por parametros en memoria): ; Entrada (paso por parametros en memoria):
-; Direccion   Parametro +; Direccion        Parametro 
-50000       Direccion de la tabla de Sprites +(DS_SPRITES)      Direccion de la tabla de Sprites 
-50002       Direccion de la tabla de Atribs  (0=no atributos) +(DS_ATTRIBS)      Direccion de la tabla de Atribs  (0=no atributos) 
-50004       Coordenada X en baja resolucion +(DS_COORD_X)      Coordenada X en baja resolucion 
-50005       Coordenada Y en baja resolucion +(DS_COORD_Y)      Coordenada Y en baja resolucion 
-50006       Numero de sprite a dibujar (0-N) +(DS_NUMSPR)       Numero de sprite a dibujar (0-N)
 ;------------------------------------------------------------- ;-------------------------------------------------------------
 DrawSprite_8x8_OR: DrawSprite_8x8_OR:
  
-   ; 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 
-   LD BC, (DS_COORD_X)+    ld bc, (DS_COORD_X)
  
-   ;;; Calculamos las coordenadas destino de pantalla en DE: +    ;;; Calculamos las coordenadas destino de pantalla en DE: 
-   LD AB +    ld ab 
-   AND $18 +    and $18 
-   ADD A, $40 +    add a, $40 
-   LD D          ; Ya tenemos la parte alta calculada (010TT000) +    ld d          ; Ya tenemos la parte alta calculada (010TT000) 
-   LD A          ; Ahora calculamos la parte baja +    ld a          ; Ahora calculamos la parte baja 
-   AND +    and 
-   RRCA +    rrca 
-   RRCA +    rrca 
-   RRCA              ; A = NNN00000b +    rrca              ; A = NNN00000b 
-   ADD A         ; Sumamos COLUMNA -> A = NNNCCCCCb +    add a         ; Sumamos COLUMNA -> A = NNNCCCCCb 
-   LD E          ; Lo cargamos en la parte baja de la direccion +    ld e          ; Lo cargamos en la parte baja de la direccion 
-                     ; DE contiene ahora la direccion destino.+                      ; DE contiene ahora la direccion destino.
  
-   PUSH DE           Lo guardamos para luego, lo usaremos para +    ;;; Calcular posicion origen (array sprites) en HL como: 
-                     calcular la direccion del atributo+    ;;    direccion = base_sprites + (NUM_SPRITE*8)
  
-   ;;; Calcular posicion origen (array sprites) en HL como: +    ld bc, (DS_SPRITES) 
-   ;;;     direccion = base_sprites + (NUM_SPRITE*8) +    ld a, (DS_NUMSPR) 
-   +    ld h, 0 
-   LD BC, (DS_SPRITES) +    ld l          ; HL = DS_NUMSPR 
-   LD A, (DS_NUMSPR) +    add hlhl        ; HL = HL * 2 
-   LD H, 0 +    add hlhl        ; HL = HL * 4 
-   LD L          ; HL = DS_NUMSPR +    add hlhl        ; HL = HL * 8 = DS_NUMSPR * 8 
-   ADD HLHL        ; HL = HL * 2 +    add hlbc        ; HL = BC + HL = DS_SPRITES + (DS_NUMSPR * 8) 
-   ADD HLHL        ; HL = HL * 4 +                      ; HL contiene la direccion de inicio en el sprite
-   ADD HLHL        ; HL = HL * 8 = DS_NUMSPR * 8 +
-   ADD HLBC        ; HL = BC + HL = DS_SPRITES + (DS_NUMSPR * 8) +
-                     ; HL contiene la direccion de inicio en el sprite+
  
-   EX DEHL         ; Intercambiamos DE y HL para el OR+    ex dehl         ; Intercambiamos DE y HL (DE=origen, HL=destino)
  
-   ;;; 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)
-   LD B, 8          ; 8 scanlines -> 8 iteraciones +
-  +
-drawsp8x8_loop: +
-   LD A, (DE)       ; Tomamos el dato del sprite +
-   OR (HL)          ; Hacemos un OR del scanline con el fondo +
-   LD (HL), A       ; Establecemos el valor del OR en videomemoria +
-   INC DE           ; Incrementamos puntero en sprite +
-   INC H            ; Incrementamos puntero en pantalla +
-   DJNZ drawsp8x8_loop+
  
-   ;;; En este puntolos 8 scanlines del sprite estan dibujados.+    ld b8          ; 8 scanlines -> 8 iteraciones
  
-   POP BC             Recuperamos el offset del primer scanline+drawsp8x8_loop_or: 
 +    ld a, (de)         Tomamos el dato del sprite 
 +    or (hl)            ; NUEVO: Hacemos un OR del scanline con el fondo 
 +    ld (hl), a         ; Establecemos el valor en videomemoria 
 +    inc de             ; Incrementamos puntero en sprite 
 +    inc h              ; Incrementamos puntero en pantalla (scanline+=1) 
 +    djnz drawsp8x8_loop_or
  
-   ;;; Considerar el dibujado de los atributos (Si DS_ATTRIBS=0 -> RET) +    ;;; En este punto, los 8 scanlines del sprite estan dibujados. 
-   LD HL(DS_ATTRIBS)+    ld a
 +    sub 8              ; Recuperamos la posicion de memoria del 
 +    ld b, a            ; scanline inicial donde empezamos a dibujar 
 +    ld c, l            ; BC = HL - 8
  
-   XOR A              A = 0 +    ;;; Considerar el dibujado de los atributos (Si DS_ATTRIBS=0 -> ret) 
-   ADD A, H           A = 0 + H = H +    ld hl, (DS_ATTRIBS)
-   RET Z              ; Si = 0, volver (no dibujar atributos)+
  
-   ;;; Calcular posicion destino en area de atributos en DE. +    xor a              ; A = 0 
-   LD A, B            ; Codigo de Get_Attr_Offset_From_Image +    add a, h           A = 0 + H = H 
-   RRCA               ; Obtenemos dir de atributo partir de +    ret z              Si H = 0, volver (no dibujar atributos)
-   RRCA               dir de zona de imagen. +
-   RRCA               Nos evita volver a obtener X e Y +
-   AND 3              ; y hacer el calculo completo de la  +
-   OR $58             ; direccion en zona de atributos +
-   LD D, A +
-   LD E, C            ; DE tiene el offset del attr de HL+
  
-   LD A(DS_NUMSPR)  Cogemos el numero de sprite dibujar +    ;;; Calcular posicion destino en area de atributos en DE. 
-   LD C, A +    ld ab            Codigo de Get_Attr_Offset_From_Image 
-   LD B0 +    rrca               ; Obtenemos dir de atributo partir de 
-   ADD HLBC         HL = HL+DS_NUMSPR = Origen de atributo+    rrca               ; dir de zona de imagen. 
 +    rrca               ; Nos evita volver a obtener X e Y 
 +    and 3              ; y hacer el calculo completo de la 
 +    or $58             ; direccion en zona de atributos 
 +    ld da 
 +    ld ec            DE tiene el offset del attr de HL
  
-   ;;; Copiar (HLen (DE) -> Copiar atributo de sprite a pantalla +    ld a, (DS_NUMSPR ; Cogemos el numero de sprite a dibujar 
-   LD A(HL) +    ld ca 
-   LD (DE)        ; Mas rapido que LDI (7+7 vs 16 t-estados) +    ld b
-   RET                ; porque no necesitamos incrementar HL y DE +    add hl, bc         ; HL = HL+DS_NUMSPR = Origen de atributo
  
-END 32768+    ;;; Copiar (HL) en (DE) -> Copiar atributo de sprite a pantalla 
 +    ld a, (hl) 
 +    ld (de), a         ; Mas rapido que ldi (7+7 vs 16 t-estados) 
 +    ret                ; porque no necesitamos incrementar HL y DE
 </code> </code>
  
Línea 1136: Línea 1207:
  
 \\  \\ 
-{{ :cursos:ensamblador:gfx3_sprite8x8_OR_1.png | Sprite 8x8 impreso con OR }}+{{ :cursos:ensamblador:gfx3_sprite8x8_OR_1.png?640 | Sprite 8x8 impreso con OR }}
 \\  \\ 
  
- Ampliando el sprite...+ Ampliando el sprite vemos...
  
 \\  \\ 
Línea 1145: Línea 1216:
 \\  \\ 
  
- ¿Qué ha ocurrido con el sprite? ¿Por qué le faltan los "ojos" y hay un pixel activo en medio de la "boca"? Sencillamente, porque mediante el OR hemos impreso el sprite respetando el valor a 1 de los píxeles del fondo cuando el mismo pixel estaba a 0 en nuestro sprite. Eso ha hecho que alrededor de nuestro "personaje" no se haya borrado el fondo, ya que los píxeles a cero de nuestro sprite se convierten en "transparentes". Por desgracia, eso también hace que los ojos del personaje sean transparentes en lugar de estar a cero. En el caso del ejemplo anterior, los "ojos" del personaje coinciden con 2 píxeles de pantalla activos por lo que la operación OR los deja a 1. Lo mismo ocurre con el pixel en el centro de la boca, que se corresponde con un pixel activo en la pantalla.+ ¿Qué ha ocurrido con el sprite? ¿Por qué le faltan los "ojos" y hay un pixel activo en medio de la "boca"? Sencillamente, porque mediante el or hemos impreso el sprite respetando el valor a 1 de los píxeles del fondo cuando el mismo pixel estaba a 0 en nuestro sprite. Eso ha hecho que alrededor de nuestro "personaje" no se haya borrado el fondo, ya que los píxeles a cero de nuestro sprite se convierten en "transparentes". Por desgracia, eso también hace que los ojos del personaje sean transparentes en lugar de estar a cero. En el caso del ejemplo anterior, los "ojos" del personaje coinciden con 2 píxeles de pantalla activos por lo que la operación or los deja a 1. Lo mismo ocurre con el pixel en el centro de la boca, que se corresponde con un pixel activo en la pantalla.
  
  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 1154: 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" (que suele ser unas componentes concretas de color con un tono específico de rosa fucsia). En el caso del Spectrum, el color reside en los atributos y no en los sprites en sí, por lo que no podemos utilizar un "color transparencia".+ 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" (que suele ser unas componentes concretas de color con un tono específico de rosa fucsia). En el caso del Spectrum, el color reside en los atributos y no en los sprites en sí, por lo que no podemos utilizar un "color transparencia".
  
  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 1160: 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 "borramos" del fondo todos aquellos píxeles a cero en la máscara. Después se realiza un OR del Sprite en el resultado del anterior AND, activando los píxeles del Sprite.+ 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 "borramos" del fondo todos aquellos píxeles a cero en la máscara. Después se realiza un ''OR'' del Sprite en el resultado del anterior ''AND'', activando los píxeles del Sprite.
  
  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, mientras que borramos todos aquellos píxeles de pantalla que deben de ser reemplazados por el sprite.  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, mientras que borramos todos aquellos píxeles de pantalla que deben de ser reemplazados por el sprite.
  
- Apliquemos una máscara a nuestro ejemplo anterior. Al hacer el AND entre la máscara y el fondo (paso 1.-), eliminamos el entramado de pixeles de pantalla, con lo que al imprimir nuestro sprite con el OR (paso 2.-), los ojos y el centro de la boca serán de nuevo píxeles no activos:+ Apliquemos una máscara a nuestro ejemplo anterior. Al hacer el ''AND'' entre la máscara y el fondo (paso 1.-), eliminamos el entramado de pixeles de pantalla, con lo que al imprimir nuestro sprite con el ''OR'' (paso 2.-), los ojos y el centro de la boca serán de nuevo píxeles no activos:
  
 \\  \\ 
Línea 1170: 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 "transparencia" que presentaba el dibujado con OR.+ 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 "transparencia" que presentaba el dibujado con ''OR''.
  
  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 "confunden" con los del fondo en los contornos del sprite, ya que todos tendrán idéntico color si están dentro de una misma celda 8x8. Para evitar esto, lo ideal sería disponer de un "borde" alrededor del mismo.  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 "confunden" con los del fondo en los contornos del sprite, ya que todos tendrán idéntico color si están dentro de una misma celda 8x8. Para evitar esto, lo ideal sería disponer de un "borde" alrededor del mismo.
Línea 1193: Línea 1264:
  
 \\  \\ 
-  * El uso de máscaras requiere utilizar el doble de memoria para el spriteset gráfico (no para el de atributos), ya que por cada byte del sprite necesitamos el correspondiente byte de máscara. +  * El uso de máscaras requiere utilizar el doble de memoria para el spriteset gráfico (no para el de atributos), ya que por cada byte del sprite necesitamos el correspondiente byte de máscara.
  
   * Las rutinas de impresión con máscaras son más lentas que sin ellas, porque tienen que recoger datos de la máscara, realizar operaciones lógicas entre los datos de la misma y el fondo, y realizar incrementos adicionales del puntero al sprite (DE).   * Las rutinas de impresión con máscaras son más lentas que sin ellas, porque tienen que recoger datos de la máscara, realizar operaciones lógicas entre los datos de la misma y el fondo, y realizar incrementos adicionales del puntero al sprite (DE).
Línea 1200: 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, de forma que podamos recoger ambos valores usando el mismo puntero (DE en nuestro caso). Para eso habría que exportar el Sprite como //Mask, X Char, Char line, Y Char// y activando "Mask before graphic".  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, de forma que podamos recoger ambos valores usando el mismo puntero (DE en nuestro caso). Para eso habría que exportar el Sprite como //Mask, X Char, Char line, Y Char// y activando "Mask before graphic".
  
- El pseudocódigo de la nueva rutina **DrawSprite_8x8_MASK** sería el siguiente:+ El pseudocódigo de la nueva rutina ''DrawSprite_8x8_MASK'' sería el siguiente:
  
 <code> <code>
Línea 1226: Línea 1297:
 ; ;
 ; Entrada (paso por parametros en memoria): ; Entrada (paso por parametros en memoria):
-; Direccion   Parametro +; Direccion        Parametro 
-50000       Direccion de la tabla de Sprites +(DS_SPRITES)      Direccion de la tabla de Sprites 
-50002       Direccion de la tabla de Atribs  (0=no atributos) +(DS_ATTRIBS)      Direccion de la tabla de Atribs  (0=no atributos) 
-50004       Coordenada X en baja resolucion +(DS_COORD_X)      Coordenada X en baja resolucion 
-50005       Coordenada Y en baja resolucion +(DS_COORD_Y)      Coordenada Y en baja resolucion 
-50006       Numero de sprite a dibujar (0-N) +(DS_NUMSPR)       Numero de sprite a dibujar (0-N)
 ;------------------------------------------------------------- ;-------------------------------------------------------------
 DrawSprite_8x8_MASK: DrawSprite_8x8_MASK:
  
-   ; 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 
-   LD BC, (DS_COORD_X)+    ld bc, (DS_COORD_X) 
 + 
 +    ;;; Calculamos las coordenadas destino de pantalla en DE: 
 +    ld a, b 
 +    and $18 
 +    add a, $40 
 +    ld d, a           ; Ya tenemos la parte alta calculada (010TT000) 
 +    ld a, b           ; Ahora calculamos la parte baja 
 +    and 7 
 +    rrca 
 +    rrca 
 +    rrca              ; A = NNN00000b 
 +    add a, c          ; Sumamos COLUMNA -> A = NNNCCCCCb 
 +    ld e, a           ; Lo cargamos en la parte baja de la direccion 
 +                      ; DE contiene ahora la direccion destino.
  
-   ;;; Calculamos las coordenadas destino de pantalla en DE+    ;;; Calcular posicion origen (array sprites) en HL como
-   LD A, B +    ;;;     direccion base_sprites + (NUM_SPRITE*16)
-   AND $18 +
-   ADD A, $40 +
-   LD D, A           Ya tenemos la parte alta calculada (010TT000) +
-   LD A, B           Ahora calculamos la parte baja +
-   AND 7 +
-   RRCA +
-   RRCA +
-   RRCA              NNN00000b +
-   ADD A, C          ; Sumamos COLUMNA -> A = NNNCCCCCb +
-   LD E, A           ; Lo cargamos en la parte baja de la direccion +
-                     ; DE contiene ahora la direccion destino.+
  
-   PUSH DE           Lo guardamos para luegolo usaremos para +    ld bc, (DS_SPRITES) 
-                     calcular la direccion del atributo+    ld a, (DS_NUMSPR) 
 +    ld h, 0 
 +    ld l, a            HL = DS_NUMSPR 
 +    add hlhl         ; HL = HL * 2 
 +    add hl, hl         ; HL = HL * 4 
 +    add hl, hl         ; HL = HL * 8 
 +    add hl, hl         ; HL = HL * 16 = DS_NUMSPR * 16 
 +    add hl, bc         ; HL = BC + HL = DS_SPRITES + (DS_NUMSPR * 16) 
 +                       HL contiene la direccion de inicio en el sprite
  
-   ;;; Calcular posicion origen (array sprites) en HL como: +    ex de, hl          ; Intercambiamos DE y HL para el OR
-   ;;;     direccion = base_sprites + (NUM_SPRITE*16) +
-   +
-   LD BC, (DS_SPRITES) +
-   LD A, (DS_NUMSPR) +
-   LD H, 0 +
-   LD L, A           ; HL = DS_NUMSPR +
-   ADD HL, HL        ; HL = HL * 2 +
-   ADD HL, HL        ; HL = HL * 4 +
-   ADD HL, HL        ; HL = HL * 8 +
-   ADD HL, HL        ; HL = HL * 16 = DS_NUMSPR * 16 +
-   ADD HL, BC        ; HL = BC + HL = DS_SPRITES + (DS_NUMSPR * 16) +
-                     ; HL contiene la direccion de inicio en el sprite +
-   EX DEHL         ; Intercambiamos DE y HL para el AND+OR+
  
-   ;;; Dibujar 8 scanlines (DE) -> (HL) + bajar scanline y avanzar en SPR +    ;;; Dibujar 8 scanlines (DE) -> (HL) + bajar scanline y avanzar en SPR 
-   LD B, 8+    ld b, 8
  
 drawspr8x8m_loop: drawspr8x8m_loop:
-   LD A, (DE      ; Obtenemos un byte del sprite (el byte de mascara) +    ld a, (de        ; Obtenemos un byte del sprite (el byte de mascara) 
-   AND (HL        ; A = A AND (HL+    and (hl          ; A = A and (hl
-   LD CA          ; Nos guardamos el valor del AND +    ld ca            ; Nos guardamos el valor del AND 
-   INC DE           ; Avanzamos al siguiente byte (el dato grafico) +    inc de             ; Avanzamos al siguiente byte (el dato grafico) 
-   LD A, (DE      ; Obtenemos el byte grafico +    ld a, (de        ; Obtenemos el byte grafico 
-   OR C             ; A = A OR C = A OR (MASK AND FONDO+    or c               ; A = A or c = A OR (MASK and fONDO
-   LD (HL), A       ; Imprimimos el dato tras aplicar operaciones logicas +    ld (hl), a         ; Imprimimos el dato tras aplicar operaciones logicas 
-   INC DE           ; Avanzamos al siguiente dato del sprite +    inc de             ; Avanzamos al siguiente dato del sprite 
-   INC H            ; Incrementamos puntero en pantalla (siguiente scanline) +    inc h              ; Incrementamos puntero en pantalla (siguiente scanline) 
-   DJNZ drawspr8x8m_loop+    djnz drawspr8x8m_loop
  
-   ;;; En este punto, los 8 scanlines del sprite estan dibujados.+    ;;; En este punto, los 8 scanlines del sprite estan dibujados. 
 +    ld a, h 
 +    sub 8              ; Recuperamos la posicion de memoria del 
 +    ld b, a            ; scanline inicial donde empezamos a dibujar 
 +    ld c, l            ; BC = HL - 8
  
-   POP BC             Recuperamos el offset del primer scanline+    ;;; Considerar el dibujado de los atributos (Si DS_ATTRIBS=0 -> ret) 
 +    ld hl, (DS_ATTRIBS)
  
-   ;;; Considerar el dibujado de los atributos (Si DS_ATTRIBS=0 -> RET) +    xor a              ; A = 0 
-   LD HL, (DS_ATTRIBS)+    add a, h           A = 0 + H = H 
 +    ret z              ; Si = 0, volver (no dibujar atributos)
  
-   XOR A              A = 0 +    ;;; Calcular posicion destino en area de atributos en DE. 
-   ADD AH           A = 0 + H = H +    ld ab            Codigo de Get_Attr_Offset_From_Image 
-   RET Z              Si H = 0, volver (no dibujar atributos)+    rrca               Obtenemos dir de atributo a partir de 
 +    rrca               ; dir de zona de imagen. 
 +    rrca               ; Nos evita volver a obtener X e Y 
 +    and 3              ; y hacer el calculo completo de la 
 +    or $58             ; direccion en zona de atributos 
 +    ld d, a 
 +    ld e, c            ; DE tiene el offset del attr de HL
  
-   ;;; Calcular posicion destino en area de atributos en DE. +    ld a, (DS_NUMSPR)  ; Cogemos el numero de sprite a dibujar 
-   LD A, B            ; Codigo de Get_Attr_Offset_From_Image +    ld ca 
-   RRCA               ; Obtenemos dir de atributo partir de +    ld b, 0 
-   RRCA               ; dir de zona de imagen. +    add hlbc         ; HL = HL+DS_NUMSPR = Origen de atributo
-   RRCA               ; Nos evita volver a obtener X e Y +
-   AND 3              ; y hacer el calculo completo de la  +
-   OR $58             ; direccion en zona de atributos +
-   LD D, A +
-   LD E, C            ; DE tiene el offset del attr de HL +
- +
-   LD A, (DS_NUMSPR)  ; Cogemos el numero de sprite a dibujar +
-   LD CA +
-   LD B, 0 +
-   ADD HLBC         ; HL = HL+DS_NUMSPR = Origen de atributo +
- +
-   ;;; Copiar (HL) en (DE) -> Copiar atributo de sprite a pantalla +
-   LD A, (HL) +
-   LD (DE), A         ; Mas rapido que LDI (7+7 vs 16 t-estados) +
-   RET                ; porque no necesitamos incrementar HL y DE +
  
 +    ;;; Copiar (HL) en (DE) -> Copiar atributo de sprite a pantalla
 +    ld a, (hl)
 +    ld (de), a         ; Mas rapido que ldi (7+7 vs 16 t-estados)
 +    ret                ; porque no necesitamos incrementar HL y DE
 </code> </code>
  
Línea 1332: Línea 1402:
 </code> </code>
  
- El código para el cálculo agrega un "**ADD HLHL**" adicional para esta tarea:+ El código para el cálculo agrega un ''add hlhl'' adicional para esta tarea:
  
 <code z80> <code z80>
-   LD BC, (DS_SPRITES) +    ld bc, (DS_SPRITES) 
-   LD A, (DS_NUMSPR) +    ld a, (DS_NUMSPR) 
-   LD H, 0 +    ld h, 0 
-   LD L          ; HL = DS_NUMSPR +    ld l          ; HL = DS_NUMSPR 
-   ADD HLHL        ; HL = HL * 2 +    add hlhl        ; HL = HL * 2 
-   ADD HLHL        ; HL = HL * 4 +    add hlhl        ; HL = HL * 4 
-   ADD HLHL        ; HL = HL * 8 +    add hlhl        ; HL = HL * 8 
-   ADD HLHL        ; HL = HL * 16 = DS_NUMSPR * 16 +    add hlhl        ; HL = HL * 16 = DS_NUMSPR * 16 
-   ADD HLBC        ; HL = BC + HL = DS_SPRITES + (DS_NUMSPR * 16) +    add hlbc        ; HL = BC + HL = DS_SPRITES + (DS_NUMSPR * 16) 
-                     ; HL contiene la direccion de inicio en el sprite+                      ; HL contiene la direccion de inicio en el sprite
 </code> </code>
  
Línea 1350: Línea 1420:
  
 <code z80> <code z80>
-   LD B, 8+    ld b, 8
 drawspr8x8m_loop: drawspr8x8m_loop:
-   LD A, (DE      ; Obtenemos un byte del sprite (el byte de mascara) +    ld a, (de       ; Obtenemos un byte del sprite (el byte de mascara) 
-   AND (HL        ; A = A AND (HL+    and (hl         ; A = A and (hl
-   LD CA          ; Nos guardamos el valor del AND +    ld ca           ; Nos guardamos el valor del AND 
-   INC DE           ; Avanzamos al siguiente byte (el dato grafico) +    inc de            ; Avanzamos al siguiente byte (el dato grafico) 
-   LD A, (DE      ; Obtenemos el byte grafico +    ld a, (de       ; Obtenemos el byte grafico 
-   OR C             ; A = A OR C = A OR (MASK AND FONDO+    or c              ; A = A or c = A OR (MASK and fONDO
-   LD (HL), A       ; Imprimimos el dato tras aplicar operaciones logicas +    ld (hl), a        ; Imprimimos el dato tras aplicar operaciones logicas 
-   INC DE           ; Avanzamos al siguiente dato del sprite +    inc de            ; Avanzamos al siguiente dato del sprite 
-   INC H            ; Incrementamos puntero en pantalla (siguiente scanline) +    inc h             ; Incrementamos puntero en pantalla (siguiente scanline) 
-   DJNZ drawspr8x8m_loop+    djnz drawspr8x8m_loop
 </code> </code>
  
Línea 1390: Línea 1460:
  
 cara_gfx: cara_gfx:
-   DEFB   227, 28, 193, 62, 128, 107, 128, 127 +    DEFB   227, 28, 193, 62, 128, 107, 128, 127 
-   DEFB   128, 93, 128, 99, 193,  62, 227, 28+    DEFB   128, 93, 128, 99, 193,  62, 227, 28
  
 cara_attrib: cara_attrib:
-   DEFB   56+    DEFB   56
 </code> </code>
  
Línea 1400: Línea 1470:
  
 \\  \\ 
-{{ :cursos:ensamblador:gfx3_mask_2.png | Impresión del Sprite con máscara }}+{{ :cursos:ensamblador:gfx3_mask_2.png?640 | Impresión del Sprite con máscara }}
 \\  \\ 
  
Línea 1411: Línea 1481:
  Nuestro pequeño sprite se vería mucho mejor con un reborde vacío alrededor, ya que hay píxeles del fondo pegados a nuestro personaje. Por desgracia, en un sprite de 8x8 como el nuestro apenas nos queda espacio para este reborde en la máscara, pero se podría haber aplicado si el sprite fuera de mayores dimensiones.  Nuestro pequeño sprite se vería mucho mejor con un reborde vacío alrededor, ya que hay píxeles del fondo pegados a nuestro personaje. Por desgracia, en un sprite de 8x8 como el nuestro apenas nos queda espacio para este reborde en la máscara, pero se podría haber aplicado si el sprite fuera de mayores dimensiones.
  
- Un apunte final sobre los ejemplos que hemos visto: nótese que estamos llamando a todas las rutinas asignando DS_NUMSPR=0, para imprimir el "primer" (y único sprite en nuestro caso) del spriteset. El Spriteset podría tener hasta 255 sprites dispuestos verticalmente (con sus máscaras intercaladas) y esta misma rutina, sin modificaciones, nos serviría para dibujar cualquiera de ellos variando DS_NUMSPR.+ Un apunte final sobre los ejemplos que hemos visto: nótese que estamos llamando a todas las rutinas asignando ''DS_NUMSPR = 0'', para imprimir el "primer" (y único sprite en nuestro caso) del spriteset. El Spriteset podría tener hasta 255 sprites dispuestos verticalmente (con sus máscaras intercaladas) y esta misma rutina, sin modificaciones, nos serviría para dibujar cualquiera de ellos variando ''DS_NUMSPR''.
  
  
Línea 1428: 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), pero ahora tendremos que avanzar 8*2*2=32 bytes por cada sprite en los sprites sin máscara. El cálculo quedaría como BASE+(DS_NUMSPR*32). +   * 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), pero ahora tendremos que avanzar 8*2*2=32 bytes por cada sprite en los sprites sin máscara. El cálculo quedaría como BASE+(DS_NUMSPR*32). 
-  * 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 HLHL**. 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 hlhl**. Esta técnica requiere menos ciclos de reloj para su ejecución. 
-  * El avance al siguiente scanline cambia ligeramente, ya que necesitamos incrementar HL (concretamente, L) para poder imprimir el segundo byte horizontal. + 
-  * 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, ya que necesitamos incrementar HL (concretamente, L) para poder imprimir el segundo byte horizontal. 
 + 
 +   * 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 1450: 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 1460: 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 1486: Línea 1562:
 ; Entrada (paso por parametros en memoria): ; Entrada (paso por parametros en memoria):
 ; Direccion   Parametro ; Direccion   Parametro
-50000       Direccion de la tabla de Sprites +Direccion        Parametro 
-50002       Direccion de la tabla de Atribs  (0=no atributos) +; (DS_SPRITES)      Direccion de la tabla de Sprites 
-50004       Coordenada X en baja resolucion +(DS_ATTRIBS)      Direccion de la tabla de Atribs  (0=no atributos) 
-50005       Coordenada Y en baja resolucion +(DS_COORD_X)      Coordenada X en baja resolucion 
-50006       Numero de sprite a dibujar (0-N) +(DS_COORD_Y)      Coordenada Y en baja resolucion 
 +(DS_NUMSPR)       Numero de sprite a dibujar (0-N)
 ;------------------------------------------------------------- ;-------------------------------------------------------------
 DrawSprite_16x16_LD: DrawSprite_16x16_LD:
  
-   ; 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 
-   LD BC, (DS_COORD_X) +    ld bc, (DS_COORD_X)
-  +
-   ;;; Calculamos las coordenadas destino de pantalla en DE: +
-   LD A, B +
-   AND $18 +
-   ADD A, $40 +
-   LD D, A +
-   LD A, B +
-   AND 7 +
-   RRCA +
-   RRCA +
-   RRCA +
-   ADD A, C +
-   LD E, A +
-  +
-   PUSH DE           ; Lo guardamos para luego, lo usaremos para +
-                     ; calcular la direccion del atributo +
-  +
-   ;;; Calcular posicion origen (array sprites) en HL como: +
-   ;;;     direccion = base_sprites + (NUM_SPRITE*32) +
-   ;;; Multiplicamos con desplazamientos, ver los comentarios. +
-   LD BC, (DS_SPRITES) +
-   LD A, (DS_NUMSPR) +
-   LD L, 0           ; AL = DS_NUMSPR*256 +
-   SRL A             ; Desplazamos a la derecha para dividir por dos +
-   RR L              ; AL = DS_NUMSPR*128 +
-   RRA               ; Rotamos, ya que el bit que salio de L al CF fue 0 +
-   RR L              ; AL = DS_NUMSPR*64 +
-   RRA               ; Rotamos, ya que el bit que salio de L al CF fue 0 +
-   RR L              ; AL = DS_NUMSPR*32 +
-   LD H, A           ; HL = DS_NUMSPR*32 +
-   ADD HL, BC        ; HL = BC + HL = DS_SPRITES + (DS_NUMSPR * 32) +
-                     ; HL contiene la direccion de inicio en el sprite+
  
-   EX DE, HL         ; Intercambiamos DE y HL (DE=origen, HL=destino)+    ;;; Calculamos las coordenadas destino de pantalla en DE
 +    ld a
 +    and $18 
 +    add a, $40 
 +    ld d, a 
 +    ld a, b 
 +    and 7 
 +    rrca 
 +    rrca 
 +    rrca 
 +    add a, c 
 +    ld e, a 
 + 
 +    push de           ; Lo guardamos para luego, lo usaremos para 
 +                      ; calcular la direccion del atributo 
 + 
 +    ;;; Calcular posicion origen (array sprites) en HL como: 
 +    ;;;     direccion = base_sprites + (NUM_SPRITE*32) 
 +    ;;; Multiplicamos con desplazamientos, ver los comentarios. 
 +    ld bc, (DS_SPRITES) 
 +    ld a, (DS_NUMSPR) 
 +    ld l, 0           ; AL = DS_NUMSPR*256 
 +    srl a             ; Desplazamos a la derecha para dividir por dos 
 +    rr l              ; AL = DS_NUMSPR*128 
 +    rra               ; Rotamos, ya que el bit que salio de L al CF fue 0 
 +    rr l              ; AL = DS_NUMSPR*64 
 +    rra               ; Rotamos, ya que el bit que salio de L al CF fue 0 
 +    rr l              ; AL = DS_NUMSPR*32 
 +    ld h, a           ; HL = DS_NUMSPR*32 
 +    add hl, bc        ; HL = BC + HL = DS_SPRITES + (DS_NUMSPR * 32) 
 +                      ; HL contiene la direccion de inicio en el sprite 
 + 
 +    ex de, hl         ; Intercambiamos DE y HL (DE=origen, HL=destino) 
 + 
 +    ;;; Repetir 8 veces (primeros 2 bloques horizontales): 
 +    ld b, 8
  
-   ;;; Repetir 8 veces (primeros 2 bloques horizontales): 
-   LD B, 8 
-  
 drawsp16x16_loop1: drawsp16x16_loop1:
-   LD A, (DE)         ; Bloque 1: Leemos dato del sprite +    ld a, (de)         ; Bloque 1: Leemos dato del sprite 
-   LD (HL),         ; Copiamos dato a pantalla +    ld (hl),         ; Copiamos dato a pantalla 
-   INC DE             ; Incrementar puntero en sprite +    inc de             ; Incrementar puntero en sprite 
-   INC L              ; Incrementar puntero en pantalla+    inc l              ; Incrementar puntero en pantalla
  
-   LD A, (DE)         ; Bloque 2: Leemos dato del sprite +    ld a, (de)         ; Bloque 2: Leemos dato del sprite 
-   LD (HL),         ; Copiamos dato a pantalla +    ld (hl),         ; Copiamos dato a pantalla 
-   INC DE             ; Incrementar puntero en sprite+    inc de             ; Incrementar puntero en sprite
  
-   INC H              ; Hay que sumar 256 para ir al siguiente scanline +    inc h              ; Hay que sumar 256 para ir al siguiente scanline 
-   DEC L              ; pero hay que restar el INC L que hicimos. +    dec l              ; pero hay que restar el inc l que hicimos. 
-   DJNZ drawsp16x16_loop1+    djnz drawsp16x16_loop1 
 + 
 +    ; 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 
 + 
 +    ;;;inc h           ; No hay que hacer inc h, lo hizo en el bucle 
 +    ;;;ld a, h         ; No hay que hacer esta prueba, sabemos que 
 +    ;;;and 7           ; no hay salto (es un cambio de bloque) 
 +    ;;;jr nz, drawsp16_nofix_abajop 
 +    ld a, l 
 +    add a, 32 
 +    ld l, a 
 +    jr c, drawsp16_nofix_abajop 
 +    ld a, h 
 +    sub 8 
 +    ld h, a
  
-   ; 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 
-    
-   ;;;INC H           ; No hay que hacer INC H, lo hizo en el bucle 
-   LD A, H 
-   AND 7 
-   JR NZ, drawsp16_nofix_abajop 
-   LD A, L 
-   ADD A, 32 
-   LD L, A 
-   JR C, drawsp16_nofix_abajop 
-   LD A, H 
-   SUB 8 
-   LD H, A 
-  
 drawsp16_nofix_abajop: drawsp16_nofix_abajop:
  
-   ;;; Repetir 8 veces (segundos 2 bloques horizontales): +    ;;; Repetir 8 veces (segundos 2 bloques horizontales): 
-   LD B, 8 +    ld b, 8 
- +
 drawsp16x16_loop2: drawsp16x16_loop2:
-   LD A, (DE)         ; Bloque 1: Leemos dato del sprite +    ld a, (de)         ; Bloque 1: Leemos dato del sprite 
-   LD (HL),         ; Copiamos dato a pantalla +    ld (hl),         ; Copiamos dato a pantalla 
-   INC DE             ; Incrementar puntero en sprite +    inc de             ; Incrementar puntero en sprite 
-   INC L              ; Incrementar puntero en pantalla+    inc l              ; Incrementar puntero en pantalla
  
-   LD A, (DE)         ; Bloque 2: Leemos dato del sprite +    ld a, (de)         ; Bloque 2: Leemos dato del sprite 
-   LD (HL),         ; Copiamos dato a pantalla +    ld (hl),         ; Copiamos dato a pantalla 
-   INC DE             ; Incrementar puntero en sprite+    inc de             ; Incrementar puntero en sprite
  
-   INC H              ; Hay que sumar 256 para ir al siguiente scanline +    inc h              ; Hay que sumar 256 para ir al siguiente scanline 
-   DEC L              ; pero hay que restar el INC L que hicimos. +    dec l              ; pero hay que restar el inc l que hicimos. 
-   DJNZ drawsp16x16_loop2 +    djnz drawsp16x16_loop2 
-  + 
-   ;;; En este punto, los 16 scanlines del sprite estan dibujados. +    ;;; En este punto, los 16 scanlines del sprite estan dibujados. 
-  + 
-   POP BC             ; Recuperamos el offset del primer scanline +    pop bc             ; Recuperamos el offset del primer scanline 
-  + 
-   ;;; Considerar el dibujado de los atributos (Si DS_ATTRIBS=0 -> RET+    ;;; Considerar el dibujado de los atributos (Si DS_ATTRIBS=0 -> ret
-   LD HL, (DS_ATTRIBS) +    ld hl, (DS_ATTRIBS) 
-  + 
-   XOR A              ; A = 0 +    xor a              ; A = 0 
-   ADD A          ; A = 0 + H = H +    add a          ; A = 0 + H = H 
-   RET Z              ; Si H = 0, volver (no dibujar atributos) +    ret z              ; Si H = 0, volver (no dibujar atributos) 
-  + 
-   ;;; Calcular posicion destino en area de atributos en DE. +    ;;; Calcular posicion destino en area de atributos en DE. 
-   LD A           ; Codigo de Get_Attr_Offset_From_Image +    ld a           ; Codigo de Get_Attr_Offset_From_Image 
-   RRCA               ; Obtenemos dir de atributo a partir de +    rrca               ; Obtenemos dir de atributo a partir de 
-   RRCA               ; dir de zona de imagen. +    rrca               ; dir de zona de imagen. 
-   RRCA               ; Nos evita volver a obtener X e Y +    rrca               ; Nos evita volver a obtener X e Y 
-   AND 3              ; y hacer el calculo completo de la  +    and 3              ; y hacer el calculo completo de la 
-   OR $58             ; direccion en zona de atributos +    or $58             ; direccion en zona de atributos 
-   LD DA +    ld da 
-   LD E           ; DE tiene el offset del attr de HL +    ld e           ; DE tiene el offset del attr de HL 
-  + 
-   LD A, (DS_NUMSPR)  ; Cogemos el numero de sprite a dibujar +    ld a, (DS_NUMSPR)  ; Cogemos el numero de sprite a dibujar 
-   LD CA +    ld ca 
-   LD B, 0 +    ld b, 0 
-   ADD HLBC         ; HL = HL+DS_NUMSPR +    add hlbc         ; HL = HL+DS_NUMSPR 
-   ADD HLBC         ; HL = HL+DS_NUMSPR*2 +    add hlbc         ; HL = HL+DS_NUMSPR*2 
-   ADD HLBC         ; HL = HL+DS_NUMSPR*3 +    add hlbc         ; HL = HL+DS_NUMSPR*3 
-   ADD HLBC         ; HL = HL+HL=(DS_NUMSPR*4) = Origen de atributo +    add hlbc         ; HL = HL+HL=(DS_NUMSPR*4) = Origen de atributo 
-  + 
-   LDI +    ldi 
-   LDI                ; Imprimimos las 2 primeras filas de atributo+    ldi                ; Imprimimos las 2 primeras filas de atributo
  
-   ;;; Avance diferencial a la siguiente linea de atributos +    ;;; Avance diferencial a la siguiente linea de atributos 
-   LD A           ; A = L +    ld a           ; A = L 
-   ADD A, 30          ; Sumamos A = A + 30 mas los 2 INCs de LDI+    add a, 30          ; Sumamos A = A + 30 mas los 2 INCs de ldi
-   LD E           ; Guardamos en L (L = L+30 + 2 por LDI=L+32) +    ld e           ; Guardamos en L (L = L+30 + 2 por ldi=L+32) 
-   JR NC, drawsp16x16_attrab_noinc +    jr nc, drawsp16x16_attrab_noinc 
-   INC D+    inc d
 drawsp16x16_attrab_noinc: drawsp16x16_attrab_noinc:
-   LDI +    ldi 
-   LDI +    ldi 
-   RET                ; porque no necesitamos incrementar HL y DE +    ret                ; porque no necesitamos incrementar HL y DE
 </code> </code>
  
 \\  \\ 
- 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 HLHL** 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 ''DS_NUMSPR''. Una primera aproximación de multiplicación de HL = NUM_SPR * 32 podría ser aumentar el número de sumas ''add hlhl'' tal y como se realizan en las rutinas de 8x8:
  
 <code z80> <code z80>
-   ;;; Multiplicar DS_SPRITES por 32 con sumas +    ;;; Multiplicar DS_SPRITES por 32 con sumas 
-   LD BC, (DS_SPRITES) +    ld bc, (DS_SPRITES) 
-   LD A, (DS_NUMSPR) +    ld a, (DS_NUMSPR) 
-   LD H, 0           ; H = 0 +    ld h, 0           ; H = 0 
-   LD L          ; HL = DS_NUMSPR +    ld l          ; HL = DS_NUMSPR 
-   ADD HLHL        ; HL = HL * 2  +    add hlhl        ; HL = HL * 2 
-   ADD HLHL        ; HL = HL * 4 +    add hlhl        ; HL = HL * 4 
-   ADD HLHL        ; HL = HL * 8 +    add hlhl        ; HL = HL * 8 
-   ADD HLHL        ; HL = HL * 16  +    add hlhl        ; HL = HL * 16 
-   ADD HLHL        ; HL = HL * 32 +    add hlhl        ; HL = HL * 32 
-   ADD HLBC        ; HL = DS_SPRITES + (DS_NUMSPR * 32)+    add hlbc        ; HL = DS_SPRITES + (DS_NUMSPR * 32)
 </code> </code>
  
- 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 LA**, 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 ''ADD'' de 16 bits, más 7 t-estados de ''ld h, 0'', más 4 de ''ld la'', lo que da un total de 77 t-estados para realizar la multiplicación.
  
  La técnica empleada en el listado, proporcionada por **metalbrain**, implica cargar el valor de DS_NUMSPR en la parte alta de un registro de 16 bits (con lo que el registro tendría el valor de DS_NUMSPR*256, como si lo hubieramos desplazado 8 veces a la izquierda), y después realizar desplazamientos de 16 bits a la derecha, dividiendo este valor por 2 en cada desplazamiento. Con un desplazamiento, obtenemos en el registro el valor DS_NUMSPR * 128, con otro desplazamiento DS_NUMSPR * 64, y con otro más DS_NUMSPR * 32.  La técnica empleada en el listado, proporcionada por **metalbrain**, implica cargar el valor de DS_NUMSPR en la parte alta de un registro de 16 bits (con lo que el registro tendría el valor de DS_NUMSPR*256, como si lo hubieramos desplazado 8 veces a la izquierda), y después realizar desplazamientos de 16 bits a la derecha, dividiendo este valor por 2 en cada desplazamiento. Con un desplazamiento, obtenemos en el registro el valor DS_NUMSPR * 128, con otro desplazamiento DS_NUMSPR * 64, y con otro más DS_NUMSPR * 32.
Línea 1650: Línea 1727:
  
 <code z80> <code z80>
-   ;;; Multiplicar DS_SPRITES por 32 con desplazamientos >> +    ;;; Multiplicar DS_SPRITES por 32 con desplazamientos >> 
-   LD BC, (DS_SPRITES) +    ld bc, (DS_SPRITES) 
-   LD A, (DS_NUMSPR) +    ld a, (DS_NUMSPR) 
-   LD L, 0           ; AL = DS_NUMSPR*256 +    ld l, 0           ; AL = DS_NUMSPR*256 
-   SRL A             ; Desplazamos a la derecha para dividir por dos +    srl a             ; Desplazamos a la derecha para dividir por dos 
-   RR L              ; AL = DS_NUMSPR*128 +    rr l              ; AL = DS_NUMSPR*128 
-   RRA               ; Rotamos, ya que el bit que salio de L al CF fue 0 +    rra               ; Rotamos, ya que el bit que salio de L al CF fue 0 
-   RR L              ; AL = DS_NUMSPR*64 +    rr l              ; AL = DS_NUMSPR*64 
-   RRA               ; Rotamos, ya que el bit que salio de L al CF fue 0 +    rra               ; Rotamos, ya que el bit que salio de L al CF fue 0 
-   RR L              ; AL = DS_NUMSPR*32 +    rr l              ; AL = DS_NUMSPR*32 
-   LD H          ; HL = DS_NUMSPR*32 +    ld h          ; HL = DS_NUMSPR*32 
-   ADD HLBC        ; HL = BC + HL = DS_SPRITES + (DS_NUMSPR * 32) +    add hlbc        ; HL = BC + HL = DS_SPRITES + (DS_NUMSPR * 32) 
-                     ; HL contiene la direccion de inicio en el sprite+                      ; HL contiene la direccion de inicio en el sprite
 </code> </code>
  
- 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 (''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''.
  
- 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, con un coste total de 55 t-estados.+ 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, con un coste total de 55 t-estados.
  
- 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 **INC H** (HL=HL+256).+ 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 ''inc h'' (HL=HL+256).
  
- 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"). Para ello se ha insertado el código de "//Siguiente_Scanline_HL//" dentro de la rutina (evitando el CALL y el RET). La instrucción inicial **INC H** de la rutina que vimos en el capítulo anterior no es necesaria porque la ejecuta la última iteración del bucle anterior.+ 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"). Para ello se ha insertado el código de ''Siguiente_Scanline_HL'' dentro de la rutina (evitando el ''call'' y el ''RET''). La instrucción inicial ''inc h'' de la rutina que vimos en el capítulo anterior no es necesaria porque la ejecuta la última iteración del bucle anterior.
  
  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 1680: Línea 1757:
  
 <code z80> <code z80>
-   LD B, 16           ; 16 iteraciones +    ld b, 16           ; 16 iteraciones 
-   +
 drawsp16x16_loop: drawsp16x16_loop:
-   LD A, (DE)         ; Bloque 1: Leemos dato del sprite +    ld a, (de)         ; Bloque 1: Leemos dato del sprite 
-   LD (HL),         ; Copiamos dato a pantalla +    ld (hl),         ; Copiamos dato a pantalla 
-   INC DE             ; Incrementar puntero en sprite +    inc de             ; Incrementar puntero en sprite 
-   INC L              ; Incrementar puntero en pantalla +    inc l              ; Incrementar puntero en pantalla 
-   LD A, (DE)         ; Bloque 2: Leemos dato del sprite +    ld a, (de)         ; Bloque 2: Leemos dato del sprite 
-   LD (HL),         ; Copiamos dato a pantalla +    ld (hl),         ; Copiamos dato a pantalla 
-   INC DE             ; Incrementar puntero en sprite +    inc de             ; Incrementar puntero en sprite 
-   DEC L              ; Decrementamos el avance realizado+    dec l              ; Decrementamos el avance realizado
  
-   ; 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 +    ;;;inc h           ; No hay que hacer inc h, lo hizo en el bucle 
-   LD AH +    ;;;ld a, h         ; No hay que hacer esta pruebasabemos que 
-   AND +    ;;;and           ; no hay salto (es un cambio de bloque) 
-   JR NZ, drawsp16_nofix_abajop +    ;;;jr nz, drawsp16_nofix_abajop 
-   LD AL +    ld al 
-   ADD A, 32 +    add a, 32 
-   LD LA +    ld la 
-   JR C, drawsp16_nofix_abajop +    jr c, drawsp16_nofix_abajop 
-   LD AH +    ld ah 
-   SUB +    sub 
-   LD HA+    ld ha
 drawsp16_nofix_abajop: drawsp16_nofix_abajop:
  
-   DJNZ drawsp16x16_loop+    djnz drawsp16x16_loop
 </code> </code>
  
- 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, ya que sólo se debe chequear el caracter/tercio en el salto de un bloque de pantalla al siguiente, que sólo ocurre 1 vez en el caso de un sprite de 2x2  bloques impreso en posiciones de carácter. Además, en la última iteración es innecesario incrementar y ajustar HL (y realizar el JR NZ), por lo que son ciclos de reloj que se malgastan.+ 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, ya que sólo se debe chequear el caracter/tercio en el salto de un bloque de pantalla al siguiente, que sólo ocurre 1 vez en el caso de un sprite de 2x2  bloques impreso en posiciones de carácter. Además, en la última iteración es innecesario incrementar y ajustar HL, por lo que son ciclos de reloj que se malgastan.
  
- Finalmente, a la hora de escribir los atributos cambia el cálculo de la posición origen (ahora es DS_NUMSPR*4, cuya multiplicación realizamos en base a 4 sumas), así como la copia de los 4 bytes, ya que hay que imprimir los 2 primeros (LDI + LDI), avanzar DE hasta la siguiente "fila de atributos", y copiar los 2 siguientes (con otras 2 instrucciones LDI).+ Finalmente, a la hora de escribir los atributos cambia el cálculo de la posición origen (ahora es DS_NUMSPR*4, cuya multiplicación realizamos en base a 4 sumas), así como la copia de los 4 bytes, ya que hay que imprimir los 2 primeros (''LDI'' ''LDI''), avanzar DE hasta la siguiente "fila de atributos", y copiar los 2 siguientes (con otras 2 instrucciones ldi).
  
  Veamos la ejecución de la rutina con un sencillo ejemplo... Primero dibujamos en SevenuP el pequeño personaje de 2x2 bloques con el que abríamos este apartado y lo exportamos a ASM:  Veamos la ejecución de la rutina con un sencillo ejemplo... Primero dibujamos en SevenuP el pequeño personaje de 2x2 bloques con el que abríamos este apartado y lo exportamos a ASM:
Línea 1726: Línea 1803:
  
 bicho_gfx: bicho_gfx:
-   DEFB    8,128,  4, 64,  0,  0,  7,224 +    DEFB    8,128,  4, 64,  0,  0,  7,224 
-   DEFB   15, 80, 15, 80, 15,240,  7,224 +    DEFB   15, 80, 15, 80, 15,240,  7,224 
-   DEFB    0,  0,  1, 64,  0, 32,  2,  0 +    DEFB    0,  0,  1, 64,  0, 32,  2,  0 
-   DEFB  104, 22,112, 14, 56, 28,  0,  0+    DEFB  104, 22,112, 14, 56, 28,  0,  0
  
 bicho_attrib: bicho_attrib:
-   DEFB   70, 71, 67,  3+    DEFB   70, 71, 67,  3
 </code> </code>
  
Línea 1738: Línea 1815:
  
 <code z80> <code z80>
-  LD HL, bicho_gfx +    ld hl, bicho_gfx 
-  LD (DS_SPRITES), HL +    ld (DS_SPRITES), hl 
-  LD HL, bicho_attrib +    ld hl, bicho_attrib 
-  LD (DS_ATTRIBS), HL +    ld (DS_ATTRIBS), hl 
-  LD A, 13 +    ld a, 13 
-  LD (DS_COORD_X), A +    ld (DS_COORD_X), a 
-  LD A, 8 +    ld a, 8 
-  LD (DS_COORD_Y), A +    ld (DS_COORD_Y), a 
-  XOR A +    xor a 
-  LD (DS_NUMSPR), A +    ld (DS_NUMSPR), a 
-  CALL DrawSprite_16x16_LD +    call DrawSprite_16x16_LD 
-</code> +</code>
  
  Este es el aspecto del sprite impreso en pantalla sobre la trama de píxeles alternos que estamos utilizando hasta el momento:  Este es el aspecto del sprite impreso en pantalla sobre la trama de píxeles alternos que estamos utilizando hasta el momento:
  
 \\  \\ 
-{{ :cursos:ensamblador:gfx3_sprite16x16_1.png | Sprite 16x16 impreso en pantalla (sin máscara)}}+{{ :cursos:ensamblador:gfx3_sprite16x16_1.png?640 | Sprite 16x16 impreso en pantalla (sin máscara)}}
 \\  \\ 
  
Línea 1767: Línea 1844:
 ==== Impresión 16x16 usándo operaciones lógicas ==== ==== Impresión 16x16 usándo operaciones lógicas ====
  
- Podemos convertir la anterior rutina fácilmente en una rutina de impresión con operaciones lógicas añadiendo el **OR (HL)** (o el XOR) antes de la escritura del dato en pantalla.+ Podemos convertir la anterior rutina fácilmente en una rutina de impresión con operaciones lógicas añadiendo el ''or (hl)'' (o el ''XOR'') antes de la escritura del dato en pantalla.
  
  Simplemente, reemplazaríamos las diferentes operaciones de transferencia cambiando:  Simplemente, reemplazaríamos las diferentes operaciones de transferencia cambiando:
  
 <code z80> <code z80>
-   LD A, (DE)         ; Bloque 1: Leemos dato del sprite +    ld a, (de)         ; Bloque 1: Leemos dato del sprite 
-   LD (HL),         ; Copiamos dato a pantalla +    ld (hl),         ; Copiamos dato a pantalla 
-   INC DE             ; Incrementar puntero en sprite +    inc de             ; Incrementar puntero en sprite 
-   INC L              ; Incrementar puntero en pantalla+    inc l              ; Incrementar puntero en pantalla
 </code> </code>
  
Línea 1781: Línea 1858:
  
 <code z80> <code z80>
-   LD A, (DE)         ; Bloque 1: Leemos dato del sprite +    ld a, (de)         ; Bloque 1: Leemos dato del sprite 
-   OR (HL)            ; Realizamos operación lógica +    or (hl)            ; Realizamos operación lógica 
-   LD (HL),         ; Copiamos dato a pantalla +    ld (hl),         ; Copiamos dato a pantalla 
-   INC DE             ; Incrementar puntero en sprite +    inc de             ; Incrementar puntero en sprite 
-   INC L              ; Incrementar puntero en pantalla+    inc l              ; Incrementar puntero en pantalla
 </code> </code>
  
- 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 ''LD''.
  
  
Línea 1798: 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), ahora tendremos que calcularla como BASE+(DS_NUMSPR*64). Esta circunstancia nos ahorra un desplazamiento del pseudoregistro "AL" hacia la derecha (evitamos un **RRA** **RR L**), lo que nos permite hacer la operación de multiplicación con sólo 46 t-estados:+  * 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), ahora tendremos que calcularla como BASE+(DS_NUMSPR*64). Esta circunstancia nos ahorra un desplazamiento del pseudoregistro "AL" hacia la derecha (evitamos un ''RRA'' ''rr l''), lo que nos permite hacer la operación de multiplicación con sólo 46 t-estados:
  
 \\  \\ 
Línea 1810: Línea 1887:
 ; ;
 ; Entrada (paso por parametros en memoria): ; Entrada (paso por parametros en memoria):
-; Direccion   Parametro +; Direccion        Parametro 
-50000       Direccion de la tabla de Sprites +(DS_SPRITES)      Direccion de la tabla de Sprites 
-50002       Direccion de la tabla de Atribs  (0=no atributos) +(DS_ATTRIBS)      Direccion de la tabla de Atribs  (0=no atributos) 
-50004       Coordenada X en baja resolucion +(DS_COORD_X)      Coordenada X en baja resolucion 
-50005       Coordenada Y en baja resolucion +(DS_COORD_Y)      Coordenada Y en baja resolucion 
-50006       Numero de sprite a dibujar (0-N) +(DS_NUMSPR)       Numero de sprite a dibujar (0-N)
 ;------------------------------------------------------------- ;-------------------------------------------------------------
 DrawSprite_16x16_MASK: DrawSprite_16x16_MASK:
  
-   ; 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 
-   LD BC, (DS_COORD_X)+    ld bc, (DS_COORD_X)
  
-   ;;; Calculamos las coordenadas destino de pantalla en DE: +    ;;; Calculamos las coordenadas destino de pantalla en DE: 
-   LD AB +    ld ab 
-   AND $18 +    and $18 
-   ADD A, $40 +    add a, $40 
-   LD DA +    ld da 
-   LD AB +    ld ab 
-   AND +    and 
-   RRCA +    rrca 
-   RRCA +    rrca 
-   RRCA +    rrca 
-   ADD AC +    add ac 
-   LD EA+    ld ea
  
-   PUSH DE           ; Lo guardamos para luego, lo usaremos para +    push de           ; Lo guardamos para luego, lo usaremos para 
-                     ; calcular la direccion del atributo+                      ; calcular la direccion del atributo
  
-   ;;; Calcular posicion origen (array sprites) en HL como: +    ;;; Calcular posicion origen (array sprites) en HL como: 
-   ;;;     direccion = base_sprites + (NUM_SPRITE*64) +    ;;;     direccion = base_sprites + (NUM_SPRITE*64) 
-   ;;; Multiplicamos con desplazamientos, ver los comentarios. +    ;;; Multiplicamos con desplazamientos, ver los comentarios. 
-   LD BC, (DS_SPRITES) +    ld bc, (DS_SPRITES) 
-   LD A, (DS_NUMSPR) +    ld a, (DS_NUMSPR) 
-   LD L, 0           ; AL = DS_NUMSPR*256 +    ld l, 0           ; AL = DS_NUMSPR*256 
-   SRL A             ; Desplazamos a la derecha para dividir por dos +    srl a             ; Desplazamos a la derecha para dividir por dos 
-   RR L              ; AL = DS_NUMSPR*128 +    rr l              ; AL = DS_NUMSPR*128 
-   RRA               ; Rotamos, ya que el bit que salio de L al CF fue 0 +    rra               ; Rotamos, ya que el bit que salio de L al CF fue 0 
-   RR L              ; AL = DS_NUMSPR*64 +    rr l              ; AL = DS_NUMSPR*64 
-   LD H          ; HL = DS_NUMSPR*64 +    ld h          ; HL = DS_NUMSPR*64 
-   ADD HLBC        ; HL = BC + HL = DS_SPRITES + (DS_NUMSPR * 64) +    add hlbc        ; HL = BC + HL = DS_SPRITES + (DS_NUMSPR * 64) 
-                     ; HL contiene la direccion de inicio en el sprite+                      ; HL contiene la direccion de inicio en el sprite
  
-   EX DEHL         ; Intercambiamos DE y HL para las OP LOGICAS+    ex dehl         ; Intercambiamos DE y HL para las OP LOGICAS
  
-   ;;; Dibujar 8 scanlines (DE) -> (HL) + bajar scanline y avanzar en SPR +    ;;; Dibujar 8 scanlines (DE) -> (HL) + bajar scanline y avanzar en SPR 
-   LD B, 8+    ld b, 8
  
 drawspr16m_loop1: drawspr16m_loop1:
-   LD A, (DE)       ; Obtenemos un byte del sprite (el byte de mascara) +    ld a, (de)       ; Obtenemos un byte del sprite (el byte de mascara) 
-   AND (HL)         ; A = A AND (HL+    and (hl)         ; A = A and (hl
-   LD C         ; Nos guardamos el valor del AND +    ld c         ; Nos guardamos el valor del AND 
-   INC DE           ; Avanzamos al siguiente byte (el dato grafico) +    inc de           ; Avanzamos al siguiente byte (el dato grafico) 
-   LD A, (DE)       ; Obtenemos el byte grafico +    ld a, (de)       ; Obtenemos el byte grafico 
-   OR C             ; A = A OR C = A OR (MASK AND FONDO+    or c             ; A = A or c = A OR (MASK and fONDO
-   LD (HL),       ; Imprimimos el dato tras aplicar operaciones logicas +    ld (hl),       ; Imprimimos el dato tras aplicar operaciones logicas 
-   INC DE           ; Avanzamos al siguiente dato del sprite +    inc de           ; Avanzamos al siguiente dato del sprite 
-   INC L            ; Avanzamos al segundo bloque en pantalla+    inc l            ; Avanzamos al segundo bloque en pantalla
  
-   LD A, (DE)       ; Obtenemos un byte del sprite (el byte de mascara) +    ld a, (de)       ; Obtenemos un byte del sprite (el byte de mascara) 
-   AND (HL)         ; A = A AND (HL+    and (hl)         ; A = A and (hl
-   LD C         ; Nos guardamos el valor del AND +    ld c         ; Nos guardamos el valor del AND 
-   INC DE           ; Avanzamos al siguiente byte (el dato grafico) +    inc de           ; Avanzamos al siguiente byte (el dato grafico) 
-   LD A, (DE)       ; Obtenemos el byte grafico +    ld a, (de)       ; Obtenemos el byte grafico 
-   OR C             ; A = A OR C = A OR (MASK AND FONDO+    or c             ; A = A or c = A OR (MASK and fONDO
-   LD (HL),       ; Imprimimos el dato tras aplicar operaciones logicas +    ld (hl),       ; Imprimimos el dato tras aplicar operaciones logicas 
-   INC DE           ; Avanzamos al siguiente dato del sprite+    inc de           ; Avanzamos al siguiente dato del sprite
  
-   DEC L            ; Volvemos atras del valor que incrementamos +    dec l            ; Volvemos atras del valor que incrementamos 
-   INC H            ; Incrementamos puntero en pantalla (siguiente scanline) +    inc h            ; Incrementamos puntero en pantalla (siguiente scanline) 
-   DJNZ drawspr16m_loop1+    djnz 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 D, lo hizo en el bucle +    ;;;inc h           ; No hay que hacer inc h, lo hizo en el bucle 
-   LD AH +    ;;;ld ah         ; No hay que hacer esta prueba, sabemos que 
-   AND +    ;;;and           ; no hay salto (es un cambio de bloque) 
-   JR NZ, drawsp16m_nofix_abajop +    ;;;jr nz, drawsp16_nofix_abajop 
-   LD AL +    ld a, h 
-   ADD A, 32 +    and 7 
-   LD LA +    jr nz, drawsp16m_nofix_abajop 
-   JR C, drawsp16m_nofix_abajop +    ld al 
-   LD AH +    add a, 32 
-   SUB +    ld la 
-   LD HA+    jr c, drawsp16m_nofix_abajop 
 +    ld ah 
 +    sub 
 +    ld ha
 drawsp16m_nofix_abajop: drawsp16m_nofix_abajop:
  
-   ;;; Repetir 8 veces (segundos 2 bloques horizontales): +    ;;; Repetir 8 veces (segundos 2 bloques horizontales): 
-   LD B, 8+    ld b, 8
  
 drawspr16m_loop2: drawspr16m_loop2:
-   LD A, (DE)       ; Obtenemos un byte del sprite (el byte de mascara) +    ld a, (de)       ; Obtenemos un byte del sprite (el byte de mascara) 
-   AND (HL)         ; A = A AND (HL+    and (hl)         ; A = A and (hl
-   LD C         ; Nos guardamos el valor del AND +    ld c         ; Nos guardamos el valor del AND 
-   INC DE           ; Avanzamos al siguiente byte (el dato grafico) +    inc de           ; Avanzamos al siguiente byte (el dato grafico) 
-   LD A, (DE)       ; Obtenemos el byte grafico +    ld a, (de)       ; Obtenemos el byte grafico 
-   OR C             ; A = A OR C = A OR (MASK AND FONDO+    or c             ; A = A or c = A OR (MASK and fONDO
-   LD (HL),       ; Imprimimos el dato tras aplicar operaciones logicas +    ld (hl),       ; Imprimimos el dato tras aplicar operaciones logicas 
-   INC DE           ; Avanzamos al siguiente dato del sprite +    inc de           ; Avanzamos al siguiente dato del sprite 
-   INC L            ; Avanzamos al segundo bloque en pantalla+    inc l            ; Avanzamos al segundo bloque en pantalla
  
-   LD A, (DE)       ; Obtenemos un byte del sprite (el byte de mascara) +    ld a, (de)       ; Obtenemos un byte del sprite (el byte de mascara) 
-   AND (HL)         ; A = A AND (HL+    and (hl)         ; A = A and (hl
-   LD C         ; Nos guardamos el valor del AND +    ld c         ; Nos guardamos el valor del AND 
-   INC DE           ; Avanzamos al siguiente byte (el dato grafico) +    inc de           ; Avanzamos al siguiente byte (el dato grafico) 
-   LD A, (DE)       ; Obtenemos el byte grafico +    ld a, (de)       ; Obtenemos el byte grafico 
-   OR C             ; A = A OR C = A OR (MASK AND FONDO+    or c             ; A = A or c = A OR (MASK and fONDO
-   LD (HL),       ; Imprimimos el dato tras aplicar operaciones logicas +    ld (hl),       ; Imprimimos el dato tras aplicar operaciones logicas 
-   INC DE           ; Avanzamos al siguiente dato del sprite+    inc de           ; Avanzamos al siguiente dato del sprite
  
-   DEC L            ; Volvemos atras del valor que incrementamos +    dec l            ; Volvemos atras del valor que incrementamos 
-   INC H            ; Incrementamos puntero en pantalla (siguiente scanline) +    inc h            ; Incrementamos puntero en pantalla (siguiente scanline) 
-   DJNZ drawspr16m_loop2+    djnz drawspr16m_loop2
  
-   ;;; En este punto, los 16 scanlines del sprite estan dibujados.+    ;;; En este punto, los 16 scanlines del sprite estan dibujados.
  
-   POP BC             ; Recuperamos el offset del primer scanline+    pop bc             ; Recuperamos el offset del primer scanline
  
-   ;;; Considerar el dibujado de los atributos (Si DS_ATTRIBS=0 -> RET+    ;;; Considerar el dibujado de los atributos (Si DS_ATTRIBS=0 -> ret
-   LD HL, (DS_ATTRIBS)+    ld hl, (DS_ATTRIBS)
  
-   XOR A              ; A = 0 +    xor a              ; A = 0 
-   ADD A          ; A = 0 + H = H +    add a          ; A = 0 + H = H 
-   RET Z              ; Si H = 0, volver (no dibujar atributos)+    ret z              ; Si H = 0, volver (no dibujar atributos)
  
-   ;;; Calcular posicion destino en area de atributos en DE. +    ;;; Calcular posicion destino en area de atributos en DE. 
-   LD A           ; Codigo de Get_Attr_Offset_From_Image +    ld a           ; Codigo de Get_Attr_Offset_From_Image 
-   RRCA               ; Obtenemos dir de atributo a partir de +    rrca               ; Obtenemos dir de atributo a partir de 
-   RRCA               ; dir de zona de imagen. +    rrca               ; dir de zona de imagen. 
-   RRCA               ; Nos evita volver a obtener X e Y +    rrca               ; Nos evita volver a obtener X e Y 
-   AND 3              ; y hacer el calculo completo de la  +    and 3              ; y hacer el calculo completo de la 
-   OR $58             ; direccion en zona de atributos +    or $58             ; direccion en zona de atributos 
-   LD DA +    ld da 
-   LD E           ; DE tiene el offset del attr de HL+    ld e           ; DE tiene el offset del attr de HL
  
-   LD A, (DS_NUMSPR)  ; Cogemos el numero de sprite a dibujar +    ld a, (DS_NUMSPR)  ; Cogemos el numero de sprite a dibujar 
-   LD CA +    ld ca 
-   LD B, 0 +    ld b, 0 
-   ADD HLBC         ; HL = HL+DS_NUMSPR +    add hlbc         ; HL = HL+DS_NUMSPR 
-   ADD HLBC         ; HL = HL+DS_NUMSPR*2 +    add hlbc         ; HL = HL+DS_NUMSPR*2 
-   ADD HLBC         ; HL = HL+DS_NUMSPR*3 +    add hlbc         ; HL = HL+DS_NUMSPR*3 
-   ADD HLBC         ; HL = HL+HL=(DS_NUMSPR*4) = Origen de atributo+    add hlbc         ; HL = HL+HL=(DS_NUMSPR*4) = Origen de atributo
  
-   LDI +    ldi 
-   LDI                ; Imprimimos las 2 primeras filas de atributo+    ldi                ; Imprimimos las 2 primeras filas de atributo
  
-   ;;; Avance diferencial a la siguiente linea de atributos +    ;;; Avance diferencial a la siguiente linea de atributos 
-   LD A           ; A = L +    ld a           ; A = L 
-   ADD A, 30          ; Sumamos A = A + 30 mas los 2 INCs de LDI+    add a, 30          ; Sumamos A = A + 30 mas los 2 INCs de ldi
-   LD E           ; Guardamos en L (L = L+30 + 2 por LDI=L+32) +    ld e           ; Guardamos en L (L = L+30 + 2 por ldi=L+32) 
-   JR NC, drawsp16m_attrab_noinc +    jr nc, drawsp16m_attrab_noinc 
-   INC D+    inc d
 drawsp16m_attrab_noinc: drawsp16m_attrab_noinc:
-   LDI +    ldi 
-   LDI +    ldi 
-   RET                ; porque no necesitamos incrementar HL y DE +    ret                ; porque no necesitamos incrementar HL y DE
 </code> </code>
  
  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, en el código hay 2 de ellas repetidas 8 veces, por lo que en desenrollamiento añadiría 14 veces el código de impresión). Esto puede suponer un problema cuando los programas son grandes y nos acercamos a los límites de la memoria del Spectrum.+  * 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, en el código hay 2 de ellas repetidas 8 veces, por lo que en desenrollamiento añadiría 14 veces el código de impresión). Esto puede suponer un problema cuando los programas son grandes y nos acercamos a los límites de la memoria del Spectrum.
  
  Probemos la rutina con un spriteset de múltiples sprites de 2x2 bloques con su máscara:  Probemos la rutina con un spriteset de múltiples sprites de 2x2 bloques con su máscara:
Línea 1993: Línea 2073:
  
 bicho_gfx: bicho_gfx:
- DEFB 247,  8,127,128,251,  4,191, 64, 255,  0,255,  0,248,  7, 31,224 +    DEFB 247,  8,127,128,251,  4,191, 64, 255,  0,255,  0,248,  7, 31,224 
- DEFB 240, 15, 15, 80,240, 15, 15, 80, 240, 15, 15,240,248,  7, 31,224 +    DEFB 240, 15, 15, 80,240, 15, 15, 80, 240, 15, 15,240,248,  7, 31,224 
- DEFB 255,  0,255,  0,254,  1,191, 64, 255,  0,223, 32,253,  2,255, +    DEFB 255,  0,255,  0,254,  1,191, 64, 255,  0,223, 32,253,  2,255, 
- DEFB 151,104,233, 22,143,112,241, 14, 199, 56,227, 28,255,  0,255, +    DEFB 151,104,233, 22,143,112,241, 14, 199, 56,227, 28,255,  0,255, 
- DEFB 251,  4,191, 64,255,  0,255,  0, 248,  7, 31,224,240, 15, 15, 80 +    DEFB 251,  4,191, 64,255,  0,255,  0, 248,  7, 31,224,240, 15, 15, 80 
- DEFB 240, 15, 15, 80,240, 15, 15,240, 248,  7, 31,224,255,  0,255, +    DEFB 240, 15, 15, 80,240, 15, 15,240, 248,  7, 31,224,255,  0,255, 
- DEFB 255,  0,255,  0,254,  1,191, 64, 255,  0,255,  0,253,  2,223, 32 +    DEFB 255,  0,255,  0,254,  1,191, 64, 255,  0,255,  0,253,  2,223, 32 
- DEFB 247,  8,255,  0,241, 14,143,112, 248,  7,135,120,255,  0,255, +    DEFB 247,  8,255,  0,241, 14,143,112, 248,  7,135,120,255,  0,255, 
- DEFB 253,  2,223, 32,255,  0,255,  0, 252,  3, 15,240,248,  7,  7,168 +    DEFB 253,  2,223, 32,255,  0,255,  0, 252,  3, 15,240,248,  7,  7,168 
- DEFB 248,  7,  7,168,248,  7,  7,248, 252,  3, 15,240,255,  0,255, +    DEFB 248,  7,  7,168,248,  7,  7,248, 252,  3, 15,240,255,  0,255, 
- DEFB 255,  0,255,  0,254,  1,191, 64, 255,  0,255,  0,254,  1,191, 64 +    DEFB 255,  0,255,  0,254,  1,191, 64, 255,  0,255,  0,254,  1,191, 64 
- DEFB 255,  0,255,  0,253,  2, 31,224, 250,  5, 15,240,255,  0,255, +    DEFB 255,  0,255,  0,253,  2, 31,224, 250,  5, 15,240,255,  0,255, 
- DEFB 254,  1,239, 16,253,  2,223, 32, 255,  0,255,  0,252,  3, 15,240 +    DEFB 254,  1,239, 16,253,  2,223, 32, 255,  0,255,  0,252,  3, 15,240 
- DEFB 248,  7,  7,168,248,  7,  7,168, 248,  7,  7,248,252,  3, 15,240 +    DEFB 248,  7,  7,168,248,  7,  7,168, 248,  7,  7,248,252,  3, 15,240 
- DEFB 255,  0,255,  0,254,  1,191, 64, 255,  0,223, 32,253,  2,255, +    DEFB 255,  0,255,  0,254,  1,191, 64, 255,  0,223, 32,253,  2,255, 
- DEFB 151,104,233, 22,143,112,241, 14, 199, 56,227, 28,255,  0,255,  0+    DEFB 151,104,233, 22,143,112,241, 14, 199, 56,227, 28,255,  0,255,  0
  
 bicho_attrib: bicho_attrib:
- DEFB 70, 71, 67,  3, 70, 71, 67,  3, 70, 71,  3, 67, 70, 71,  3, 67+    DEFB 70, 71, 67,  3, 70, 71, 67,  3, 70, 71,  3, 67, 70, 71,  3, 67
 </code> </code>
  
Línea 2017: Línea 2097:
  
 \\  \\ 
-{{ :cursos:ensamblador:gfx3_spr16x16_mask.png | Sprite 16x16 con máscara sobre fondo alterno }}+{{ :cursos:ensamblador:gfx3_spr16x16_mask.png?640 | Sprite 16x16 con máscara sobre fondo alterno }}
 \\  \\ 
  
Línea 2045: 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 "bloque a bloque" (en baja resolución), donde todos los sprites (y el mapa de juego) se mueven en la rejilla de 32x24 bloques del Spectrum (juegos de puzzle, rogue-likes, juegos basados en mapas de bloques, etc). La impresión por transferencia directa de datos (sprite -> pantalla) es adecuada para juegos con movimiento "bloque a bloque" (en baja resolución), donde todos los sprites (y el mapa de juego) se mueven en la rejilla de 32x24 bloques del Spectrum (juegos de puzzle, rogue-likes, juegos basados en mapas de bloques, etc).
Línea 2062: 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 ''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 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 2077: 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 "inversa" del mismo sobre el sprite) y para poder ser borrados con otra operación 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 "inversa" del mismo sobre el sprite) y para poder ser borrados con otra operación ''XOR''.
  
 \\  \\ 
Línea 2110: 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.
  
- Incluso una opción mucho más recomendable sería crear tantas rutinas específicas como tamaños de sprites tengamos en nuestro juego y hacer una rutina "maestra" (un //wrapper//) que llame a una u otra según el tamaño del Sprite que estemos solicitando imprimir. Un par de comprobaciones sobre el ancho y el alto del Sprite y saltos condicionales con un CALL final a la rutina adecuada resultará muchísimo más óptimo que la rutina que vamos a examinar:+ Incluso una opción mucho más recomendable sería crear tantas rutinas específicas como tamaños de sprites tengamos en nuestro juego y hacer una rutina "maestra" (un //wrapper//) que llame a una u otra según el tamaño del Sprite que estemos solicitando imprimir. Un par de comprobaciones sobre el ancho y el alto del Sprite y saltos condicionales con un call final a la rutina adecuada resultará muchísimo más óptimo que la rutina que vamos a examinar:
  
 <code z80> <code z80>
Línea 2120: Línea 2200:
 ; Entrada (paso por parametros en memoria): ; Entrada (paso por parametros en memoria):
 ; Direccion   Parametro ; Direccion   Parametro
-50000       Direccion de la tabla de Sprites +Direccion        Parametro 
-50002       Direccion de la tabla de Atribs  (0=no atributos) +; (DS_SPRITES)      Direccion de la tabla de Sprites 
-50004       Coordenada X en baja resolucion +(DS_ATTRIBS)      Direccion de la tabla de Atribs  (0=no atributos) 
-50005       Coordenada Y en baja resolucion +(DS_COORD_X)      Coordenada X en baja resolucion 
-50006       Numero de sprite a dibujar (0-N)  +(DS_COORD_Y)      Coordenada Y en baja resolucion 
-50010       Ancho del sprite en caracteres +(DS_NUMSPR)       Numero de sprite a dibujar (0-N) 
-50011       Alto del sprite en caracteres +(DS_WIDTH)        Ancho del sprite 
 +(DS_HEIGHT)       Alto del sprite
 ;------------------------------------------------------------- ;-------------------------------------------------------------
 DrawSprite_MxN_LD: DrawSprite_MxN_LD:
-   
-   ;;; Calcular posicion origen (array sprites) en HL como: 
-   ;;;     direccion = base_sprites + (NUM_SPRITE*ANCHO*ALTO)  
  
-   ;;;; Multiplicamos ancho por alto (en bloques) +    ;;; Calcular posicion origen (array sprites) en HL como: 
-   LD A, (DS_WIDTH) +    ;;;     direccion = base_sprites + (NUM_SPRITE*ANCHO*ALTO)
-   LD C, A +
-   LD A, (DS_HEIGHT) +
-   RLCA               Multiplicamos por 8, necesitamos +
-   RLCA               la altura en pixeles (FILAS*8) +
-   RLCA               ; Y la guardamos porque la necesitaremos: +
-   LD (drawsp_height), A+
  
-   ;;; Multiplicamos Ancho_bloques * Alto_pixeles: +    ;;;; Multiplicamos ancho por alto (en bloques) 
-   LD BA +    ld a, (DS_WIDTH) 
-   XOR A              ; A = 0+    ld c, a 
 +    ld a, (DS_HEIGHT) 
 +    rlca               ; Multiplicamos por 8, necesitamos 
 +    rlca               ; la altura en pixeles (FILAS*8) 
 +    rlca               ; Y la guardamos porque la necesitaremos: 
 +    ld (drawsp_height),
 + 
 +    ;;; Multiplicamos Ancho_bloques * Alto_pixeles: 
 +    ld ba 
 +    xor a              ; A = 0
 drawsp_mul1: drawsp_mul1:
-   ADD A          ; A = A + C   (B veces)  = B*C +    add a          ; A = A + C   (B veces)  = B*C 
-   DJNZ drawsp_mul1   ; B veces -> A = A*C = Ancho * Alto +    djnz drawsp_mul1   ; B veces -> A = A*C = Ancho * Alto 
-                      ; 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) 
-   LD B           ; Repetimos Ancho * Alto veces +    ld b           ; Repetimos Ancho * Alto veces 
-   LD A(DS_NUMSPR) +    ld hl0 
-   LD E           ; DE DS_NUMSPR +    ld d           ; HL 0 
-   LD D0 +    ld a(DS_NUMSPR) 
-   LD H           ; HL +    ld e           ; DE DS_NUMSPR
-   LD L, H           +
 drawsp_mul2: drawsp_mul2:
-   ADD HLDE         ; HL = HL+DS_NUMSPR +    add hlde         ; HL = HL+DS_NUMSPR 
-   DJNZ drawsp_mul2   ; Sumamos HL+DE B veces = DS_NUMSPR*B+    djnz drawsp_mul2   ; Sumamos HL+DE B veces = DS_NUMSPR*B
  
-                      ; guardamos el valor de ancho*alto_pixeles*NUMSPR +                        ; guardamos el valor de ancho*alto_pixeles*NUMSPR 
-   LD (drawsp_width_by_height), HL+    ld (drawsp_width_by_height), hl
  
-   ;;; Calculamos direccion origen copia en el sprite +    ;;; Calculamos direccion origen copia en el sprite 
-   LD BC, (DS_SPRITES) +    ld bc, (DS_SPRITES) 
-   ADD HLBC         ; HL = BC + HL = DS_SPRITES + (DS_NUMSPR*ANCHO*ALTO) +    add hlbc         ; HL = BC + HL = DS_SPRITES + (DS_NUMSPR*ANCHO*ALTO) 
-                      ; 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:+
  
-   LD BC, (DS_COORD_X)     ; B = Y,  C = X +    ;;; Calculamos las coordenadas destino de pantalla en DE:
-   LD A, B +
-   AND $18 +
-   ADD A, $40 +
-   LD D, A +
-   LD A, B +
-   AND 7 +
-   RRCA +
-   RRCA +
-   RRCA +
-   ADD A, C +
-   LD E, A  +
-   PUSH DE            Lo guardamos para luego, lo usaremos para +
-                      calcular la direccion del atributo +
-   EX DE, HL          ; Intercambiamos DE y HL (DE=origen, HL=destino)+
  
 +    ld bc, (DS_COORD_X)     ; B = Y,  C = X
 +    ld a, b
 +    and $18
 +    add a, $40
 +    ld d, a
 +    ld a, b
 +    and 7
 +    rrca
 +    rrca
 +    rrca
 +    add a, c
 +    ld e, a
 +    push de            ; Lo guardamos para luego, lo usaremos para
 +                       ; calcular la direccion del atributo
 +    ex de, hl          ; Intercambiamos DE y HL (DE=origen, HL=destino)
  
-   ;;; Bucle de impresión vertical + 
-                      ; Recogemos de nuevo la altura en pixeles +    ;;; Bucle de impresión vertical 
-   LD A, (drawsp_height)   +                        ; Recogemos de nuevo la altura en pixeles 
-   LD B           ; Contador del bucle exterior del bucle+    ld a, (drawsp_height) 
 +    ld b           ; Contador del bucle exterior del bucle
  
 drawsp_yloop: drawsp_yloop:
-   LD C           ; Nos guardamos el contador+    ld c           ; Nos guardamos el contador
  
-   ;;; Bucle de impresion horizontal +    ;;; Bucle de impresion horizontal 
-   LD A, (DS_WIDTH) +    ld a, (DS_WIDTH) 
-   LD BA +    ld ba 
-  + 
-   PUSH HL            ; Guardamos en pila inicio de scanline +    push hl            ; Guardamos en pila inicio de scanline 
-                      ; para poder volver a el luego+                        ; para poder volver a el luego
 drawsp_xloop: drawsp_xloop:
-   LD A, (DE)         ; Leemos dato del sprite +    ld a, (de)         ; Leemos dato del sprite 
-   LD (HL),         ; Copiamos dato a pantalla +    ld (hl),         ; Copiamos dato a pantalla 
-   INC DE             ; Incrementar puntero en sprite +    inc de             ; Incrementar puntero en sprite 
-   INC L              ; Incrementar puntero en pantalla +    inc l              ; Incrementar puntero en pantalla 
-   DJNZ drawsp_xloop +    djnz drawsp_xloop 
-   POP HL             ; Recuperamos de pila inicio de scanline+    pop hl             ; Recuperamos de pila inicio de scanline
  
-   ;;; Avanzamos al siguiente scanline de pantalla +    ;;; Avanzamos al siguiente scanline de pantalla 
-   INC H +    inc h 
-   LD AH +    ld ah 
-   AND +    and 
-   JR NZ, drawspNM_nofix +    jr nz, drawspNM_nofix 
-   LD AL +    ld al 
-   ADD A, 32 +    add a, 32 
-   LD LA +    ld la 
-   JR C, drawspNM_nofix +    jr c, drawspNM_nofix 
-   LD AH +    ld ah 
-   SUB +    sub 
-   LD HA+    ld ha
 drawspNM_nofix: drawspNM_nofix:
  
-   LD BC +    ld bc 
-   DJNZ drawsp_yloop  ; Repetimos "alto_en_pixeles" veces+    djnz drawsp_yloop  ; Repetimos "alto_en_pixeles" veces
  
-   ;;; Aqui hemos dibujado todo el sprite, vamos a los attributos+    ;;; Aqui hemos dibujado todo el sprite, vamos a los attributos
  
-   POP BC             ; Recuperamos el offset del primer scanline+    pop bc             ; Recuperamos el offset del primer scanline
  
-   ;;; Considerar el dibujado de atributos (Si DS_ATTRIBS=0 -> RET+    ;;; Considerar el dibujado de atributos (Si DS_ATTRIBS=0 -> ret
-   LD HL(DS_ATTRIBS+    ld  a,[DS_ATTRIBS+1]     para obtener la parte alta de la direccion 
-    +    or  a                    para comprobar si es 
-   XOR A              A = 0 +    ret z                    ; Si H = 0, volver (no dibujar atributos)
-   ADD A, H           A = + H = H +
-   RET Z              ; Si H = 0, volver (no dibujar atributos+
-  +
-   ;;; Calcular posicion destino en area de atributos en DE. +
-   LD A, B            ; Codigo de Get_Attr_Offset_From_Image +
-   RRCA               ; Obtenemos dir de atributo a partir de +
-   RRCA               ; dir de zona de imagen. +
-   RRCA               ; Nos evita volver a obtener X e Y +
-   AND 3              ; y hacer el calculo completo de la  +
-   OR $58             ; direccion en zona de atributos +
-   LD D, A +
-   LD E, C            ; DE tiene el offset del attr de HL +
-   PUSH DE            ; Guardamos una copia +
-  +
-   ; Recuperamos el valor de ancho_caracteres * alto_en_pixeles * NUMSPR +
-   ; para ahorrarnos repetir otra vez dos multiplicaciones: +
-   LD HL, (drawsp_width_by_height)+
  
-   ;;; HL = ANCHO_BLOQUES*ALTO_PIXELES*NUMSPR +    ;;; Calcular posicion destino en area de atributos en DE. 
-   ;;; El Alto lo necesitamos en BLOQUES, no en píxeles-> dividir /8 +    ld a, b            ; Codigo de Get_Attr_Offset_From_Image 
-   SRL H     ; Desplazamos H a la derecha +    rrca               ; Obtenemos dir de atributo a partir de 
-   RR L      ; Rotamos L a la derecha introduciendo CF +    rrca               ; dir de zona de imagen. 
-   SRL H     ; +    rrca               ; Nos evita volver a obtener X e Y 
-   RR L      ; +    and 3              ; y hacer el calculo completo de la 
-   SRL H     ; +    or $58             ; direccion en zona de atributos 
-   RR L      ; Resultado : HL = HL >> 3 = HL / 8 +    ld d, a 
-    +    ld e, c            ; DE tiene el offset del attr de HL 
-   ;;;; HL = ANCHO_BLOQUES*ALTO_BLOQUES*NUMSPR +    push de            ; Guardamos una copia 
-   LD CL + 
-   LD BH +    ; Recuperamos el valor de ancho_caracteres * alto_en_pixeles * NUMSPR 
-   LD HL, (DS_ATTRIBS) +    ; para ahorrarnos repetir otra vez dos multiplicaciones: 
-   ADD HLBC         ; HL = Base_Atributos + (DS_NUMSPR*ALTO*ANCHO) +    ld hl, (drawsp_width_by_height) 
-  + 
-   POP DE             ; Recuperamos direccion destino+    ;;; HL = ANCHO_BLOQUES*ALTO_PIXELES*NUMSPR 
 +    ;;; El Alto lo necesitamos en BLOQUES, no en píxeles-> dividir /8 
 +    srl h     ; Desplazamos H a la derecha 
 +    rr l      ; Rotamos L a la derecha introduciendo CF 
 +    srl h     ; 
 +    rr l      ; 
 +    srl h     ; 
 +    rr l      ; Resultado : HL = HL >> 3 = HL / 8 
 + 
 +    ;;;; HL = ANCHO_BLOQUES*ALTO_BLOQUES*NUMSPR 
 +    ld cl 
 +    ld bh 
 +    ld hl, (DS_ATTRIBS) 
 +    add hlbc         ; HL = Base_Atributos + (DS_NUMSPR*ALTO*ANCHO) 
 + 
 +    pop de             ; Recuperamos direccion destino
  
-   LD A, (DS_HEIGHT) +    ld a, (DS_HEIGHT) 
-   LD BA+    ld ba
  
-   ;;; Bucle impresion vertical de atributos+    ;;; Bucle impresion vertical de atributos
 drawsp_attyloop: drawsp_attyloop:
-   LD CB+    ld cb
  
-   PUSH DE            ; Guardamos inicio de linea de atributos +    push de            ; Guardamos inicio de linea de atributos 
-   LD A, (DS_WIDTH) +    ld a, (DS_WIDTH) 
-   LD BA+    ld ba
  
-   ;;; Bucle impresion horizontal de atributos+    ;;; Bucle impresion horizontal de atributos
 drawsp_attxloop: drawsp_attxloop:
-   LD A, (HL)         ; Leer atributo del sprite +    ld a, (hl)         ; Leer atributo del sprite 
-   INC HL +    inc hl 
-   LD (DE),         ; Escribir atributo +    ld (de),         ; Escribir atributo 
-   INC E +    inc e 
-   DJNZ  drawsp_attxloop+    djnz  drawsp_attxloop
  
-   POP DE             ; Recuperamos inicio de linea de atributos+    pop de             ; Recuperamos inicio de linea de atributos
  
-   ;;; Avance diferencial a la siguiente linea de atributos +    ;;; Avance diferencial a la siguiente linea de atributos 
-   LD AE +    ld ae 
-   ADD A, 32 +    add a, 32 
-   LD EA +    ld ea 
-   JR NC, drawsp_attrab_noinc +    jr nc, drawsp_attrab_noinc 
-   INC D+    inc d
 drawsp_attrab_noinc: drawsp_attrab_noinc:
  
-   LD BC +    ld bc 
-   DJNZ drawsp_attyloop+    djnz drawsp_attyloop
  
-   RET+    ret
  
 drawsp_height          DB 0 drawsp_height          DB 0
Línea 2313: Línea 2391:
  
 <code z80> <code z80>
-  ORG 32768+    ORG 35000
  
-DS_SPRITES  EQU  50000 +    ld hl, bicho_gfx 
-DS_ATTRIBS  EQU  50002 +    ld (DS_SPRITES), hl 
-DS_COORD_X  EQU  50004 +    ld hl, bicho_attrib 
-DS_COORD_Y  EQU  50005 +    ld (DS_ATTRIBS), hl 
-DS_NUMSPR   EQU  50006 +    ld a, 13 
-DS_WIDTH    EQU  50010 +    ld (DS_COORD_X), a 
-DS_HEIGHT   EQU  50011+    ld a, 8 
 +    ld (DS_COORD_Y), a 
 +    ld a, 2 
 +    ld (DS_WIDTH), a 
 +    ld a, 2 
 +    ld (DS_HEIGHT), a 
 +    xor a 
 +    ld (DS_NUMSPR), a
  
-  LD HL, bicho_gfx +    call DrawSprite_MxN_LD 
-  LD (DS_SPRITES), HL +    ret
-  LD HL, bicho_attrib +
-  LD (DS_ATTRIBS), HL +
-  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), A+
  
-  CALL DrawSprite_MxN_LD +; Variables que usaremos como parámetros 
-  RET+DS_SPRITES  DEFW   0 
 +DS_ATTRIBS  DEFW   0 
 +DS_COORD_X  DEFB   0 
 +DS_COORD_Y  DEFB   0 
 +DS_NUMSPR   DEFB   0 
 +DS_WIDTH    DEFB   0 
 +DS_HEIGHT   DEFB   0
 </code> </code>
  
- 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 ''OR'' ''XOR'' contra (HL).
  
  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 2374: Línea 2453:
 <code> <code>
 El buffer al que me refiero puedes verlo como una segunda capa de atributos, El buffer al que me refiero puedes verlo como una segunda capa de atributos,
-que es la forma más fácil de implementar. Es una zona de memoria de +que es la forma más fácil de implementar. Es una zona de memoria de
 32x24 = 768 bytes (como el area de atributos) en la que se marcan los caracteres 32x24 = 768 bytes (como el area de atributos) en la que se marcan los caracteres
-ocupados en cada momento. +ocupados en cada momento.
  
 Algunas pistas para implementarlo: Algunas pistas para implementarlo:
  
 - 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
 que imprimir mezclando para preservar el fondo (p.e. con XOR). De esta forma tu que imprimir mezclando para preservar el fondo (p.e. con XOR). De esta forma tu
 rutina de impresión imprimirá siempre de la forma más rápida posible, excepto en rutina de impresión imprimirá siempre de la forma más rápida posible, excepto en
-los caracteres que se superpongan. +los caracteres que se superpongan.
  
 - con este método puedes borrar pintando atributos en lugar de volcar blancos con - con este método puedes borrar pintando atributos en lugar de volcar blancos con
Línea 2411: Línea 2490:
 **Borrado (e impresión) con XOR** **Borrado (e impresión) con XOR**
  
- Cuando un sprite ha sido dibujado con la operación XOR, podemos borrarlo realizando otra impresión en la misma posición mediante la misma operación. Esto es así porque la operación XOR es reversible.+ Cuando un sprite ha sido dibujado con la operación ''XOR'', podemos borrarlo realizando otra impresión en la misma posición mediante la misma operación. Esto es así porque la operación ''XOR'' es reversible.
  
- Veamos la tabla de verdad de la operación XOR:+ Veamos la tabla de verdad de la operación ''XOR'':
  
 +|< 30% >|
 ^ Bit Pantalla ^ Bit Sprite ^ XOR ^ ^ Bit Pantalla ^ Bit Sprite ^ XOR ^
-      +| 0 | 0 | 0 | 
-      +| 0 | 1 | 1 | 
-      +| 1 | 0 | 1 | 
-      |+| 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 ''XOR'':
  
 <code> <code>
 Impresión del sprite con XOR: Impresión del sprite con XOR:
 ----------------------------- -----------------------------
-Fondo:    01010101 +Fondo:    01010101
 Sprite:   00111000 Sprite:   00111000
 Tras XOR: 01101101 Tras XOR: 01101101
Línea 2438: Línea 2518:
 </code> </code>
  
- La impresión de un sprite con XOR produce una especie de "negativo" del Sprite en la pantalla allá donde haya píxeles de fondo activos, por lo que esto, unido a la facilidad de borrado, lo hace ideal para la impresión de cursores y punteros.+ La impresión de un sprite con ''XOR'' produce una especie de "negativo" del Sprite en la pantalla allá donde haya píxeles de fondo activos, por lo que esto, unido a la facilidad de borrado, lo hace ideal para la impresión de cursores y punteros.
  
  
Línea 2451: Línea 2531:
  Podríamos realizar una función específica para esta tarea (recuperar una porción de fondo, o lo que es lo mismo, crear un sprite tomando los datos de la pantalla); sería una modificación de las rutinas de impresión donde el origen sería la pantalla y el destino el array. No obstante, esto no es eficiente porque estaríamos realizando las operaciones de cálculo de posiciones 2 veces: una al salvaguardar el contenido del fondio y otra, más tarde, al llamar a la función de dibujado del Sprite.  Podríamos realizar una función específica para esta tarea (recuperar una porción de fondo, o lo que es lo mismo, crear un sprite tomando los datos de la pantalla); sería una modificación de las rutinas de impresión donde el origen sería la pantalla y el destino el array. No obstante, esto no es eficiente porque estaríamos realizando las operaciones de cálculo de posiciones 2 veces: una al salvaguardar el contenido del fondio y otra, más tarde, al llamar a la función de dibujado del Sprite.
  
- Para evitar esto, podemos adaptar las rutinas de impresión para que salvaguarden el fondo en nuestro array temporal antes de escribir los datos en pantalla. + Para evitar esto, podemos adaptar las rutinas de impresión para que salvaguarden el fondo en nuestro array temporal antes de escribir los datos en pantalla.
  
  Como tenemos ya utilizados DE y HL para la transferencia del Sprite, necesitamos un registro de 16 bits adicional. En este caso utilizaremos el registro IX para apuntar al array temporal.  Como tenemos ya utilizados DE y HL para la transferencia del Sprite, necesitamos un registro de 16 bits adicional. En este caso utilizaremos el registro IX para apuntar al array temporal.
Línea 2461: Línea 2541:
 ; DrawSprite_8x8_Restore: ; DrawSprite_8x8_Restore:
 ; Imprime un sprite de 8x8 pixeles salvaguardando el fondo ; Imprime un sprite de 8x8 pixeles salvaguardando el fondo
-; en la direccion de memoria indicada por (50008).+; en la direccion de memoria indicada por (DS_TEMPBUF).
 ; ;
 ; Entrada (paso por parametros en memoria): ; Entrada (paso por parametros en memoria):
-; Direccion   Parametro +; Direccion        Parametro 
-50000       Direccion de la tabla de Sprites +... 
-50002       Direccion de la tabla de Atribs  (0=no atributos) +; (DS_TEMPBUF     Direccion del array temporal para el fondo
-; 50004       Coordenada X en baja resolucion +
-; 50005       Coordenada Y en baja resolucion +
-; 50006       Numero de sprite a dibujar (0-N)  +
-; 50008       Direccion del array temporal para el fondo+
 ;------------------------------------------------------------- ;-------------------------------------------------------------
-DS_TEMPBUF EQU 50008 
  
-   ;;; Recogida de datos y parametros +    ;;; Recogida de datos y parametros 
-   (...) +    (...) 
-   LD IX, (DS_TEMPBUF) +    ld ix, (DS_TEMPBUF) 
-   (...) +    (...) 
-    + 
-   ;;; Bucle de impresion del Sprite: +    ;;; Bucle de impresion del Sprite: 
-   LD B, 8          ; 8 scanlines +    ld b, 8          ; 8 scanlines 
- +
 drawsp8x8_loopLD: drawsp8x8_loopLD:
-   LD A, (HL)       ; NUEVO: Leemos el valor actual del fondo +    ld a, (hl)       ; NUEVO: Leemos el valor actual del fondo 
-   LD (IX),       ; NUEVO: Lo almacenamos en el array temporal +    ld (ix),       ; NUEVO: Lo almacenamos en el array temporal 
-   INC IX           ; NUEVO: Incrementamos IX (puntero array fondo)+    inc ix           ; NUEVO: Incrementamos IX (puntero array fondo) 
 + 
 +                        ; Ya podemos imprimir el sprite 
 +    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) 
 +    djnz drawsp8x8_loopLD 
 + 
 +    (...) 
 +    ;;; Impresion de atributos 
 +    ld a, (de)       ; NUEVO: Leemos el atributo actual 
 +    ld (ix), a       ; NUEVO: Lo guardamos en el array temporal
  
-                    ; Ya podemos imprimir el sprite +    ld a, (hl)       ; Ya podemos imprimir el atributo 
-   LD A, (DE)       ; Tomamos el dato del sprite +    ld (de), a 
-   LD (HL), A       ; Establecemos el valor en videomemoria +    ret
-   INC DE           ; Incrementamos puntero en sprite +
-   INC H            ; Incrementamos puntero en pantalla (scanline+=1) +
-   DJNZ drawsp8x8_loopLD+
  
-   (...) +DS_TEMPBUF   DEFW  0
-   ;;; Impresion de atributos +
-   LD A, (DE)       ; NUEVO: Leemos el atributo actual +
-   LD (IX), A       ; NUEVO: Lo guardamos en el array temporal +
-    +
-   LD A, (HL)       ; Ya podemos imprimir el atributo +
-   LD (DE), A +
-   RET+
 </code> </code>
  
- El registro IX no es especialmente rápido (10 t-estados el **INC IX** y 19 t-estados el **LD (IX), A** **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 ''inc ix'' y 19 t-estados el ''ld (ix), a'' ''ld (ix+0), a'', pero es más rápido utilizarlo que escribir y ejecutar una rutina adicional para almacenar el fondo.
  
  Cuando necesitemos borrar el sprite, bastará con recuperar el fondo que había antes de imprimirlo, es decir, volcar el contenido del buffer temporal en la posición del sprite a borrar. Para realizar este volcado podemos llamar a una de las funciones de volcado de Sprites basada en transferencias ya que el buffer del fondo en memoria se corresponde con un spriteset de 1 sólo elemento (DS_NUMSPR=0), sin máscara. Las rutinas impresión de 8x8, 16x16 o genéricas basadas en LD servirían para recuperar el contenido del fondo usando como origen el buffer temporal donde lo hemos almacenado.  Cuando necesitemos borrar el sprite, bastará con recuperar el fondo que había antes de imprimirlo, es decir, volcar el contenido del buffer temporal en la posición del sprite a borrar. Para realizar este volcado podemos llamar a una de las funciones de volcado de Sprites basada en transferencias ya que el buffer del fondo en memoria se corresponde con un spriteset de 1 sólo elemento (DS_NUMSPR=0), sin máscara. Las rutinas impresión de 8x8, 16x16 o genéricas basadas en LD servirían para recuperar el contenido del fondo usando como origen el buffer temporal donde lo hemos almacenado.
Línea 2530: 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 (''DS_SPRITES'' ''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:
  
 <code z80> <code z80>
-   LD BC, (DS_SPRITES)+    ld bc, (DS_SPRITES)
 </code> </code>
  
Línea 2539: Línea 2616:
  
 <code z80> <code z80>
-   LD BC, Tabla_Sprites+    ld bc, Tabla_Sprites
 </code> </code>
  
- Establecer BC a un valor inmediato (**LD BC, NN**) tiene un coste de 10 t-estados, frente a los 20 t-estados que cuesta establecerlo desde una posición de memoria (**LD BC, (NN)**). De esta forma evitamos cargar desde memoria las direcciones de los tilesets y también tener que establecerlos al inicio del programa.+ Establecer BC a un valor inmediato (''ld bc, NN'') tiene un coste de 10 t-estados, frente a los 20 t-estados que cuesta establecerlo desde una posición de memoria (''ld bc, (NN)''). De esta forma evitamos cargar desde memoria las direcciones de los tilesets y también tener que establecerlos al inicio del programa.
  
  
Línea 2548: 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 ''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.
  
- Normalmente no deberíamos requerir de esta operación porque lo normal es estar sincronizado con las interrupciones y realizar el redibujado de los sprites en la ISR o al volver de la ISR de atención a la ULA. + Normalmente no deberíamos requerir de esta operación porque lo normal es estar sincronizado con las interrupciones y realizar el redibujado de los sprites en la ISR o al volver de la ISR de atención a la ULA.
  
- No obstante, si no estamos sincronizados con las interrupciones o si estamos usando IM1, es un dato interesante que nos puede evitar la ejecución de código ISR en medio de la ejecución de la rutina.+ No obstante, si no estamos sincronizados con las interrupciones o si estamos usando im1, es un dato interesante que nos puede evitar la ejecución de código ISR en medio de la ejecución de la rutina.
  
  Trataremos este tema con más detalle en el capítulo dedicado a //Animaciones//, en el apartado sobre //Evitar Flickering o Parpadeos//.  Trataremos este tema con más detalle en el capítulo dedicado a //Animaciones//, en el apartado sobre //Evitar Flickering o Parpadeos//.
Línea 2561: 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 (''POP'').
  
- Apuntando SP a nuestro sprite, un simple **POP DE** carga en E y D (en ese orden) los 2 bytes apuntados por SP e incrementa la dirección contenida en DE en 2 unidades, por lo que realizamos 2 operaciones de lectura y 2 incrementos (de 16 bits) en 1 byte de código y 10 t-estados, frente a los 7+7+6+6 = 26 t-estados que cuestan las operaciones LD+INC por separado. Eso implica leer en E y D dos bytes de datos del sprite o el byte de máscara y un byte de datos.+ Apuntando SP a nuestro sprite, un simple ''pop de'' carga en E y D (en ese orden) los 2 bytes apuntados por SP e incrementa la dirección contenida en DE en 2 unidades, por lo que realizamos 2 operaciones de lectura y 2 incrementos (de 16 bits) en 1 byte de código y 10 t-estados, frente a los 7+7+6+6 = 26 t-estados que cuestan las operaciones LD+INC por separado. Eso implica leer en E y D dos bytes de datos del sprite o el byte de máscara y un byte de datos.
  
- 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", "JP" "CALL" ejecutado con SP apuntando al Sprite provocaría la corrupción del mismo al introducirse en la pila la dirección de memoria del RET o RETI para la ISR.+ 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'' ''call'' (así como un ''PUSH''ejecutado con SP apuntando al Sprite provocaría la corrupción del mismo al introducirse en la pila la dirección de memoria del ''RET'' ''RETI'' para la ISR.
  
  Veamos el pseudocódigo de una rutina que use la pila de esta forma:  Veamos el pseudocódigo de una rutina que use la pila de esta forma:
  
 <code> <code>
-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 2582: Línea 2659:
  
 ; Recuperar puntero de pila ; Recuperar puntero de pila
-EI +ei 
-RET+ret
 </code> </code>
  
- A continuación vamos a ver una versión "reducida" (sin impresión de atributos, y con un bucle de 16 iteraciones sin desenrollar) de DrawSprite_16x16 que utiliza la pila para recuperar los datos del sprite mediante **POP DE**. Esto implica la lectura de 2 bytes en una sóla instrucción y el doble incremento de DE para apuntar a los 2 siguientes elementos. + A continuación vamos a ver una versión "reducida" (sin impresión de atributos, y con un bucle de 16 iteraciones sin desenrollar) de ''DrawSprite_16x16'' que utiliza la pila para recuperar los datos del sprite mediante ''pop de''. Esto implica la lectura de 2 bytes en una sóla instrucción y el doble incremento de DE para apuntar a los 2 siguientes elementos.
- +
- En la rutina guardaremos el valor de SP en una variable temporal, antes de modificarlo, para poder recuperarlo antes del RET. Una vez calculada la dirección de origen del sprite a dibujar, realizaremos la copia de SP en DS_TEMP_SP (variable de memoria) y cargaremos el valor de HL en SP:+
  
 + En la rutina guardaremos el valor de SP en una variable temporal, antes de modificarlo, para poder recuperarlo antes del ret. Una vez calculada la dirección de origen del sprite a dibujar, realizaremos la copia de SP en ''DS_TEMP_SP'' (variable de memoria) y cargaremos el valor de HL en SP:
  
 <code z80> <code z80>
 ;------------------------------------------------------------- ;-------------------------------------------------------------
 ; DrawSprite_16x16_LD_STACK_no_attr: ; DrawSprite_16x16_LD_STACK_no_attr:
-; Imprime un sprite de 16x16 pixeles sin atributos +; Imprime un sprite de 16x16 pixeles sin atributos
 ; usando la pila para obtener datos del sprite. ; usando la pila para obtener datos del sprite.
 ;------------------------------------------------------------- ;-------------------------------------------------------------
Línea 2601: Línea 2677:
  
 DrawSprite_16x16_LD_STACK_no_attr: DrawSprite_16x16_LD_STACK_no_attr:
-  
-   ;;; Deshabilitar interrupciones (vamos a cambiar SP). 
-   DI 
  
-   ; Guardamos en BC la pareja (x,y) -> B=COORD_Y y C=COORD_X +    ;;; Deshabilitar interrupciones (vamos a cambiar SP). 
-   LD BC, (DS_COORD_X) +    di 
-  + 
-   ;;; Calculamos las coordenadas destino de pantalla en DE: +    ; Guardamos en BC la pareja (x,y) -> B=COORD_Y y C=COORD_X 
-   LD AB +    ld bc, (DS_COORD_X) 
-   AND $18 + 
-   ADD A, $40 +    ;;; Calculamos las coordenadas destino de pantalla en DE: 
-   LD DA +    ld ab 
-   LD AB +    and $18 
-   AND +    add a, $40 
-   RRCA +    ld da 
-   RRCA +    ld ab 
-   RRCA +    and 
-   ADD AC +    rrca 
-   LD EA+    rrca 
 +    rrca 
 +    add ac 
 +    ld ea
  
     ;;; Guardamos el valor actual y correcto de SP     ;;; Guardamos el valor actual y correcto de SP
-   LD (DS_TEMP_SP), SP   +    ld (DS_TEMP_sp), sp
-  +
-   ;;; Calcular posicion origen (array sprites) en HL: +
-   LD BC, (DS_SPRITES) +
-   LD A, (DS_NUMSPR) +
-   LD L, 0           ; AL = DS_NUMSPR*256 +
-   SRL A             ; Desplazamos a la derecha para dividir por dos +
-   RR L              ; AL = DS_NUMSPR*128 +
-   RRA               ; Rotamos, ya que el bit que salio de L al CF fue 0 +
-   RR L              ; AL = DS_NUMSPR*64 +
-   RRA               ; Rotamos, ya que el bit que salio de L al CF fue 0 +
-   RR L              ; AL = DS_NUMSPR*32 +
-   LD H, A           ; HL = DS_NUMSPR*32 +
-   ADD HL, BC        ; HL = BC + HL = DS_SPRITES + (DS_NUMSPR * 32) +
-                     ; HL contiene la direccion de inicio en el sprite +
-                      +
-   ;;; Establecemos el valor de SP apuntando al sprite +
-   LD SP, HL+
  
-   EX DE, HL         ; Intercambiamos DE y HL (DE=origen, HL=destino)+    ;;; Calcular posicion origen (array sprites) en HL: 
 +    ld bc(DS_SPRITES) 
 +    ld a, (DS_NUMSPR) 
 +    ld l, 0           ; AL = DS_NUMSPR*256 
 +    srl a             ; Desplazamos a la derecha para dividir por dos 
 +    rr l              ; AL = DS_NUMSPR*128 
 +    rra               ; Rotamos, ya que el bit que salio de L al CF fue 0 
 +    rr l              ; AL = DS_NUMSPR*64 
 +    rra               ; Rotamos, ya que el bit que salio de L al CF fue 0 
 +    rr l              ; AL = DS_NUMSPR*32 
 +    ld h, a           ; HL = DS_NUMSPR*32 
 +    add hl, bc        ; HL = BC + HL = DS_SPRITES + (DS_NUMSPR * 32) 
 +                      ; HL contiene la direccion de inicio en el sprite 
 + 
 +    ;;; Establecemos el valor de SP apuntando al sprite 
 +    ld sp, hl 
 + 
 +    ex de, hl         ; Intercambiamos DE y HL (DE=origen, HL=destino) 
 + 
 +    ;;; Repetir 8 veces (primeros 2 bloques horizontales): 
 +    ld b, 16
  
-   ;;; Repetir 8 veces (primeros 2 bloques horizontales): 
-   LD B, 16 
-  
 drawsp16x16_stack_loop: drawsp16x16_stack_loop:
-   POP DE             ; Recuperamos 2 bytes del sprite +    pop de             ; Recuperamos 2 bytes del sprite 
-                      ; Y ademas no hace falta sumar 2 a DE +                        ; Y ademas no hace falta sumar 2 a DE 
-   LD (HL),         ; Ahora imprimimos los 2 bytes en pantalla +    ld (hl),         ; Ahora imprimimos los 2 bytes en pantalla 
-   INC L               +    inc l 
-   LD (HL), D +    ld (hl), d 
-   DEC L +    dec l 
-  + 
-   ; 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              ; Siguiente scanline            +    inc h              ; Siguiente scanline 
-   LD A           ; Ajustar tercio/bloque si necesario +    ld a           ; Ajustar tercio/bloque si necesario 
-   AND +    and 
-   JR NZ, drawsp16_nofix_abajop +    jr nz, drawsp16_nofix_abajop 
-   LD AL +    ld al 
-   ADD A, 32 +    add a, 32 
-   LD LA +    ld la 
-   JR C, drawsp16_nofix_abajop +    jr c, drawsp16_nofix_abajop 
-   LD AH +    ld ah 
-   SUB +    sub 
-   LD HA +    ld ha 
- +
 drawsp16_nofix_abajop: drawsp16_nofix_abajop:
-   DJNZ drawsp16x16_stack_loop +    djnz drawsp16x16_stack_loop
-  +
-   ;;; En este punto, los 16 scanlines del sprite estan dibujados. +
-  +
-   ;;; Recuperamos el valor de SP +
-   LD HL, (DS_TEMP_SP) +
-   LD SP, HL+
  
-   EI                  ; Habilitar de nuevo interrupciones +    ;;; En este punto, los 16 scanlines del sprite estan dibujados. 
-   RET+ 
 +    ;;; Recuperamos el valor de SP 
 +    ld hl, (DS_TEMP_SP) 
 +    ld sp, hl 
 + 
 +    ei                  ; Habilitar de nuevo interrupciones 
 +    ret
 </code> </code>
  
 + 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 //Scanline_Abajo_HL// entre ambos. El bucle convertido en dos quedaría de la siguiente forma:
 +
 +<code z80>
 +drawsp16x16_stack_loop1:
 +    pop de             ; Recuperamos 2 bytes del sprite
 +                       ; Y ademas no hace falta sumar 2 a DE
 +    ld (hl), e         ; Ahora imprimimos los 2 bytes en pantalla
 +    inc l              ; de la parte superior del Sprite
 +    ld (hl), d
 +    dec l
 +    inc h
 +    djnz drawsp16x16_stack_loop1
 +
 +    ; 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
 +    ; No hay que comprobar si (H & $07) == 0 porque sabemos que lo es
 +    ld a, l
 +    add a, 32
 +    ld l, a
 +    jr c, drawsp16_nofix_abajop
 +    ld a, h
 +    sub 8
 +    ld h, a
 +drawsp16_nofix_abajop:
 +
 +   ld b,8
 +drawsp16x16_stack_loop2:
 +    pop de             ; Recuperamos 2 bytes del sprite
 +                       ; Y ademas no hace falta sumar 2 a DE
 +    ld (hl), e         ; Ahora imprimimos los 2 bytes en pantalla
 +    inc l              ; de la parte inferior del Sprite
 +    ld (hl), d
 +    dec l
 +    inc h
 +    djnz drawsp16x16_stack_loop2
 +</code>
 +
 +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 directa, y no por ejemplo un ''OR'' o ''XOR'' del dato en la pantalla.
 +
 +\\ 
 +\\ 
 +**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:
 +
 +<code>
 +ABC
 +DEF
 +GHI
 +</code>
 +
 +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:
 +
 +<code>
 +Linea 0:  8_pixeles_linea_0_A    8_pixeles_linea_0_B     8_pixeles_linea_0_C
 +Linea 1:  8_pixeles_linea_1_A    8_pixeles_linea_1_B     8_pixeles_linea_1_C
 +...
 +Linea 8:  8_pixeles_linea_0_D    8_pixeles_linea_0_E     8_pixeles_linea_0_F
 +Linea 9:  8_pixeles_linea_1_D    8_pixeles_linea_1_E     8_pixeles_linea_1_F
 +...
 +Linea 22:  8_pixeles_linea_6_G    8_pixeles_linea_6_H     8_pixeles_linea_6_I
 +Linea 23:  8_pixeles_linea_7_G    8_pixeles_linea_7_H     8_pixeles_linea_7_I
 +</code>
 +
 +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
 +</code>
 +
 +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.
  
 \\  \\ 
Línea 2695: Línea 2861:
   * {{cursos:ensamblador:gfx3_sprite16x16.tap|Tap del ejemplo anterior}}   * {{cursos:ensamblador:gfx3_sprite16x16.tap|Tap del ejemplo anterior}}
   * {{cursos:ensamblador:gfx3_sprite16x16_mask.asm|Impresión de Sprite 16x16 con máscara}}   * {{cursos:ensamblador:gfx3_sprite16x16_mask.asm|Impresión de Sprite 16x16 con máscara}}
-  * {{cursos:ensamblador:gfx3_sprite16x16_mask.tap|Tap del ejemplo anterior}} +  * {{cursos:ensamblador:gfx3_sprite16x16_mask.tap|Tap del ejemplo anterior}}
 \\  \\ 
 ===== Enlaces ===== ===== Enlaces =====
Línea 2704: Línea 2870:
   * [[http://www.worldofspectrum.org/faq/reference/z80reference.htm|Z80 Reference de WOS]].   * [[http://www.worldofspectrum.org/faq/reference/z80reference.htm|Z80 Reference de WOS]].
   * [[http://www.speccy.org/trastero/cosas/Fichas/fichas.htm|Microfichas de CM de MicroHobby]].   * [[http://www.speccy.org/trastero/cosas/Fichas/fichas.htm|Microfichas de CM de MicroHobby]].
-  * [[http://www.arrakis.es/~ninsesabe/pasmo/|PASMO]]. 
- 
  
 \\  \\ 
 +**[ [[.:indice|⬉]] | [[.:gfx2_direccionamiento|⬅]] | [[.:gfx4_fuentes|➡]] ]**
  • cursos/ensamblador/gfx3_sprites_lowres.1290246541.txt.gz
  • Última modificación: 20-11-2010 09:49
  • por sromero