Hard Scroll no MSX 2
Você está em: MarMSX >> Cursos >> Assembly Z80
A partir do MSX 2, é possível fazer o scroll de tela via hardware. No MSX 2, o chip de vídeo V9938 permite rolar a tela verticalmente a uma velocidade fantástica. Um exemplo desse scroll é no jogo Aleste para MSX 2. Já no MSX 2+, o chip de vídeo V9958 permite o scroll vertical e horizontal via hardware.
Mas afinal, qual a diferença entre o scroll via software e via hardware? No scroll via software, devemos mover manualmente todos os pixels de uma região para a outra. Já no scroll por hardware, o próprio chip se encarrega de mover toda a tela em uma direção (inclusive os sprites). Esse tipo de scroll não envolve o deslocamento de pixels, tornando-o sensivelmente mais rápido.
Em ambos os MSXs, o VDP(24) se encarrega de deslocar a tela N pixels para cima. Veja o exemplo a seguir.
10 SCREEN 5
20 OPEN"GRP:" AS #1
30 CIRCLE(128,95),50,15
40 PAINT(128,95),4,15
50 PRESET(72,170),0,TPSET:PRINT#1,"Tecle algo ..."
60 A$=INPUT$(1)
70 VDP(24)=50
80 GOTO 80
Quando movemos a tela na vertical, há uma área "suja" entre as linhas 212 e 255, que são os dados dos sprites [1]. Sim, o scroll via hardware acessa as linhas de 212 a 255, que estão ocultas na exibição de tela normal.
A solução para o problema é executar a sub-rotina da BIOS que limpa todos os sprites (CLRSPR - &H69) e também limpar essa área através do comando COPY [1].
Veja este novo exemplo:
10 SCREEN 5
15 DEFUSR=&H69:X=USR(0)
16 COPY(0,0)-(255,44),0 TO (0,212),0 ' Limpa area
20 CIRCLE(128,105),50,15
30 PAINT(128,95),12,15
40 ' Inicia scroll
50 FOR X=0 TO 255
60 VDP(24)=X
70 NEXT
Uma vez que limpamos os dados dos sprites, como fazer para usá-los com o scroll de tela? Se criarmos novos sprites, não vamos sujar aquela área de novo? Vejamos a solução [2].
Sabemos que o MSX 2 possui 4 páginas de vídeo para as screens 5 e 6, e 2 páginas de vídeo para as screens 7 e 8. Cada página de vídeo possui sua própria tabela de sprites. Dessa forma, poderíamos utilizar a área de sprites de outra página na página atual. Certo?
Bem, a solução é essa sim, mas devemos tomar alguns cuidados. Por exemplo, os comandos SPRITE$(n) e PUT SPRITE modificam as tabelas de sprite de todas as páginas de vídeo. Logo, não podemos utilizá-los, pois voltarão a "sujar" a área de sprites da página 0.
O programa a seguir demonstra como o SPRITE$(n) e o PUT SPRITE modificam todas as tabelas.
10 SCREEN 5
20 OPEN"GRP:" AS#1
30 DEFUSR=&H69:X=USR(0)
40 FOR P=0 TO 3
50 PRESET(52,100),0,TPSET:PRINT#1,"Estamos na pagina";P
60 COPY(0,0)-(255,44),P TO (0,212),P
70 FOR Y=0 TO 255 : VDP(24)=Y : NEXT Y
80 NEXT P
90 SPRITE$(0)=STRING$(8,255)
100 PUT SPRITE 0,(128,15),15,0
110 FOR P=0 TO 3
120 PRESET(52,120),0,TPSET:PRINT#1,"Agora com sprites";P
130 FOR Y=0 TO 255 : VDP(24)=Y : NEXT Y
140 NEXT P
Observamos que, quando os sprites são acionados dessa forma, todas as áreas entre Y=212 e Y=255 de todas as páginas estão "sujas" novamente.
Para solucionar esse problema, teremos que colocar os sprites manualmente na página 1 através do VPOKE. O artigo sobre sprites desse curso entra em detalhes sobre como o sprite é configurado na VRAM.
As tabelas de sprites da página 1 da screen 5 se localizam a partir de &HFxxx da VRAM. Então, vamos criar um desenho de uma bola nessas tabelas e mostrá-lo.
10 SCREEN 5
20 DEFUSR=&H69:X=USR(0)
30 COPY(0,0)-(255,44),0 TO (0,212),0 ' Limpa area
40 CIRCLE(128,105),50,15
50 PAINT(128,95),12,15
65 GOSUB 500
70 ' Inicia scroll
80 FOR X=0 TO 255
90 VDP(24)=X
100 NEXT
110 GOTO 80
497 '
498 ' Desenha sprite
499 '
500 VPOKE &HF800,&B0011100
510 VPOKE &HF801,&B0111110
520 VPOKE &HF802,&B1111111
530 VPOKE &HF803,&B1111111
540 VPOKE &HF804,&B1111111
550 VPOKE &HF805,&B1111111
560 VPOKE &HF806,&B0111110
570 VPOKE &HF807,&B0011100
575 VPOKE &HF600,100 ' Y
580 VPOKE &HF601,128 ' X
590 VPOKE &HF602,0 ' Numero sprite
600 FOR F=&HF400 TO &HF40F:VPOKE F,15:NEXT ' Define cor
610 RETURN
Cade o sprite? Esquecemos alguma coisa? Sim. As tabelas de sprites da página 0 ainda apontam para &H7xxx em vez de &HFxxx.
Assim, devemos mudar a localização das tabelas dos sprites nos registros do VDP [3]:
Registro VDP Controla
#5 5 Endereço LOW da tabela de atributos do sprite
#11 12 Endereço HI da tabela de atributos do sprite
#6 6 Endereço da tabela de padrões de sprite
Entretanto, não passamos o endereço da VRAM com os valores diretamente. Os endereços de memória de vídeo do MSX 2 são números de 17 bits. Por causa disso, foram criados modos peculiares de se passar endereços de VRAM nesses registros. As tabelas a seguir mostram como funcionam os endereçamentos nos registros #5, #6 e #11 [3]:
Registro |
Endereçamento |
#5 |
A14 |
A13 |
A12 |
A11 |
A10 |
1 |
1 |
1 |
#11 |
0 |
0 |
0 |
0 |
0 |
0 |
A16 |
A15 |
#6 |
0 |
0 |
A16 |
A15 |
A14 |
A13 |
A12 |
A11 |
O endereço de 17 bits é formado a partir da combinação dos bits An, conforme mostra o quadro abaixo. Os valores estão agrupados por 8 bits.
A16 A15A14A13A12A11A10A9A8 A7A6A5A4A3A2A1A0
Os registros #5 e #11 controlam os seguintes bits assinalados em vermelho:
A16 A15A14A13A12A11A10A9A8 A7A6A5A4A3A2A1A0
Os demais bits são tratados como 0.
O comando VDP do Basic DEPENDE da página ativa. Assim, para obtermos o endereço das tabelas de sprites de cada página através do VDP, devemos estar localizados na referida página. Exemplo:
10 SCREEN 5
20 SET PAGE 1,1
30 A=VDP(5):B=VDP(12)
40 SCREEN 0
50 PRINT"LOW:";A
60 PRINT"HI:";B
Obtém o endereço da tabela de atributos dos sprites da página 1.
A seguir, são apresentados os valores retornados dos endereços LOW e HI das tabelas de atributos dos sprites das páginas do MSX 2.
Screen Página 0 Página 1 Página 2 Página 3
5/6 0 e 239 1 e 239 2 e 239 3 e 239
7/8 1 e 247 3 e 247
Na página 0 das screens 5/6, o valor HI do endereço é 0, enquanto que o valor LOW é 239 (&b11101111). Assim temos:
16 15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00
0 0 1 1 1 0 1 0 0 0 0 0 0 0 0 0 0
Os valores assinalados em azul correspondem aos bits do HI, enquanto que os valores assinalados em verde correspondem aos bits do LOW.
Convertendo o valor de 17 bits em hexadecimal, temos o endereço &H7400. Esse endereço é o conjunto de tabelas de cores + atributos dos sprites da página 0.
Para o registro #6, que controla o endereço da tabela de padrões dos sprites, o modo de formação do endereço é semelhante. A seguir, são apresentados os valores retornados para os endereços das tabelas de padrões dos sprites para as telas do MSX 2 - VDP(6).
Screen Página 0 Página 1 Página 2 Página 3
5/6 15 31 47 63
7/8 30 62
Na página 1 das screens 5/6, o valor é 31 (&b00011111)
16 15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00
0 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0
Convertendo o valor de 17 bits em hexadecimal, temos o endereço &HF800.
Para evitar a complexidade dos cálculos de endereços dessas tabelas, vamos utilizar os valores padrão para as tabelas de outras páginas. Assim, como desejamos apontar a tabela de sprites da página 0 para as tabelas de sprites da página 1 na screen 5, fazemos:
35 VDP(6)=31: VDP(12)=1: VDP(5)=239
Lá esta ela! Observe que bolinha acompanha o scroll da tela.
Se desejarmos movimentar a bolinha, alteramos o valor de Y na tabela de sprites a cada iteração:
95 VPOKE &HF600,(X+100) MOD 255
Scroll em Assembly
De modo a tornar a animação mais rápida, iremos mover a tela, bem como atualizar a coordenada Y do sprite, em Assembly.
Antes de começar, vamos ver a lista de registros do VDP [3]:
┌──────┬─────────┬──────────────────────────────────────────────────────────┐
│ R#n │ VDP │ Função │
├──────┼─────────┼──────────────────────────────────────────────────────────┤
│ R#0 │ VDP(0) │ mode register #0 │
│ R#1 │ VDP(1) │ mode register #1 │
│ R#2 │ VDP(2) │ pattern name table │
│ R#3 │ VDP(3) │ colour table (LOW) │
│ R#4 │ VDP(4) │ pattern generator table │
│ R#5 │ VDP(5) │ sprite attribute table (LOW) │
│ R#6 │ VDP(6) │ sprite pattern generator table │
│ R#7 │ VDP(7) │ border colour/character colour at text mode │
│ R#8 │ VDP(9) │ mode register #2 │
│ R#9 │ VDP(10) │ mode register #3 │
│ R#10 │ VDP(11) │ colour table (HIGH) │
│ R#11 │ VDP(12) │ sprite attribute table (HIGH) │
│ R#12 │ VDP(13) │ character colour at text blinks │
│ R#13 │ VDP(14) │ blinking period │
│ R#14 │ VDP(15) │ VRAM access address (HIGH) │
│ R#15 │ VDP(16) │ indirect specification of S#n │
│ R#16 │ VDP(17) │ indirect specification of P#n │
│ R#17 │ VDP(18) │ indirect specification of R#n │
│ R#18 │ VDP(19) │ screen location adjustment (ADJUST) │
│ R#19 │ VDP(20) │ scanning line number when the interrupt occurs │
│ R#20 │ VDP(21) │ colour burst signal 1 │
│ R#21 │ VDP(22) │ colour burst signal 2 │
│ R#22 │ VDP(23) │ colour burst signal 3 │
│ R#23 │ VDP(24) │ screen hard scroll │
└──────┴─────────┴──────────────────────────────────────────────────────────┘
O primeiro passo é criar um programa em Assembly para modificar o registro R#23 (ou VDP 24) do VDP.
End Código Mnemônicos
-------------------------------------
5 WRTVDP: EQU &H012D
6 EXTROM: EQU &H015F
10 ORG &HC000
C000 01 17 32 20 LD BC,&H3217 ; Move 50, R#23
C003 DD 21 2D 01 30 LD IX, WRTVRM
C007 CD 5F 01 40 CALL EXTROM
C00A C9 50 RET
Para testar em Basic digite o seguinte programa:
10 SCREEN 5
20 CIRCLE(128,95),50,15
30 FOR T=0 TO 10
40 READ A$:POKE &HC000+T,VAL("&H"+A$)
50 NEXT T
60 DEFUSR=&HC000
70 A$=INPUT$(1)
80 X=USR(0)
90 GOTO 90
500 DATA 01,17,32,DD,21,2D,01
510 DATA CD,5F,01,C9
O deslocamento vertical foi feito em Assembly.
Listagem completa em Assembly para alterar o registro #23 e mover o sprite é apresentada a seguir.
End Código Mnemônicos
----------------------------------------------
012D 5 WRTVDP: EQU &H12D
015F 6 EXTROM: EQU &H015F
0109 7 WRTVRM: EQU &H109
C000 10 ORG &HC000
C000 06 00 15 LD B,0
C002 C5 16 LOOP: PUSH BC
C003 0E 17 20 LD C,&H17 ; Usa o valor B do loop
C005 DD 21 2D 01 30 LD IX,WRTVDP
C009 CD 5F 01 40 CALL EXTROM
C00C 78 50 LD A,B
C00D C6 64 60 ADD A,100 ; Também usa o B do loop,
C00F 21 00 F6 70 LD HL,&HF600 ; mas desloca Y em 100
C012 DD 21 09 01 80 LD IX,WRTVRM
C016 CD 5F 01 90 CALL EXTROM
C019 C1 100 POP BC ; Retorna o valor B do loop
C01A 10 E6 110 DJNZ LOOP
C01C C9 120 RET
O programa em Basic para testar é apresentado a seguir.
10 SCREEN 5
20 DEFUSR=&H69:X=USR(0)
30 COPY(0,0)-(255,44),0 TO (0,212),0 ' Limpa area
35 VDP(6)=31: VDP(12)=1: VDP(5)=239
40 CIRCLE(128,105),50,15
50 PAINT(128,95),12,15
65 GOSUB 500
70 ' Le dados de LM
80 E=&HC000
90 READ A$:IF A$="M" THEN 200
100 POKE E,VAL("&H"+A$)
110 E=E+1 : GOTO 90
200 ' Inicia scroll
210 DEFUSR=&HC000
220 X=USR(0)
230 GOTO 220
497 '
498 ' Desenha sprite
499 '
500 VPOKE &HF800,&B0011100
510 VPOKE &HF801,&B0111110
520 VPOKE &HF802,&B1111111
530 VPOKE &HF803,&B1111111
540 VPOKE &HF804,&B1111111
550 VPOKE &HF805,&B1111111
560 VPOKE &HF806,&B0111110
570 VPOKE &HF807,&B0011100
575 VPOKE &HF600,100 ' Y
580 VPOKE &HF601,128 ' X
590 VPOKE &HF602,0 ' Numero sprite
600 FOR F=&HF400 TO &HF40F:VPOKE F,15:NEXT ' Define cor
610 RETURN
997 '
998 ' Codigo de maquina
999 '
1000 DATA 06,00,C5,0E,17,DD,21,2D,01,CD,5F,01,78
1010 DATA C6,64,21,00,F6,DD,21,09,01,CD,5F,01,C1
1020 DATA 10,E6,C9,M
Compare a velocidade de scroll desse programa com aquele feito todo em Basic. Para isso, acrescente a seguinte linha:
225 FOR Y=255 TO 0 STEP-1:VDP(24)=Y:NEXT
Os scrolls em Basic e em Assembly se alternam.
Podemos inserir um delay para a movimentação se tornar mais lenta. É so acrescentar as seguintes linhas na listagem Assembly:
06 14 95 LD B,20
10 FE 96 DELAY: DJZN DELAY
Altere as linhas do programa em Basic:
1010 DATA C6,64,21,00,F6,DD,21,09,01,CD,5F,01,06
1020 DATA 14,10,FE,C1,10,E2,C9,M
Através do comando "POKE &HC01A, valor", podemos alterar o tempo de delay. Temos:
205 POKE &HC01A,0 ' Mais lento que 255
ou
205 POKE &HC01A,1 ' Mais rápido
Obs: de 1 a 255, depois 0 vai ficando mais lento o scroll.
Referências:
1- Vertical Scroll (in Basic), em https://www.msx.org/forum/development/msx-development/vertical-scroll-basic.
2- Programa Sterren (estrelas). MSX Computer Magazine #30, MBI Publications, 1989.
3- MSX2 Technical Handbook, ASCII Corporation, 1987.