Telas do speccy no MSX – parte 4

tbzscmcb-4_abertura

Continuando com visualizador de telas de ZX-Spectrum no MSX e, como já faz algum muito tempo, é bom aproveitar para relembrar que a primeira parte tratou das diferenças entre os modos de vídeo dos dois computadores (mesma resolução mas uma organização completamente diferente), a segunda sobre como ler um arquivo do disco diretamente em assembly usando as rotinas da BDOS e a terceira sobre como recuperar e interpretar os argumentos da linha de comando do MSX-DOS.

Agora é a vez de tratar da parte mais importante de todo o programa, a rotina que faz a conversão da imagem do vídeo do speecy para a VRAM do MSX.

TL/DR; o código!

Para esta parte uma versão mais simples que pode ser montada e executada diretamente como uma imagem de cartucho.


;
; Converte uma tela de ZX-Spectrum para ser exibida em um MSX
;
; Para montar:
; pasmo -d -v zxview.asm zxview.rom
;
; Não esqueça de baixar a imagem, instruções lá no final!
;
; endereços da MSX-BIOS
DISSCR: equ $0041
ENASCR: equ $0044
INIGRP: equ $0072
GRPCGP: equ $F3CB
GRPCOL: equ $F3C9
WRTVDP: equ $0047
WRTVRM: equ $004D
rom_size: equ 8192
rom_area: equ $8000
ram_area: equ $C000
; variáveis usadas no programa
zx_ink: equ ram_area
zx_paper: equ ram_area+1
org rom_area
db "AB" ; cabeçalho do cartucho
dw start_code ; endereço de execução
db "ZXVIEW" ; identificação => "ZXVIEW"
ds 6,0 ; mantenha em branco
start_code:
proc
call INIGRP ; inicializa a SCREEN 2
call DISSCR ; desliga o vídeo
ld b,0 ; 'B' <- (cor transparente)
ld c,7 ; registrador 7, cor da borda
call WRTVDP ; muda a cor da borda
call decode_patterns ; traduz a tabela de padrões
call decode_attributes ; traduz a tabela de atributos
call ENASCR ; religa o vídeo
loop:
jr loop
endp
;
; decodifica a tabela de padrões diretamente na VRAM
;
decode_patterns:
proc
local __loop_tt
local __loop_lll
local __loop_rrr
local __loop_ccccc
; composição do vídeo do endereço de vídeo no speccy
;
; +——————-+——————-+
; | H | L |
; +———+———+———+———+
; | 0 0 0 T | T L L L | R R R C | C C C C |
; +———+———+———+———+
;
ld de,scr_file ; 'DE' na tabela de padrões do speccy
ld hl,(GRPCGP) ; 'HL' no tabela de padrões da VRAM.
ld b,3 ; repetições do laço TT (×3)
__loop_tt:
push bc ; salva 'BC' atual
ld b,8 ; repetições do laço LLL (×8)
__loop_lll:
push bc ; salva 'BC' atual
push hl ; salva 'HL' atual
ld b,8 ; repetições do laço RRR (×8)
__loop_rrr:
push bc ; salva 'BC' atual
ld b,32 ; repetições do laço CCCCC (×32)
__loop_ccccc:
push bc ; salva 'BC' atual
ld a,(de) ; lê um byte da tela do speccy
call WRTVRM ; escreve na VRAM do MSX
inc de ; próximo byte da tela do speccy
ld bc,8 ; 'BC' <- 8
add hl,bc ; avança a VRAM oito posições
pop bc ; recupera 'BC'
djnz __loop_ccccc ; repete o laço
pop bc ; recupera 'BC'
djnz __loop_rrr ; repete o laço
pop hl ; recupera 'HL'
inc hl ; avança a VRAM uma posição
pop bc ; recupera 'BC'
djnz __loop_lll ; repete o laço
ld bc,2040 ; 'BC' recebe 2040
add hl,bc ; avaça a VRAM em 2040 posições
pop bc ; recupera 'BC'
djnz __loop_tt ; repete o laço
ret ; fim da rotina
endp
;
; decodifica a tabela de atributos diretamente na VRAM
;
decode_attributes:
proc
local __loop0
local __loop1
local __apply_bright
local __skip_bright
; tabela de atributos do speccy:
;
; +——-+——–+—-+—-+—-+—+—+—+
; | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
; +——-+——–+—-+—-+—-+—+—+—+
; | flash | bright | paper | ink |
; +——-+——–+————–+———–+
;
; (esta versão ainda não trata o 'flash')
ld de,scr_file+6144 ; 'DE' na tabela de atributos do speccy
ld hl,(GRPCOL) ; 'HL' na tabela de atributos da VRAM.
ld bc,768 ; número de repetiçõpes do laço
__loop0:
push bc ; salva 'BC' atual
ld b,8 ; número de repetiçõpes do laço
__loop1:
push bc ; salva 'BC' atual
ld a,(de) ; lê um byte da tela do speccy
call extract_attr ; traduz os atributos
bit 6,a ; o 6º bit (brilho) está ligado?
jr z,__skip_bright ; se desligado, pula a conversão
__apply_bright:
ld a,1 ; 'A' <- 1
and b ; 'A' <- 'A' and 'B'
cp 0
jr z,__skip_bright ; se 'A' é 0, pula a conversão
ld c,8 ; 'C' <- 8
ld a,(zx_ink) ; lê o valor de 'zx_ink'
or c ; 'A' <- 'A' or 'C'
ld (zx_ink),a ; atualiza o valor de 'zx_ink'
ld a,(zx_paper) ; lê o valor de 'zx_paper
or c ; 'A' <- 'A' or 'C'
ld (zx_paper),a ; atualiza o valor de 'zx_paper'
__skip_bright:
ld a,(zx_ink) ; lê o valor de 'ink'
call translate_color ; converte para a cor do MSX
rept 4
sla b ; desloca 'B' para esquerda (×4)
endm
ld c,b ; 'C' <- (cor de frente)
ld a,(zx_paper) ; lê o valor de 'paper'
call translate_color ; converte para a cor do MSX
ld a,b ; 'A' <- (cor de fundo)
or c ; 'A' <- 'A' or (cor de frente)
call WRTVRM ; escreve na VRAM do MSX
inc hl ; avança a VRAM uma posição
pop bc ; recupera 'BC'
djnz __loop1 ; repete o laço
inc de ; próximo byte da tela do speccy
pop bc ; recupera 'BC'
dec bc ; decrementa o contador
ld a,b ; 'A' recebe 'B'
or c ; 'A' <- 'A' or 'C'
cp 0 ; 'A' é zero?
jr nz, __loop0 ; se 'A' != 0, repete o laço
ret ; fim da rotina
endp
;
; extrai os valores de INK e PAPER da tabela de atributos
;
extract_attr:
proc
push af ; salva 'AF' atual
ld i,a ; salva em 'I' o valor de 'A'
and $07 ; 'A' <- 'A' AND %00000111
ld (zx_ink),a ; armazena o valor em 'zx_ink'
ld a,i ; recupera de 'I' o valor de 'A''
and $38 ; 'A' <- 'A' AND %00111000
rept 3
srl a ; desloca 'B' para esquerda (×3)
endm
ld (zx_paper),a ; armazena o valor em 'zx_paper'
pop af ; recupera 'AF'
ret ; fim da rotina
endp
;
; traduz a cor do ZX Spectrum em sua cor de MSX
;
translate_color:
proc
local __loop
local __zx_colors
push hl ; salva 'HL'
ld hl,__zx_colors-1 ; aponta para a tabela de cores
ld b,a ; 'B' <- (cor a traduzir)
inc b ; decrementa 'B'
__loop:
inc hl ; incrementa o ponteiro
djnz __loop ; repete o laço
ld b,(hl) ; 'B' <- (cor traduzida)
pop hl ; recupera 'HL'
ret ; fim da rotina
__zx_colors:
db $1, $4, $6, $d, $c, $7, $a, $e
db $1, $5, $8, $9, $2, $f, $b, $f
endp
scr_file:
; este arquivo pode ser baixado diretamente do World of Spectrum em:
; https://www.worldofspectrum.org/pub/sinclair/screens/load/h/scr/HeadOverHeels.scr
incbin "HeadOverHeels.scr"
; ou substitua-o por qualquer imagem SCR de sua preferência.
rom_pad:
ds rom_size-(rom_pad-rom_area), 0

