cursos:ensamblador:gfx1_vram

Diferencias

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

Enlace a la vista de comparación

Ambos lados, revisión anterior Revisión previa
Próxima revisión
Revisión previa
cursos:ensamblador:gfx1_vram [12-01-2024 16:32] sromerocursos:ensamblador:gfx1_vram [21-01-2024 18:43] (actual) – [Efectos sobre la imagen y los atributos] sromero
Línea 79: Línea 79:
 ===== La videomemoria del Spectrum ===== ===== La videomemoria del Spectrum =====
  
- Cuando comenzamos nuestro curso de ensamblador vimos la organización del mapa de memoria del Spectrum, con la ROM mapeada entre $0000 y $3FFFF, y los 16 o 48KB de memoria a continuación de la misma. A partir de la dirección de memoria $4000 y hasta $7FFF nos encontramos un área de memoria etiquetada como "videoram" o "videomemoria".+ Cuando comenzamos nuestro curso de ensamblador vimos la organización del mapa de memoria del Spectrum, con la ROM mapeada entre $0000 y $3fffF, y los 16 o 48KB de memoria a continuación de la misma. A partir de la dirección de memoria $4000 y hasta $7fff nos encontramos un área de memoria etiquetada como "videoram" o "videomemoria".
  
  Este área de aprox. 7 KB de memoria es donde podemos encontrar la representación digital de la imagen que estamos viendo en el monitor y que la ULA lee regularmente para poder generar la señal de vídeo que requiere el retrazar la imagen.  Este área de aprox. 7 KB de memoria es donde podemos encontrar la representación digital de la imagen que estamos viendo en el monitor y que la ULA lee regularmente para poder generar la señal de vídeo que requiere el retrazar la imagen.
Línea 133: Línea 133:
     ORG 50000     ORG 50000
  
-    LD HL, 18514       ; Scanline 0 en Y=96 +    ld hl, 18514           ; Scanline 0 en Y=96 
-    LD A, 60           ; 00111100b +    ld a, 60               ; 00111100b 
-    LD (HL), A         ; Escribir+    ld (hl), a             ; Escribir
  
-    LD HL, 18770       ; Scanline 1 en Y=97 +    ld hl, 18770           ; Scanline 1 en Y=97 
-    LD A, 66           ; 01000010b +    ld a, 66               ; 01000010b 
-    LD (HL), A         ; Escribir+    ld (hl), a             ; Escribir
  
-    LD HL, 19026       ; Scanline 2 en Y=98 +    ld hl, 19026           ; Scanline 2 en Y=98 
-    LD A, 66           ; 01000010b +    ld a, 66               ; 01000010b 
-    LD (HL), A         ; Escribir+    ld (hl), a             ; Escribir
  
-    LD HL, 19282       ; Scanline 3 en Y=99 +    ld hl, 19282           ; Scanline 3 en Y=99 
-    LD A, 126          ; 01111110b +    ld a, 126              ; 01111110b 
-    LD (HL), A         ; Escribir+    ld (hl), a             ; Escribir
  
-    LD HL, 19538       ; Scanline 4 en Y=100 +    ld hl, 19538           ; Scanline 4 en Y=100 
-    LD A, 66           ; 01000001b +    ld a, 66               ; 01000001b 
-    LD (HL), A         ; Escribir+    ld (hl), a             ; Escribir
  
-    LD HL, 19794       ; Scanline 5 en Y=101 +    ld hl, 19794           ; Scanline 5 en Y=101 
-    LD A, 66           ; 01000001b +    ld a, 66               ; 01000001b 
-    LD (HL), A         ; Escribir+    ld (hl), a             ; Escribir
  
-    LD HL, 20050       ; Scanline 6 en Y=102 +    ld hl, 20050           ; Scanline 6 en Y=102 
-    LD A66           01000001b +    ld a%01000001        También se puede usar binario 
-    LD (HL), A         ; Escribir+    ld (hl), a             ; Escribir
  
-    LD HL, 20306       ; Scanline 7 en Y=103 +    ld hl, 20306           ; Scanline 7 en Y=103 
-    LD A, 0            ; 00000000b +    ld a, 0                ; 00000000b 
-    LD (HL), A         ; Escribir+    ld (hl), a             ; Escribir
  
-    RET +    ret 
-END 50000+ 
 +    END 50000
 </code> </code>
  
Línea 190: Línea 191:
  
 \\  \\ 
-  * **El área de imagen**: Es el área de memoria que va desde $4000 (16384) hasta $57FF (22527). Este área de memoria de 6 KB almacena la información gráfica de 256x192 píxeles, donde cada byte (de 8 bits) define el estado de 8 píxeles (en cada bit del byte se tiene el estado de un pixel, con 1=activo, 0=no activo), de forma que se puede codificar cada línea de 256 pixeles con 256/8=32 bytes. Utilizando 32 bytes por línea, podemos almacenar el estado de una pantalla completa con 32*192 = 6144 bytes = 6 KB de memoria. Por ejemplo, la celdilla de memoria 16384 contiene el estado de los 8 primeros píxeles de la línea 0 de la pantalla, desde (0,0) a (7,0).+  * **El área de imagen**: Es el área de memoria que va desde $4000 (16384) hasta $57ff (22527). Este área de memoria de 6 KB almacena la información gráfica de 256x192 píxeles, donde cada byte (de 8 bits) define el estado de 8 píxeles (en cada bit del byte se tiene el estado de un pixel, con 1=activo, 0=no activo), de forma que se puede codificar cada línea de 256 pixeles con 256/8=32 bytes. Utilizando 32 bytes por línea, podemos almacenar el estado de una pantalla completa con 32*192 = 6144 bytes = 6 KB de memoria. Por ejemplo, la celdilla de memoria 16384 contiene el estado de los 8 primeros píxeles de la línea 0 de la pantalla, desde (0,0) a (7,0).
  
-  * **El área de atributos**: Es el área de memoria comprendida entre $5800 (22528) y $5AFF (23295). Cada uno de estos 768 bytes se denomina **atributo** y almacena los colores de pixel activo (tinta) y no activo (papel) de un bloque de 8x8 de la pantalla. Por ejemplo, la celdilla de memoria 22528 almacena el atributo de color del bloque (0,0) que se corresponde con los 64 píxeles desde las posiciones de pantalla (0,0) hasta (7,7).+  * **El área de atributos**: Es el área de memoria comprendida entre $5800 (22528) y $5aff (23295). Cada uno de estos 768 bytes se denomina **atributo** y almacena los colores de pixel activo (tinta) y no activo (papel) de un bloque de 8x8 de la pantalla. Por ejemplo, la celdilla de memoria 22528 almacena el atributo de color del bloque (0,0) que se corresponde con los 64 píxeles desde las posiciones de pantalla (0,0) hasta (7,7).
 \\  \\ 
  
Línea 224: Línea 225:
 <code basic> <code basic>
 10 BORDER 1: PAPER 1: INK 7: CLS 10 BORDER 1: PAPER 1: INK 7: CLS
-20 FOR = 10 TO 70 STEP 10 : CIRCLE 128, 96, R : NEXT R+20 FOR = 10 TO 70 STEP 10 : CIRCLE 128, 96, R : NEXT R
 30 PAUSE 0 30 PAUSE 0
 40 INK 2 : PLOT 30, 30 : DRAW 220, 120 40 INK 2 : PLOT 30, 30 : DRAW 220, 120
Línea 283: Línea 284:
 ===== Videomemoria: Área de Imagen ===== ===== Videomemoria: Área de Imagen =====
  
- El área de imagen del Spectrum es el bloque de 6144 bytes (6KB) entre 16384 ($4000) y 22527 ($57FF). Cada una de las posiciones de memoria de este área almacenan la información de imagen (estado de los píxeles) de 8 píxeles de pantalla consecutivos, donde un bit a 1 significa que el pixel está encendido y un valor de 0 que está apagado.+ El área de imagen del Spectrum es el bloque de 6144 bytes (6KB) entre 16384 ($4000) y 22527 ($57ff). Cada una de las posiciones de memoria de este área almacenan la información de imagen (estado de los píxeles) de 8 píxeles de pantalla consecutivos, donde un bit a 1 significa que el pixel está encendido y un valor de 0 que está apagado.
  
  Como veremos cuando hablemos del área de atributos, que los píxeles estén a ON o a OFF no implica que la ULA sólo dibuje los píxeles activos. Si el pixel está activo (bit a 1), la ULA lo traza en pantalla utilizando el color de tinta actual que corresponda a ese píxel mientras que un bit a 0 significa que el pixel no está encendido y que la ULA debe de dibujarlo con el color de papel actual.  Como veremos cuando hablemos del área de atributos, que los píxeles estén a ON o a OFF no implica que la ULA sólo dibuje los píxeles activos. Si el pixel está activo (bit a 1), la ULA lo traza en pantalla utilizando el color de tinta actual que corresponda a ese píxel mientras que un bit a 0 significa que el pixel no está encendido y que la ULA debe de dibujarlo con el color de papel actual.
Línea 333: Línea 334:
 <code basic> <code basic>
 10 CLS 10 CLS
-20 FOR I=0 TO 31 : POKE 16384+I, 170 : NEXT I+20 FOR i=0 TO 31 : POKE 16384+I, 170 : NEXT I
 30 PAUSE 0 30 PAUSE 0
 </code> </code>
Línea 355: Línea 356:
 <code basic> <code basic>
 10 CLS 10 CLS
-20 FOR I=0 TO 63 : POKE 16384+I, 170 : NEXT I+20 FOR i=0 TO 63 : POKE 16384+I, 170 : NEXT I
 30 PAUSE 0 30 PAUSE 0
 </code> </code>
Línea 386: Línea 387:
    * (etc...)    * (etc...)
  
- Así hasta el byte 18331 o $47FF (cuando hemos avanzado 32*8*8  = 32 bytes por 8 líneas de cada una de las 8 filas de caracteres), que contiene el estado de los 8 píxeles del bloque de baja resolución (31,7) de la pantalla.+ Así hasta el byte 18331 o $47ff (cuando hemos avanzado 32*8*8  = 32 bytes por 8 líneas de cada una de las 8 filas de caracteres), que contiene el estado de los 8 píxeles del bloque de baja resolución (31,7) de la pantalla.
  
 \\  \\ 
Línea 395: Línea 396:
 \\  \\ 
  
- ¿Qué quiere decir esto? Que los primeros 2KB de videoram (entre $4000 y $47FF) contienen la información de todos los píxeles de los 8 primeros bloques en baja resolución de pantalla, de forma que primero vienen todas las líneas horizontales 0 de cada bloque, luego todas las líneas horizontales 1 de cada bloque, líneas horizontales 2 de cada bloque, etc, hasta que se rellenan las últimas líneas horizontales (líneas 7) de los 8 primeros caracteres. Esto produce que el rellenado de los 2 primeros KB de la videoram rellene un área de pantalla entre (0,0) y (255,63), lo que se conoce como el **primer tercio** de la pantalla.+ ¿Qué quiere decir esto? Que los primeros 2KB de videoram (entre $4000 y $47ff) contienen la información de todos los píxeles de los 8 primeros bloques en baja resolución de pantalla, de forma que primero vienen todas las líneas horizontales 0 de cada bloque, luego todas las líneas horizontales 1 de cada bloque, líneas horizontales 2 de cada bloque, etc, hasta que se rellenan las últimas líneas horizontales (líneas 7) de los 8 primeros caracteres. Esto produce que el rellenado de los 2 primeros KB de la videoram rellene un área de pantalla entre (0,0) y (255,63), lo que se conoce como el **primer tercio** de la pantalla.
  
 \\  \\ 
-  * **Primer tercio**: Los 2 primeros KB de la videoram (de $4000 a $47FF) cubren los datos gráficos de los primeros 64 scanlines de la pantalla (líneas 0 a 7). +  * **Primer tercio**: Los 2 primeros KB de la videoram (de $4000 a $47ff) cubren los datos gráficos de los primeros 64 scanlines de la pantalla (líneas 0 a 7). 
-  * **Segundo tercio**: Los siguientes 2KB de la videoram (de $4800 a $4FFF) cubren los datos gráficos de los siguientes 64 scanlines de la pantalla (líneas 8 a 15). +  * **Segundo tercio**: Los siguientes 2KB de la videoram (de $4800 a $4fff) cubren los datos gráficos de los siguientes 64 scanlines de la pantalla (líneas 8 a 15). 
-  * **Tercer tercio**: Los siguientes 2KB de la videoram (de $5000 a $57FF) cubren los datos gráficos de los últimos 64 scanlines de la pantalla (líneas 16 a 23).+  * **Tercer tercio**: Los siguientes 2KB de la videoram (de $5000 a $57ff) cubren los datos gráficos de los últimos 64 scanlines de la pantalla (líneas 16 a 23).
 \\  \\ 
  
Línea 418: Línea 419:
  Con el programa de ejemplo del apartado //Explorando el área de imagen con un ejemplo// podremos comprobar experimentalmente la organización de la videomemoria y la división de la pantalla en tercios de 8 "caracteres" de 8 scanlines cada uno.  Con el programa de ejemplo del apartado //Explorando el área de imagen con un ejemplo// podremos comprobar experimentalmente la organización de la videomemoria y la división de la pantalla en tercios de 8 "caracteres" de 8 scanlines cada uno.
  
- Mientras tanto, sabiendo que entre $4000 y $57FF (6144 bytes) tenemos el área de imagen de la pantalla, donde cada byte representa el estado de 8 píxeles, podemos realizar la siguiente rutina útil que sirve para rellenar toda la pantalla con un patrón de píxeles determinado (CLS con patrón):+ Mientras tanto, sabiendo que entre $4000 y $57ff (6144 bytes) tenemos el área de imagen de la pantalla, donde cada byte representa el estado de 8 píxeles, podemos realizar la siguiente rutina útil que sirve para rellenar toda la pantalla con un patrón de píxeles determinado (CLS con patrón):
  
 <code z80> <code z80>
Línea 426: Línea 427:
 ;------------------------------------------------------- ;-------------------------------------------------------
 ClearScreen: ClearScreen:
-    PUSH HL +    push hl 
-    PUSH DE +    push de 
-    PUSH BC+    push bc
  
-    LD HL, 16384          ; HL = Inicio de la videoram +    ld hl, 16384          ; HL = Inicio de la videoram 
-    LD (HL),            ; Escribimos el patron A en (HL) +    ld (hl),            ; Escribimos el patron A en (HL) 
-    LD DE, 16385          ; Apuntamos DE a 16385 +    ld de, 16385          ; Apuntamos DE a 16385 
-    LD BC, 192*32-1       ; Copiamos 192*32-1 veces (HL) en (DE) +    ld bc, 192*32-1       ; Copiamos 192*32-1 veces (HL) en (DE) 
-    LDIR                  ; e incrementamos HL y DL. Restamos 1+    ldir                  ; e incrementamos HL y DL. Restamos 1
                           ; porque ya hemos escrito en 16384.                           ; porque ya hemos escrito en 16384.
  
-    POP BC +    pop bc 
-    POP DE +    pop de 
-    POP HL +    pop hl 
-    RET+    ret
 </code> </code>
  
Línea 466: Línea 467:
   ; Mostrando la organizacion de la videomemoria   ; Mostrando la organizacion de la videomemoria
  
-  ORG 50000+    ORG 50000 
   ; Pseudocodigo del programa:   ; Pseudocodigo del programa:
   ;   ;
Línea 478: Línea 480:
  
 Start: Start:
-    LD A, 0 +    ld a, 0 
-    CALL ClearScreen           ; Borramos la pantalla+    call ClearScreen           ; Borramos la pantalla
  
-    LD HL, 16384               ; HL apunta a la VRAM +    ld hl, 16384               ; HL apunta a la VRAM 
-    LD B, 192                  ; Repetimos para 192 lineas+    ld b, 192                  ; Repetimos para 192 lineas
  
 bucle_192_lineas: bucle_192_lineas:
-    LD D                   ; Nos guardamos el valor de D para el+    ld d                   ; Nos guardamos el valor de D para el
                                ; bucle exterior (usaremos B ahora en otro)                                ; bucle exterior (usaremos B ahora en otro)
-    LD B, 32                   ; B=32 para el bucle interior+    ld b, 32                   ; B=32 para el bucle interior
  
-    CALL Wait_For_Key          ; Esperamos que se pulse y libere tecla+    call Wait_For_Key          ; Esperamos que se pulse y libere tecla
  
-    LD A, 255                  ; 255 = 11111111b = todos los pixeles+    ld a, 255                  ; 255 = 11111111b = todos los pixeles
  
 bucle_32_bytes: bucle_32_bytes:
-    LD (HL),                 ; Almacenamos A en (HL) = 8 pixeles +    ld (hl),                 ; Almacenamos A en (HL) = 8 pixeles 
-    INC HL                     ; siguiente byte (siguientes 8 pix.) +    inc hl                     ; siguiente byte (siguientes 8 pix.) 
-    DJNZ bucle_32_bytes        ; 32 veces = 32 bytes = 1 scanline+    djnz bucle_32_bytes        ; 32 veces = 32 bytes = 1 scanline
  
-    LD B                   ; Recuperamos el B del bucle exterior+    ld b                   ; Recuperamos el B del bucle exterior
  