view raw

zxview.asm

hosted with ❤ by GitHub

Só lembrando que a imagem (linha 256) não foi incluída mas que ela pode ser baixada do World of Spectrum — você pode usar qualquer outra, até mesmo a do Chase H.Q. :-D.

Explicando (um pouco) o código

Para a explicar o funcionamento do código vou recorrer ao protótipo em MSX-BASIC apresentado na segunda parte, este sujeito aqui:


100 DEFINT A-Z:DIM L(15)
105 KEY OFF:COLOR 15,4,0:SCREEN 2
110 FOR A=0 TO 15:READ L(A):NEXT A
115 OPEN "CHASEHQ.SCR" AS#1 LEN=1
120 FIELD#1,1 AS E$
125 FOR A=0 TO 6143 STEP 2048
130 FOR B=0 TO 7
135 FOR C=0 TO 2047 STEP 256
140 FOR D=0 TO 255 STEP 8
145 GET#1:VPOKE A+B+C+D,ASC(E$)
150 NEXT D,C,B,A
155 FOR A=8192 TO 14335 STEP 8
160 GET #1:C=ASC(E$):D=(C AND &H38)\8:E=(C AND &H7):C=C AND &H40
165 FOR B=0 TO 7
170 IF C<>0 AND (B AND 1)=1 THEN F=8 ELSE F=0
175 VPOKE A+B,L(F+E)*16+L(F+D)
180 NEXT B,A:CLOSE #1
185 GOTO 185
190 DATA 1,4,6,13,12,7,10,14,1,5,8,9,2,15,11,15