-    DJNZ bucle_192_lineas      ; Repetir 192 veces+    djnz bucle_192_lineas      ; Repetir 192 veces
  
-    JP Start                   ; Inicio del programa+    jp Start                   ; Inicio del programa
  
 ;----------------------------------------------------------------------- ;-----------------------------------------------------------------------
Línea 509: Línea 511:
 ;----------------------------------------------------------------------- ;-----------------------------------------------------------------------
 ClearScreen: ClearScreen:
-    LD HL, 16384 +    ld hl, 16384 
-    LD (HL), A +    ld (hl), a 
-    LD DE, 16385 +    ld de, 16385 
-    LD BC, 192*32-1 +    ld bc, 192*32-1 
-    LDIR +    ldir 
-    RET+    ret
  
-INCLUDE "utils.asm"+    INCLUDE "utils.asm"
  
-END 50000+    END 50000
 </code> </code>
  
Línea 564: Línea 566:
 | 7 | $4700 | | 7 | $4700 |
  
- Tal y como está organizada la videoram, basta con calcular la dirección de inicio del bloque en baja resolución donde queremos trazar un carácter, imprimir los 8 píxeles que forman su scanline (con la escritura de un único byte en videomemoria), y saltar a la siguiente posición de videomemoria donde escribir. Como se puede apreciar en la tabla anterior, este salto a la siguiente línea se realiza con un simple INC del byte alto de la direccion (''INC H'' en el caso de que estemos usando HL para escribir). De esta forma se simplifican las rutinas de trazado de caracteres y UDGs de la ROM.+ Tal y como está organizada la videoram, basta con calcular la dirección de inicio del bloque en baja resolución donde queremos trazar un carácter, imprimir los 8 píxeles que forman su scanline (con la escritura de un único byte en videomemoria), y saltar a la siguiente posición de videomemoria donde escribir. Como se puede apreciar en la tabla anterior, este salto a la siguiente línea se realiza con un simple INC del byte alto de la direccion (''inc h'' en el caso de que estemos usando HL para escribir). De esta forma se simplifican las rutinas de trazado de caracteres y UDGs de la ROM.
  
  Pensemos que los antecesores del ZX Spectrum (ZX80 y ZX81) tenían una videomemoria orientada al texto en baja resolución, y con la visión del software de la época y la potencia de los microprocesadores existentes lo normal era pensar en el Spectrum como un microordenador orientado a programar en BASIC y realizar programas "de gestión", más que pensar en él como una máquina de juegos. En este contexto, potenciar la velocidad de ejecución del trazado de texto era crucial.  Pensemos que los antecesores del ZX Spectrum (ZX80 y ZX81) tenían una videomemoria orientada al texto en baja resolución, y con la visión del software de la época y la potencia de los microprocesadores existentes lo normal era pensar en el Spectrum como un microordenador orientado a programar en BASIC y realizar programas "de gestión", más que pensar en él como una máquina de juegos. En este contexto, potenciar la velocidad de ejecución del trazado de texto era crucial.
Línea 572: Línea 574:
 ===== Videomemoria: Área de atributos ===== ===== Videomemoria: Área de atributos =====
  
- El área de atributos es el bloque de 768 bytes entre $5800 (22528) y $5AFF (23295), ambas celdillas de memoria incluídas. Cada una de las posiciones de memoria de este área almacenan la información de color (color de tinta, color de papel, brillo y flash) de un bloque de 8x8 píxeles en la pantalla.+ El área de atributos es el bloque de 768 bytes entre $5800 (22528) y $5aff (23295), ambas celdillas de memoria incluídas. Cada una de las posiciones de memoria de este área almacenan la información de color (color de tinta, color de papel, brillo y flash) de un bloque de 8x8 píxeles en la pantalla.
  
  El tamaño de 768 bytes de este área viene determinado por la resolución del sistema de color del Spectrum: Hemos dicho que el sistema gráfico dispone de una resolución de 256x192, pero el sistema de color divide la pantalla en bloques de 8x8 píxeles, lo que nos da una resolución de color de 256/8 x 192/8 = 32x24 bloques. Como la información de color de cada bloque se codifica en un único byte, para almacenar la información de color de toda una pantalla se requieren 32 x 24 x 1 = 768 bytes.  El tamaño de 768 bytes de este área viene determinado por la resolución del sistema de color del Spectrum: Hemos dicho que el sistema gráfico dispone de una resolución de 256x192, pero el sistema de color divide la pantalla en bloques de 8x8 píxeles, lo que nos da una resolución de color de 256/8 x 192/8 = 32x24 bloques. Como la información de color de cada bloque se codifica en un único byte, para almacenar la información de color de toda una pantalla se requieren 32 x 24 x 1 = 768 bytes.
Línea 587: Línea 589:
 \\  \\ 
  
- Se puede decir que el área de atributos es totalmente lineal; consta de 768 bytes que se corresponden de forma consecutiva con el estado de cada bloque y de cada fila horizontal de bloques de pantalla: Los primeros 32 bytes del área se corresponden con la primera fila horizontal de bloques, los siguientes 32 bytes con la segunda, los siguientes 32 bytes con la tercera, hasta los últimos 32 bytes, que se corresponden con los de la línea 23. El byte alojado en la última posición ($5AFF) se corresponde con el atributo del bloque (31,23).+ Se puede decir que el área de atributos es totalmente lineal; consta de 768 bytes que se corresponden de forma consecutiva con el estado de cada bloque y de cada fila horizontal de bloques de pantalla: Los primeros 32 bytes del área se corresponden con la primera fila horizontal de bloques, los siguientes 32 bytes con la segunda, los siguientes 32 bytes con la tercera, hasta los últimos 32 bytes, que se corresponden con los de la línea 23. El byte alojado en la última posición ($5aff) se corresponde con el atributo del bloque (31,23).
  
  A continuación podemos ver una tabla que muestra los inicios y fin de cada línea de atributos en pantalla:  A continuación podemos ver una tabla que muestra los inicios y fin de cada línea de atributos en pantalla:
Línea 593: Línea 595:
 |< 50% >| |< 50% >|
 ^ Línea ^ Inicio (carácter 0,N) ^ Fin (carácter 31,N) ^ ^ Línea ^ Inicio (carácter 0,N) ^ Fin (carácter 31,N) ^
-| 0 | $5800 | $581F +| 0 | $5800 | $581f 
-| 1 | $5820 | $583F +| 1 | $5820 | $583f 
-| 2 | $5860 | $585F +| 2 | $5860 | $585f 
-| 3 | $5840 | $587F +| 3 | $5840 | $587f 
-| 4 | $5880 | $589F +| 4 | $5880 | $589f 
-| 5 | $58A0 | $58BF +| 5 | $58a0 | $58bf 
-| 6 | $58C0 | $58DF +| 6 | $58c0 | $58df 
-| 7 | $58E0 | $58FF +| 7 | $58e0 | $58ff 
-| 8 | $5900 | $591F +| 8 | $5900 | $591f 
-| 9 | $5920 | $593F +| 9 | $5920 | $593f 
-| 10 | $5940 | $595F +| 10 | $5940 | $595f 
-| 11 | $5960 | $597F +| 11 | $5960 | $597f 
-| 12 | $5980 | $599F +| 12 | $5980 | $599f 
-| 13 | $59A0 | $59BF +| 13 | $59a0 | $59bf 
-| 14 | $59C0 | $59DF +| 14 | $59c0 | $59df 
-| 15 | $59E0 | $59FF +| 15 | $59e0 | $59ff 
-| 16 | $5A00 | $5A1F +| 16 | $5a00 | $5a1f 
-| 17 | $5A20 | $5A3F +| 17 | $5a20 | $5a3f 
-| 18 | $5A40 | $5A5F +| 18 | $5a40 | $5a5f 
-| 19 | $5A60 | $5A7F +| 19 | $5a60 | $5a7f 
-| 20 | $5A80 | $5A9F +| 20 | $5a80 | $5a9f 
-| 21 | $5AA0 | $5ABF +| 21 | $5aa0 | $5abf 
-| 22 | $5AC0 | $5ADF +| 22 | $5ac0 | $5adf 
-| 23 | $5AE0 | $5AFF |+| 23 | $5ae0 | $5aff |
  
  Esta organización del área de atributos es muy sencilla y permite un cálculo muy sencillo de la posición de memoria del atributo de un bloque concreto de pantalla. Es decir, podemos encontrar fácilmente la posición de memoria que almacena el atributo que corresponde a un bloque concreto en baja resolución de pantalla mediante:  Esta organización del área de atributos es muy sencilla y permite un cálculo muy sencillo de la posición de memoria del atributo de un bloque concreto de pantalla. Es decir, podemos encontrar fácilmente la posición de memoria que almacena el atributo que corresponde a un bloque concreto en baja resolución de pantalla mediante:
Línea 720: Línea 722:
  
 <code z80> <code z80>
-  ; Mostrando la organizacion de la videomemoria (atributos) +; Mostrando la organizacion de la videomemoria (atributos) 
-  ORG 50000+    ORG 50000
  
   ; Pseudocodigo del programa:   ; Pseudocodigo del programa:
Línea 734: Línea 736:
  
 Start: Start:
-    LD A, 0 +    ld a, 0 
-    CALL ClearScreen           ; Borramos la pantalla+    call ClearScreen           ; Borramos la pantalla
  
-    LD HL, 22528               ; HL apunta a la VRAM +    ld hl, 22528               ; HL apunta a la VRAM 
-    LD B, 24                   ; Repetimos para 192 lineas+    ld b, 24                   ; Repetimos para 192 lineas
  
 bucle_lineas: bucle_lineas:
-    LD D                   ; Nos guardamos el valor de D para el+    ld d                   ; Nos guardamos el valor de D para el
                                ; bucle exterior (usaremos B ahora en otro)                                ; bucle exterior (usaremos B ahora en otro)
-    LD B, 32                   ; B=32 para el bucle interior+    ld b, 32                   ; B=32 para el bucle interior
  
-    CALL Wait_For_Key          ; Esperamos que se pulse y libere tecla+    call Wait_For_Key          ; Esperamos que se pulse y libere tecla
  
-    LD A, (papel)              ; Cogemos el valor del papel +    ld a, (papel)              ; Cogemos el valor del papel 
-    INC A                      ; Lo incrementamos +    inc a                      ; Lo incrementamos 
-    LD (papel),              ; Lo guardamos de nuevo +    ld (papel),              ; Lo guardamos de nuevo 
-    CP 8                       ; Si es == 8 (>7), resetear +    cp 8                       ; Si es == 8 (>7), resetear 
-    JR NZ,no_resetear_papel+    jr nz,no_resetear_papel
  
-    LD A, 255 +    ld a, 255 
-    LD (papel),              ; Lo hemos reseteado: lo guardamos +    ld (papel),              ; Lo hemos reseteado: lo guardamos 
-    XOR A                      ; A=0+    xor a                      ; A=0
  
 no_resetear_papel: no_resetear_papel:
  
-    SLA A                      ; Desplazamos A 3 veces a la izquierda +    sla a                      ; Desplazamos A 3 veces a la izquierda 
-    SLA A                      ; para colocar el valor 0-7 en los bits +    sla a                      ; para colocar el valor 0-7 en los bits 
-    SLA A                      ; donde se debe ubicar PAPER (bits 3-5).+    sla a                      ; donde se debe ubicar PAPER (bits 3-5).
  
 bucle_32_bytes: bucle_32_bytes:
-    LD (HL),                 ; Almacenamos A en (HL) = attrib de 8x8 +    ld (hl),                 ; Almacenamos A en (HL) = attrib de 8x8 
-    INC HL                     ; siguiente byte (siguientes 8x8 pixeles.) +    inc hl                     ; siguiente byte (siguientes 8x8 pixeles.) 
-    DJNZ bucle_32_bytes        ; 32 veces = 32 bytes = 1 scanline de bloques+    djnz bucle_32_bytes        ; 32 veces = 32 bytes = 1 scanline de bloques
  
-    LD B                   ; Recuperamos el B del bucle exterior+    ld b                   ; Recuperamos el B del bucle exterior
  
-    DJNZ bucle_lineas          ; Repetir 24 veces+    djnz bucle_lineas          ; Repetir 24 veces
  
-    JP Start                   ; Inicio del programa+    jp Start                   ; Inicio del programa
  
 papel  DEFB   255              ; Valor del papel papel  DEFB   255              ; Valor del papel
- 
  
 ;----------------------------------------------------------------------- ;-----------------------------------------------------------------------
Línea 782: Línea 783:
 ;----------------------------------------------------------------------- ;-----------------------------------------------------------------------
 ClearScreen: ClearScreen:
-    LD HL, 16384 +    ld hl, 16384 
-    LD (HL), A +    ld (hl), a 
-    LD DE, 16385 +    ld de, 16385 
-    LD BC, 192*32-1 +    ld bc, 192*32-1 
-    LDIR +    ldir 
-    RET+    ret
  
-INCLUDE "utils.asm"+    INCLUDE "utils.asm"
  
-END 50000+    END 50000
 </code> </code>
  
Línea 815: Línea 816:
 ;------------------------------------------------------- ;-------------------------------------------------------
 ClearAttributes: ClearAttributes:
-    PUSH HL +    push hl 
-    PUSH DE +    push de 
-    PUSH BC+    push bc
  
-    LD HL, 22528          ; HL = Inicio del area de atributos +    ld hl, 22528          ; HL = Inicio del area de atributos 
-    LD (HL),            ; Escribimos el patron A en (HL) +    ld (hl),            ; Escribimos el patron A en (HL) 
-    LD DE, 22529          ; Apuntamos DE a 22528 +    ld de, 22529          ; Apuntamos DE a 22528 
-    LD BC, 24*32-1        ; Copiamos 767 veces (HL) en (DE) +    ld bc, 24*32-1        ; Copiamos 767 veces (HL) en (DE) 
-    LDIR                  ; e incrementamos HL y DL. Restamos 1+    ldir                  ; e incrementamos HL y DL. Restamos 1
                           ; porque ya hemos escrito en 22528.                           ; porque ya hemos escrito en 22528.
-    POP BC +    pop bc 
-    POP DE +    pop de 
-    POP HL +    pop hl 
-    RET+    ret
 </code> </code>
  
Línea 839: Línea 840:
 ;----------------------------------------------------------------------- ;-----------------------------------------------------------------------
 ClearScreenAttrib: ClearScreenAttrib:
-    PUSH DE +    push de 
-    PUSH BC +    push bc 
-    PUSH BC+    push bc
  
-    LD A              ; A = el atributo +    ld a              ; A = el atributo 
-    EX AFAF             ; Nos guardamos el atributo en A' +    ex afaf             ; Nos guardamos el atributo en A' 
-    LD A              ; Cargamos en A el patron +    ld a              ; Cargamos en A el patron 
-    LD HL, 16384          ; HL = Inicio del area de imagen +    ld hl, 16384          ; HL = Inicio del area de imagen 
-    LD (HL),            ; Escribimos el valor de A en (HL) +    ld (hl),            ; Escribimos el valor de A en (HL) 
-    LD DE, 16385          ; Apuntamos DE a 16385 +    ld de, 16385          ; Apuntamos DE a 16385 
-    LD BC, 192*32-1       ; Copiamos 6142 veces (HL) en (DE) +    ld bc, 192*32-1       ; Copiamos 6142 veces (HL) en (DE) 
-    LDIR+    ldir
  
-    EX AFAF             ; Recuperamos A (atributo) de A' +    ex afaf             ; Recuperamos A (atributo) de A' 
-    INC HL                ; Incrementamos HL y DE +    inc hl                ; Incrementamos HL y DE 
-    INC DE                ; para entrar en area de atributos +    inc de                ; para entrar en area de atributos 
-    LD (HL),            ; Almacenamos el atributo +    ld (hl),            ; Almacenamos el atributo 
-    LD BC, 24*32-1        ; Ahora copiamos 767 bytes +    ld bc, 24*32-1        ; Ahora copiamos 767 bytes 
-    LDIR+    ldir
  
-    POP BC +    pop bc 
-    POP BC +    pop bc 
-    POP DE +    pop de 
-    RET+    ret
 </code> </code>
  
Línea 875: Línea 876:
  El conocido comando de BASIC ''BORDER'' llama a la rutina de la ROM ''BORDER'' en $2294, la cual realiza el cambio del color del borde mediante el acceso a la ULA y además actualiza la variable del sistema ''BORDCR'' en 23624d.  El conocido comando de BASIC ''BORDER'' llama a la rutina de la ROM ''BORDER'' en $2294, la cual realiza el cambio del color del borde mediante el acceso a la ULA y además actualiza la variable del sistema ''BORDCR'' en 23624d.
  