view raw

zxview.bas

hosted with ❤ by GitHub

Motivo? Simples, a versão em assembler é quase que a transcrição direta deste programa.

Rotina “decode_patterns”

É a parte mais importante e corresponde ao trecho entre as linhas 125 e 150 do programa em BASIC. O conjunto de quatro laços TT, LLL, RRR e CCCCC se encarregam de fazer a leitura da imagem do speccy de forma sequencial (ponteiro no registrador DE) e também de incrementar o ponteiro da VRAM (ponteiro no registrador HL) para a posição correta daquele byte no MSX.

Uma curiosidade aqui é o fato do laço TT adicionar a HL 2040 e não 2048, isto ocorre pois ao terminar do laço LLL ela já foi deixado oito posições para frente pois os laços implementados aqui são todos “do «ações» while «condição»”.

Rotinas “decode_attributes”, “extract_attr” e “translate_color”

Correspondem às linhas de 155 a 185 e se encarregam de recuperar o byte de atributo, traduzir as cores para as usadas pelo MSX e, claro, fazer a alternância de cores em linhas pares e ímpares para simular o atributo de bright.

A rotina “decode_attributes” se encarrega de cuidar dos dois laços, um que recupera cada byte das 768 posições da tabela de atributos do speccy e o outro que repete esta informação para cada bloco de 8 bytes da tabela de atributos do MSX.

A “extract_attr” é quase que integralmente a linha 150 — a exceção é o comando GET já que não estou carregado de um arquivo — enquanto que “translate_color” substitui o cálculo do VPOKE usado na linha 175.

Finalizando esta parte

Relembrando que por ser quase uma transcrição da versão em MSX-BASIC e, apesar de infinitamente mais rápida que esta, ela pode receber alguma otimização mas preferi mantê-la desta forma para ajudar na comparação entre os dois códigos, algo que melhoraria em muito a performance seria utilizar um buffer para fazer a conversão em RAM onde o acesso é muito mais rápido para fazer a cópia para a VRAM.

E isto (quase) conclui a construção do programa que permite visualizar telas do speccy em MSX-DOS está quase concluído! E que falta? Simples, juntar todas as peças que foram desenvolvidas separadamente e será este o assunto da próxima parte.¹

Até!

(¹) E prometo não demorar quase três anos para entregá-la desta vez! 😀