- Concretamente, basta con escribir un valor en el rango 0-7 en el puerto $FE (254) para que la ULA utilice ese valor desde ese instante como color del borde. Las típicas líneas "de carga" en el borde que podemos ver durante las rutinas de ''LOAD'' y ''SAVE'' son cambios del color del borde realizados rápidamente como indicadores de la carga mientras la ULA está dibujando el cuadro actual. Si se cambia el borde con la suficiente rapidez, la ULA cambiará el color con que lo está dibujando cuando todavía no ha acabado la generación del cuadro de imagen actual. El valor 0-7 representa el identificador de color a utilizar de la paleta de 8 colores de la ULA, y este valor lo almacena internamente la ULA (no el Z80), ya que requiere de acceso instantáneo a él durante la generación del vídeo.+ Concretamente, basta con escribir un valor en el rango 0-7 en el puerto $fe (254) para que la ULA utilice ese valor desde ese instante como color del borde. Las típicas líneas "de carga" en el borde que podemos ver durante las rutinas de ''LOAD'' y ''SAVE'' son cambios del color del borde realizados rápidamente como indicadores de la carga mientras la ULA está dibujando el cuadro actual. Si se cambia el borde con la suficiente rapidez, la ULA cambiará el color con que lo está dibujando cuando todavía no ha acabado la generación del cuadro de imagen actual. El valor 0-7 representa el identificador de color a utilizar de la paleta de 8 colores de la ULA, y este valor lo almacena internamente la ULA (no el Z80), ya que requiere de acceso instantáneo a él durante la generación del vídeo.
  
  En el capítulo dedicado a los Puertos de Entrada / Salida pudimos ya observar un ejemplo de cambio de color del borde, que ahora vamos a modificar para separar el OUT en una función ''SetBorder'' propia:  En el capítulo dedicado a los Puertos de Entrada / Salida pudimos ya observar un ejemplo de cambio de color del borde, que ahora vamos a modificar para separar el OUT en una función ''SetBorder'' propia:
Línea 881: Línea 882:
 <code z80> <code z80>
 ; Cambio del color del borde al pulsar espacio ; Cambio del color del borde al pulsar espacio
-  ORG 50000+    ORG 50000
  
-    LD B, 6              ; 6 iteraciones, color inicial borde+    ld b, 6              ; 6 iteraciones, color inicial borde
  
 start: start:
  
 bucle: bucle:
-    LD A, $7F            ; Semifila B a ESPACIO +    ld a, $7f            ; Semifila B a ESPACIO 
-    IN A, ($FE)          ; Leemos el puerto +    in a, ($fe)          ; Leemos el puerto 
-    BIT 0,             ; Testeamos el bit 0 (ESPACIO) +    bit 0,             ; Testeamos el bit 0 (ESPACIO) 
-    JR NZ, bucle         ; Si esta a 1 (no pulsado), esperar+    jr nz, bucle         ; Si esta a 1 (no pulsado), esperar
  
-    LD A             ; A = B +    ld a             ; A = B 
-    CALL SetBorder       ; Cambiamos el color del borde+    call SetBorder       ; Cambiamos el color del borde
  
 suelta_tecla:            ; Ahora esperamos a que se suelte la tecla suelta_tecla:            ; Ahora esperamos a que se suelte la tecla
-    LD A, $7F            ; Semifila B a ESPACIO +    ld a, $7f            ; Semifila B a ESPACIO 
-    IN A, ($FE)          ; Leemos el puerto +    in a, ($fe)          ; Leemos el puerto 
-    BIT 0,             ; Testeamos el bit 0 +    bit 0,             ; Testeamos el bit 0 
-    JR Z, suelta_tecla   ; Saltamos hasta que se suelte+    jr z, suelta_tecla   ; Saltamos hasta que se suelte
  
-    DJNZ bucle           ; Repetimos "B" veces +    djnz bucle           ; Repetimos "B" veces 
-    LD B, 7 +    ld b, 7 
-    JP start             ; Y repetir+    jp start             ; Y repetir
  
 salir: salir:
-    RET+    ret
  
 ;------------------------------------------------------------ ;------------------------------------------------------------
Línea 913: Línea 914:
 ;------------------------------------------------------------ ;------------------------------------------------------------
 SetBorder: SetBorder:
-    OUT (254), A +    out ($fe), a 
-    RET+    ret
  
-  END 50000            ; Ejecucion en 50000+    END 50000            ; Ejecucion en 50000
 </code> </code>
  
Línea 933: Línea 934:
 ;------------------------------------------------------------ ;------------------------------------------------------------
 SetBorder: SetBorder:
-    OUT (254),           ; Cambiamos el color del borde +    out ($fe),           ; Cambiamos el color del borde 
-    RLCA +    rlca 
-    RLCA +    rlca 
-    RLCA                   ; A = A*8 (colocar en bits PAPER) +    rlca                   ; A = A*8 (colocar en bits PAPER) 
-    BIT 5,               ; Mirar si es un color BRIGHT +    bit 5,               ; Mirar si es un color BRIGHT 
-    JR NZ, SetBorder_fin   ; No es bright -> guardarlo+    jr nz, SetBorder_fin   ; No es bright -> guardarlo
                            ; Si es bright                            ; Si es bright
-    XOR 7                  ; -> cambiar la tinta a 0+    xor %00000111          ; -> cambiar la tinta a 0
  
 SetBorder_fin: SetBorder_fin:
-    LD (23624),        ; Salvar el valor en BORDCR+    ld (23624),        ; Salvar el valor en BORDCR
  
-    RET+    ret
 </code> </code>
  
- Mantener actualizado ''BORDCR'' puede ser útil si pretendemos llamar a la rutina de la ROM ''BEEPER'' (en $03B65), ya que el puerto que se utiliza para controlar el altavoz es el mismo que el del borde (salvo que se utiliza el bit 4 del valor que se envía con OUT $FE). La rutina BEEPER carga el valor de ''BORDCR'' para, además del manipular el bit 4 del puerto, cargar los bits 0, 1 y 2 con el borde actual para que éste no cambie. Si no estuviera almacenado el valor del borde en ''BORDCR'' y ''BEEPER'' no lo incluyera en los bits 0-2 de su OUT, lo establecería en negro (000) con cada cambio del estado del speaker.+ Mantener actualizado ''BORDCR'' puede ser útil si pretendemos llamar a la rutina de la ROM ''BEEPER'' (en $03b65), ya que el puerto que se utiliza para controlar el altavoz es el mismo que el del borde (salvo que se utiliza el bit 4 del valor que se envía con OUT $fe). La rutina BEEPER carga el valor de ''BORDCR'' para, además del manipular el bit 4 del puerto, cargar los bits 0, 1 y 2 con el borde actual para que éste no cambie. Si no estuviera almacenado el valor del borde en ''BORDCR'' y ''BEEPER'' no lo incluyera en los bits 0-2 de su OUT, lo establecería en negro (000) con cada cambio del estado del speaker.
  
  Finalmente, recomendamos al lector que elimine del programa anterior la necesidad de pulsar y soltar una tecla. De esta forma podrá verificar qué sucede cuando se cambia el color del borde mientras la ULA lo está dibujando:  Finalmente, recomendamos al lector que elimine del programa anterior la necesidad de pulsar y soltar una tecla. De esta forma podrá verificar qué sucede cuando se cambia el color del borde mientras la ULA lo está dibujando:
Línea 954: Línea 955:
 <code z80> <code z80>
  ; Cambio del color del borde mientras la ULA dibuja  ; Cambio del color del borde mientras la ULA dibuja
-  ORG 50000+    ORG 50000
  
-    LD B, 6              ; 6 iteraciones, color inicial borde+    ld b, 6              ; 6 iteraciones, color inicial borde
  
 start: start:
  
 bucle: bucle:
-    LD A             ; A = B +    ld a             ; A = B 
-    CALL SetBorder       ; Cambiamos el color del borde+    call SetBorder       ; Cambiamos el color del borde
  
-    DJNZ bucle           ; Repetimos "B" veces +    djnz bucle           ; Repetimos "B" veces 
-    LD B, 7 +    ld b, 7 
-    JP start             ; Y repetir+    jp start             ; Y repetir
  
 salir: salir:
-    RET+    ret 
 + 
 +    END 50000
 </code> </code>
  
Línea 981: Línea 984:
  
 <code basic> <code basic>
-10 FOR I=0 TO 7 : BORDER I : NEXT I : GOTO 10+10 FOR i=0 TO 7 : BORDER I : NEXT I : GOTO 10
 </code> </code>
  
Línea 995: Línea 998:
 ===== El "atributo actual temporal" ATTR-T ===== ===== El "atributo actual temporal" ATTR-T =====
  
- Ahora que conocemos el formato de una celdilla de atributo podemos hablar de la variable del sistema ''ATTR-T'' (dirección de memoria **$5C8F** o **23695**), la cual almacena el atributo actual temporal que las rutinas de la ROM del Spectrum como nuestra conocida RST 16.+ Ahora que conocemos el formato de una celdilla de atributo podemos hablar de la variable del sistema ''ATTR-T'' (dirección de memoria **$5c8f** o **23695**), la cual almacena el atributo actual temporal que las rutinas de la ROM del Spectrum como nuestra conocida rst 16.
  
- A continuación tenemos un ejemplo que imprime cadenas con diferentes atributos de color. Para ello se ha creado una rutina PrintString basada en imprimir caracteres mediante ''RST 16'', que utiliza el valor de ''ATTR-T''.+ A continuación tenemos un ejemplo que imprime cadenas con diferentes atributos de color. Para ello se ha creado una rutina PrintString basada en imprimir caracteres mediante ''rst 16'', que utiliza el valor de ''ATTR-T''.
  
 <code z80> <code z80>
 ; Prueba ATTR-P ; Prueba ATTR-P
-  ORG 50000+    ORG 50000
  
 Start: Start:
  
-    LD A, 1                    ; Borde azul +    ld a, 1                    ; Borde azul 
-    CALL BORDER+    call BORDER
  
-    LD A, 8+4 +    ld a, 8+4 
-    LD (CLS_COLOR), A +    ld (CLS_COLOR), a 
-    CALL CLS+    call CLS
  
-    LD A, 56                   ; Negro sobre gris +    ld a, 56                   ; Negro sobre gris 
-    LD (ATTR_T),             ; Cambiamos ATTR-T+    ld (ATTR_T),             ; Cambiamos ATTR-T
  
-    LD DE, linea1 +    ld de, linea1 
-    CALL PrintString+    call PrintString
  
-    LD A, 12                   ; Verde sobre azul +    ld a, 12                   ; Verde sobre azul 
-    LD (ATTR_T),             ; Cambiamos ATTR-T+    ld (ATTR_T),             ; Cambiamos ATTR-T
  
-    LD DE, linea2 +    ld de, linea2 
-    CALL PrintString+    call PrintString
  
-    LD A, 64+2+9               ; Atributos: m + brillo. +    ld a, 64+2+9               ; Atributos: m + brillo. 
-    LD (ATTR_T),             ; Cambiamos ATTR-T+    ld (ATTR_T),             ; Cambiamos ATTR-T
  
-    LD DE, linea2 +    ld de, linea2 
-    CALL PrintString+    call PrintString
  
-    CALL Wait_For_Key          ; Esperamos que se pulse y libere tecla+    call Wait_For_Key          ; Esperamos que se pulse y libere tecla
  
-    RET                        ; Fin del programa+    ret                        ; Fin del programa
  
-ATTR_T    EQU   $5C8F+ATTR_T    EQU   $5c8f
  
 ;------------------------------------------------------------------------- ;-------------------------------------------------------------------------
 ; Datos ; Datos
 ;------------------------------------------------------------------------- ;-------------------------------------------------------------------------
-linea1 DEFB 'Impreso con ATTR-T actual', _CR, _CR, _EOS +linea1  DEFB 'Impreso con ATTR-T actual', _CR, _CR, _EOS 
-linea2 DEFB 'Esto es una prueba', _CR,'cambiando los atributos', _CR, _CR, _EOS+linea2  DEFB 'Esto es una prueba', _CR,'cambiando los atributos', _CR, _CR, _EOS
  
-INCLUDE "utils.asm"+    INCLUDE "utils.asm"
  
-END 50000+    END 50000
 </code> </code>
  
- Con nuestra nueva rutina de ''PrintString'' trazaremos en pantalla 1 línea con los atributos actuales seguida de 2 líneas con diferentes atributos. Nótese como RST 16 entiende e interpreta en las cadenas los códigos de control como por ejemplo 13 (retorno de carro).+ Con nuestra nueva rutina de ''PrintString'' trazaremos en pantalla 1 línea con los atributos actuales seguida de 2 líneas con diferentes atributos. Nótese como rst 16 entiende e interpreta en las cadenas los códigos de control como por ejemplo 13 (retorno de carro).
  
 \\  \\ 
Línea 1077: Línea 1080:
 Start: Start:
     ; Rellenamos la VRAM de pixeles copiando 6 KB de la ROM     ; Rellenamos la VRAM de pixeles copiando 6 KB de la ROM
-    LD HL, 0 +    ld hl, 0 
-    LD DE, 16384 +    ld de, 16384 
-    LD BC, 6144 +    ld bc, 6144 
-    LDIR+    ldir
  
-    CALL Wait_For_Key +    call Wait_For_Key 
-    CALL FadeScreen+    call FadeScreen
  
-    RET+    ret
  
 ;----------------------------------------------------------------------- ;-----------------------------------------------------------------------
Línea 1091: Línea 1094:
 ;----------------------------------------------------------------------- ;-----------------------------------------------------------------------
 FadeScreen: FadeScreen:
-    PUSH AF +    push af 
-    PUSH BC +    push bc 
-    PUSH DE +    push de 
-    PUSH HL                      ; Preservamos los registros+    push hl                      ; Preservamos los registros
  
-    LD B, 9                      ; Repetiremos el bucle 9 veces +    ld b, 9                      ; Repetiremos el bucle 9 veces 
-    LD C, 0                      ; Nuestro contador de columna+    ld c, 0                      ; Nuestro contador de columna
  
 fadegfx_loop1: fadegfx_loop1:
-    LD HL, 16384                 ; Apuntamos HL a la zona de atributos +    ld hl, 16384                 ; Apuntamos HL a la zona de atributos 
-    LD DE, 6144                  ; Iteraciones bucle+    ld de, 6144                  ; Iteraciones bucle
  
 fadegfx_loop2: fadegfx_loop2:
-    LD A, (HL)                   ; Cogemos el grupo de 8 pixeles+    ld a, (hl)                   ; Cogemos el grupo de 8 pixeles
  
  
     ;-- Actuamos sobre el valor de los pixeles --     ;-- Actuamos sobre el valor de los pixeles --
-    CP 0                         ; +    cp 0                         ; 
-    JR Z, fadegfx_save           ; Si ya es cero, no hacemos nada+    jr z, fadegfx_save           ; Si ya es cero, no hacemos nada
  
-    EX AFAF                    ; Nos guardamos el dato+    ex afaf                    ; Nos guardamos el dato
  
-    LD A                     ; Pasamos el contador a A +    ld a                     ; Pasamos el contador a A 
-    CP 15                        ; Comparamos A con 15 +    cp 15                        ; Comparamos A con 15 
-    JR NC, fadegfx_mayor15       ; Si es mayor, saltamos+    jr nc, fadegfx_mayor15       ; Si es mayor, saltamos
  
-    EX AFAF                    ; Recuperamos en A los pixeles +    ex afaf                    ; Recuperamos en A los pixeles 
-    RLA                          ; Rotamos A a la izquierda +    rla                          ; Rotamos A a la izquierda 
-    JR fadegfx_save              ; Y guardamos el dato+    jr fadegfx_save              ; Y guardamos el dato
  
 fadegfx_mayor15: fadegfx_mayor15:
-    EX AFAF                    ; Recuperamos en A los pixeles +    ex afaf                    ; Recuperamos en A los pixeles 
-    SRL A                        ; Rotamos A a la derecha+    srl a                        ; Rotamos A a la derecha
  
     ;-- Fin actuacion sobre el valor de los pixeles --     ;-- Fin actuacion sobre el valor de los pixeles --
Línea 1129: Línea 1132:
 fadegfx_save: fadegfx_save:
  
-    LD (HL),                   ; Almacenamos el atributo modificado +    ld (hl),                   ; Almacenamos el atributo modificado 
-    INC HL                       ; Avanzamos puntero de memoria+    inc hl                       ; Avanzamos puntero de memoria
  
     ; Incrementamos el contador y comprobamos si hay que resetearlo     ; Incrementamos el contador y comprobamos si hay que resetearlo
-    INC C +    inc c 
-    LD AC +    ld ac 
-    CP 32 +    cp 32 
-    JR NZ, fadegfx_continue+    jr nz, fadegfx_continue
  
-    LD C, 0+    ld c, 0
  
 fadegfx_continue: fadegfx_continue:
  
-    DEC DE +    dec de 
-    LD AD +    ld ad 
-    OR E +    or e 
-    JP NZ, fadegfx_loop2      ; Hasta que DE == 0+    jp nz, fadegfx_loop2      ; Hasta que DE == 0
  
-    DJNZ fadegfx_loop1        ; Repeticion 9 veces+    djnz fadegfx_loop1        ; Repeticion 9 veces
  
-    POP HL +    pop hl 
-    POP DE +    pop de 
-    POP BC +    pop bc 
-    POP AF                       ; Restauramos registros +    pop af                       ; Restauramos registros 
-    RET+    ret
  
-INCLUDE "utils.asm"+    INCLUDE "utils.asm"
  
-END 50000+    END 50000
 </code> </code>
  
Línea 1175: Línea 1178:
 ;----------------------------------------------------------------------- ;-----------------------------------------------------------------------
 FadeAttributes: FadeAttributes:
-    PUSH AF +    push af 
-    PUSH BC +    push bc 
-    PUSH DE +    push de 
-    PUSH HL                      ; Preservamos los registros+    push hl                      ; Preservamos los registros
  
-    LD B, 9                      ; Repetiremos el bucle 9 veces+    ld b, 9                      ; Repetiremos el bucle 9 veces
  
 fadescreen_loop1: fadescreen_loop1:
-    LD HL, 16384+6144            ; Apuntamos HL a la zona de atributos +    ld hl, 16384+6144            ; Apuntamos HL a la zona de atributos 
-    LD DE, 768                   ; Iteraciones bucle+    ld de, 768                   ; Iteraciones bucle
  
-    HALT +    halt 
-    HALT                         ; Ralentizamos el efecto+    halt                         ; Ralentizamos el efecto
  
 fadescreen_loop2: fadescreen_loop2:
-    LD A, (HL)                   ; Cogemos el atributo +    ld a, (hl)                   ; Cogemos el atributo 
-    AND 127                      ; Eliminamos el bit de flash +    and %01111111                ; Eliminamos el bit de flash 
-    LD CA+    ld ca
  
-    AND 7                        ; Extraemos la tinta (AND 00000111b) +    and %00000111                ; Extraemos la tinta (and 00000111b) 
-    JR Z, fadescreen_ink_zero    ; Si la tinta ya es cero, no hacemos nada+    jr z, fadescreen_ink_zero    ; Si la tinta ya es cero, no hacemos nada
  
-    DEC A                        ; Si no es cero, decrementamos su valor+    dec a                        ; Si no es cero, decrementamos su valor
  
 fadescreen_ink_zero: fadescreen_ink_zero:
  
-    EX AFAF                    ; Nos hacemos una copia de la tinta en A' +    ex afaf                    ; Nos hacemos una copia de la tinta en A' 
-    LD A                     ; Recuperamos el atributo +    ld a                     ; Recuperamos el atributo 
-    SRA A +    sra a 
-    SRA A                        ; Pasamos los bits de paper a 0-2 +    sra a                        ; Pasamos los bits de paper a 0-2 
-    SRA A                        ; con 3 instrucciones de desplazamiento >>+    sra a                        ; con 3 instrucciones de desplazamiento >>
  
-    AND 7                        ; Eliminamos el resto de bits +    and %00000111                ; Eliminamos el resto de bits 
-    JR Z, fadescreen_paper_zero  ; Si ya es cero, no lo decrementamos+    jr z, fadescreen_paper_zero  ; Si ya es cero, no lo decrementamos
  
-    DEC A                        ; Lo decrementamos+    dec a                        ; Lo decrementamos
  
 fadescreen_paper_zero: fadescreen_paper_zero:
-    SLA A +    sla a 
-    SLA A                        ; Volvemos a color paper en bits 3-5 +    sla a                        ; Volvemos a color paper en bits 3-5 
-    SLA A                        ; Con 3 instrucciones de desplazamiento <<+    sla a                        ; Con 3 instrucciones de desplazamiento <<
  
-    LD C                     ; Guardamos el papel decrementado en A +    ld c                     ; Guardamos el papel decrementado en A 
-    EX AFAF                    ; Recuperamos A' +    ex afaf                    ; Recuperamos A' 
-    OR C                         ; A = A OR C   PAPEL OR TINTA+    or c                         ; A = A or c   PAPEL OR TINTA
  
-    LD (HL),                   ; Almacenamos el atributo modificado +    ld (hl),                   ; Almacenamos el atributo modificado 
-    INC HL                       ; Avanzamos puntero de memoria+    inc hl                       ; Avanzamos puntero de memoria
  
-    DEC DE +    dec de 
-    LD AD +    ld ad 
-    OR E +    or e 
-    JP NZ, fadescreen_loop2      ; Hasta que DE == 0+    jp nz, fadescreen_loop2      ; Hasta que DE == 0
  
-    DJNZ fadescreen_loop1        ; Repeticion 9 veces+    djnz fadescreen_loop1        ; Repeticion 9 veces
  
-    POP HL +    pop hl 
-    POP DE +    pop de 
-    POP BC +    pop bc 
-    POP AF                       ; Restauramos registros +    pop af                       ; Restauramos registros 
-    RET+    ret
 </code> </code>
  
Línea 1243: Línea 1246:
  
  Rutinas más complejas pueden producir cortinillas y efectos mucho más vistosos. En la revista Microhobby se publicaron muchos de estos efectos de zoom, desaparición de pantalla o inversión, dentro de la sección //Trucos//.  Rutinas más complejas pueden producir cortinillas y efectos mucho más vistosos. En la revista Microhobby se publicaron muchos de estos efectos de zoom, desaparición de pantalla o inversión, dentro de la sección //Trucos//.
 +
 +Del mismo modo, el libro **//40 Best Machine code Routines for the ZX Spectrum//** ("Las 40 mejores rutinas en código máquina para el ZX Spectrum") de //John Hardman y Andrew Hewson// nos proporciona una serie de rutinas en ensamblador para realizar diferentes acciones con los píxeles y los atributos de la videoram, como por ejemplo:
 +
 +\\ 
 +   * Scrollear atributos a izquierda, derecha, arriba o abajo.
 +   * Scroll de pantalla de un carácter (8 pixels) a izquierda, derecha, arriba o abajo.
 +   * Scroll de pantalla de un pixel a izquierda, derecha, arriba o abajo.
 +   * Mezclar dos imágenes con ''OR'' o ''XOR''.
 +   * Inversión de la pantalla (píxeles a 0 se ponen a 1, y píxeles a 1 se ponen a 0).
 +   * Invertir carácter vertical y horizontalmente.
 +   * Rotar carácter 90º en sentido horario.
 +   * Alterar todos los atributos de la pantalla (los bits deseados).
 +   * Cambiar todos los atributos de la pantalla de un determinado valor por otro valor.
 +   * Rellenado de regiones cerradas (poniendo a 1 los píxeles dentro de esas regiones).
 +   * Impresión de figuras.
 +   * Copia de una zona de la pantalla en otra, ampliándola por una cantidad entera (por ejemplo, x2 o x3).
 +
  
 \\  \\ 
 ===== La Shadow VRAM de los modelos de 128K ===== ===== La Shadow VRAM de los modelos de 128K =====
  
- En el capítulo dedicado a la paginación de memoria en los modelos de 128KB se habló de la paginación de bloques de 16KB sobre el área entre $C000 y $FFFF. El bloque de 16KB que almacena la videoram (el bloque 5, o, como se le conoce técnicamente, **RAM5**) está normalmente mapeado sobre $4000.+ En el capítulo dedicado a la paginación de memoria en los modelos de 128KB se habló de la paginación de bloques de 16KB sobre el área entre $c000 y $ffff. El bloque de 16KB que almacena la videoram (el bloque 5, o, como se le conoce técnicamente, **RAM5**) está normalmente mapeado sobre $4000.
  
 \\  \\ 
Línea 1256: Línea 1276:
 \\  \\ 
  
- En los modelos de 128K, existe un segundo bloque de 16KB que podemos utilizar como VideoRAM (**Shadow VRAM**). El Z80 y la ULA nos permiten mapear **RAM7** sobre $C000-$FFFF, dejando la VideoRAM original sobre $4000. Más interesante todavía, la ULA puede visualizar el contenido de RAM7 en lugar del de RAM5 aunque no hayamos mapeado RAM7 en ningún sitio. Y recordemos que también podemos mapear la VRAM estándar (RAM5) sobre $C000, accediendo a ella a través de $C000 además de mediante $4000.+ En los modelos de 128K, existe un segundo bloque de 16KB que podemos utilizar como VideoRAM (**Shadow VRAM**). El Z80 y la ULA nos permiten mapear **RAM7** sobre $c000-$ffff, dejando la VideoRAM original sobre $4000. Más interesante todavía, la ULA puede visualizar el contenido de RAM7 en lugar del de RAM5 aunque no hayamos mapeado RAM7 en ningún sitio. Y recordemos que también podemos mapear la VRAM estándar (RAM5) sobre $c000, accediendo a ella a través de $c000 además de mediante $4000.
  
- El poder visualizar una VRAM aunque no esté mapeada y el poder mapear tanto RAM5 como RAM7 sobre $C000 nos permite organizar el código de nuestro programa para que siempre escriba sobre $C000, teniendo mapeada en $C000 la pantalla que actualmente no esté visible.+ El poder visualizar una VRAM aunque no esté mapeada y el poder mapear tanto RAM5 como RAM7 sobre $c000 nos permite organizar el código de nuestro programa para que siempre escriba sobre $c000, teniendo mapeada en $c000 la pantalla que actualmente no esté visible.
  
  La utilidad principal de esta funcionalidad es la de poder generar un cuadro de imagen o animación en una "pantalla virtual" (la pantalla shadow) que no es visible, cambiando la visualización a esta pantalla una vez compuesta la imagen actual. De esta forma es posible trabajar con una pantalla completa sin que nos alcance el haz de electrones durante su dibujado, especialmente en juegos que realicen scrolles de todo el área de imagen.  La utilidad principal de esta funcionalidad es la de poder generar un cuadro de imagen o animación en una "pantalla virtual" (la pantalla shadow) que no es visible, cambiando la visualización a esta pantalla una vez compuesta la imagen actual. De esta forma es posible trabajar con una pantalla completa sin que nos alcance el haz de electrones durante su dibujado, especialmente en juegos que realicen scrolles de todo el área de imagen.
Línea 1265: Línea 1285:
  
 \\  \\ 
-   * Mapeamos RAM7 sobre $C000.+   * Mapeamos RAM7 sobre $c000.
    * Visualizamos RAM5 (RAM7 no es visible).    * Visualizamos RAM5 (RAM7 no es visible).
-   * Trabajamos sobre $C000 (sobre RAM7). Los cambios en nuestra pantalla shadow no son visibles. +   * Trabajamos sobre $c000 (sobre RAM7). Los cambios en nuestra pantalla shadow no son visibles. 
-   * Esperamos una interrupción (mediante HALT o mediante coordinación con la ISR de la ULA).+   * Esperamos una interrupción (mediante halt o mediante coordinación con la ISR de la ULA).
    * Cambiamos la visualización a RAM7 (RAM5 deja de ser visible).    * Cambiamos la visualización a RAM7 (RAM5 deja de ser visible).
-   * Mapeamos ahora RAM5 sobre $C000+   * Mapeamos ahora RAM5 sobre $c000
-   * Trabajamos sobre $C000 (sobre RAM5). Los cambios en nuestra pantalla shadow no son visibles.+   * Trabajamos sobre $c000 (sobre RAM5). Los cambios en nuestra pantalla shadow no son visibles.
    * Repetimos el proceso.    * Repetimos el proceso.
 \\  \\ 
  
- Con este mecanismo siempre trabajamos sobre $C000 pero los cambios que realizamos sobre esta pantalla virtual no son perceptibles por el usuario. Cambiando la visualización de la VRAM a nuestra pantalla actual tras una interrupción hacemos los cambios visibles de forma inmediata, sin que el haz de electrones afecte a nuestro scroll o al dibujado de sprites. La tasa de fotogramas por segundo ya no sería de 50 (no podríamos generar 1 cuadro de imagen por interrupción) pero se evitaría un posible molesto efecto de parpadeo o cortinilla.+ Con este mecanismo siempre trabajamos sobre $c000 pero los cambios que realizamos sobre esta pantalla virtual no son perceptibles por el usuario. Cambiando la visualización de la VRAM a nuestra pantalla actual tras una interrupción hacemos los cambios visibles de forma inmediata, sin que el haz de electrones afecte a nuestro scroll o al dibujado de sprites. La tasa de fotogramas por segundo ya no sería de 50 (no podríamos generar 1 cuadro de imagen por interrupción) pero se evitaría un posible molesto efecto de parpadeo o cortinilla.
  
- La desventaja de este sistema es que utilizamos $C000-$FFFF como pantalla virtual con lo que perdemos 16KB efectivos de RAM así como la posibilidad de paginar sobre $C000. Nos quedan así 16KB de memoria (entre $8000 y $BFFF) para alojar el código de nuestro programa, los datos gráficos, textos, etc. Esto puede ser una enorme limitación según el tipo de juego o programa que estemos realizando.+ La desventaja de este sistema es que utilizamos $c000-$ffff como pantalla virtual con lo que perdemos 16KB efectivos de RAM así como la posibilidad de paginar sobre $c000. Nos quedan así 16KB de memoria (entre $8000 y $bfff) para alojar el código de nuestro programa, los datos gráficos, textos, etc. Esto puede ser una enorme limitación según el tipo de juego o programa que estemos realizando.
  
- En realidad, si diseñamos adecuadamente nuestro programa, podemos aprovechar más de 16KB, puesto que sólo necesitamos mapear RAM5 ó RAM7 en $C000 durante la generación de la pantalla virtual. Esto obliga a que los gráficos, fuentes, sprites y mapeados del juego deban estar disponibles en $8000-$BFFF, pero una vez finalizada la generación de la pantalla podemos volver a mapear RAM0 sobre $C000, volviendo a la lógica del juego que podría estar ubicada en ese bloque, junto al resto de variables, imágenes o textos usados en los menúes, efectos sonoros, músicas, etc.+ En realidad, si diseñamos adecuadamente nuestro programa, podemos aprovechar más de 16KB, puesto que sólo necesitamos mapear RAM5 ó RAM7 en $c000 durante la generación de la pantalla virtual. Esto obliga a que los gráficos, fuentes, sprites y mapeados del juego deban estar disponibles en $8000-$bfff, pero una vez finalizada la generación de la pantalla podemos volver a mapear RAM0 sobre $c000, volviendo a la lógica del juego que podría estar ubicada en ese bloque, junto al resto de variables, imágenes o textos usados en los menúes, efectos sonoros, músicas, etc.
  
- Como véis, se necesita tener muy controlada la ubicación de las diferentes rutinas y variables y diseñar el juego para que mapee la página adecuada en cada momento y salte a una rutina concreta sólo cuando la rutina a la que hace referencia un CALL esté contenida en la página mapeada.+ Como véis, se necesita tener muy controlada la ubicación de las diferentes rutinas y variables y diseñar el juego para que mapee la página adecuada en cada momento y salte a una rutina concreta sólo cuando la rutina a la que hace referencia un call esté contenida en la página mapeada.
  
- Se reseñó también, en el apartado //Particularidades del +2A/+3// la existencia de unos modos extendidos de paginación que permitirían ubicar la segunda VideoRAM (el bloque 7, o RAM7) sobre $4000, permitiendo el alternar entre la visualización de RAM5 o de RAM7 sin perder la memoria $C000-$FFFF como "Pantalla Virtual":+ Se reseñó también, en el apartado //Particularidades del +2A/+3// la existencia de unos modos extendidos de paginación que permitirían ubicar la segunda VideoRAM (el bloque 7, o RAM7) sobre $4000, permitiendo el alternar entre la visualización de RAM5 o de RAM7 sin perder la memoria $c000-$ffff como "Pantalla Virtual":
  
 \\  \\ 
Línea 1292: Línea 1312:
 \\  \\ 
  
- Como puede verse en la figura anterior, los modos Bit2 = 0, Bit1 = 1 (Bancos 4-5-6-3) y Bit2 = 1, Bit 1=1 (Bancos 4-7-6-3) del puerto $1FFD permiten paginar cualquiera de las 2 videorams (RAM5 o RAM7) sobre $4000.+ Como puede verse en la figura anterior, los modos Bit2 = 0, Bit1 = 1 (Bancos 4-5-6-3) y Bit2 = 1, Bit 1=1 (Bancos 4-7-6-3) del puerto $1ffd permiten paginar cualquiera de las 2 videorams (RAM5 o RAM7) sobre $4000.
  
- Pese a las posibilidades de "animación sin parpadeo" que proporcionan estas técnicas, la utilización de cualquiera de las dos tiene una desventaja clara además de la "pérdida" (durante el dibujado de la pantalla shadow) de los 16KB $C000-$FFFF, y es la incompatibilidad con modelos de 48K, requiriendo un modelo de 128Kb para paginar RAM7 o incluso de un +2A/+3 para el uso de la paginación extendida. Si a los 16KB de RAM5 le restamos los 7KB de pantalla nos quedan otros 9KB adicionales, pero con la particularidad de que ese bloque de memoria está "compartido" con la ULA por lo que la velocidad de lectura, escritura y ejecución efectiva de este bloque se puede ver reducida hasta en un 25%.+ Pese a las posibilidades de "animación sin parpadeo" que proporcionan estas técnicas, la utilización de cualquiera de las dos tiene una desventaja clara además de la "pérdida" (durante el dibujado de la pantalla shadow) de los 16KB $c000-$ffff, y es la incompatibilidad con modelos de 48K, requiriendo un modelo de 128Kb para paginar RAM7 o incluso de un +2A/+3 para el uso de la paginación extendida. Si a los 16KB de RAM5 le restamos los 7KB de pantalla nos quedan otros 9KB adicionales, pero con la particularidad de que ese bloque de memoria está "compartido" con la ULA por lo que la velocidad de lectura, escritura y ejecución efectiva de este bloque se puede ver reducida hasta en un 25%.
  
 \\  \\ 
  • cursos/ensamblador/gfx1_vram.1705077162.txt.gz
  • Última modificación: 12-01-2024 16:32
  • por sromero