terça-feira, 29 de outubro de 2019

O ecrã do ZX Spectrum

Neste artigo, vamos focar as razões sobre o qual o ecrã do ZX Spectrum foi desenhado com a estranha estrutura que tem.

Esta estrutura invulgar de ecrã foi desenhada para optimização de recursos da máquina, e para tornar rotinas gráficas / jogos mais rápidos. A localização desta em RAM também é destinada a optimizar e tornar mais simples operações de assembly para manusear o ecrã (daqui para a frente, vamos usar notação hexadecimal intencionalmente, para demonstrar vários pontos - note-se que em terminologia Z80, $número denota notação hexadecimal).

Zona de memória de pixels

O endereço do ecrã de 256x192 pixels, usando 6144 bytes, começa a seguir à ROM na posição de RAM $4000 (hexadecimal), e acaba em $57FF. O ecrã é dividido em três secções, nomeadamente $4000, $4800 e $5000, e acaba em $57FF (antes dos atributos que começam em $5800).

Ou seja:

$4000 - início de ecrã e primeira secção
$4800 - segunda secção de ecrã
$5000 - terceira secção de ecrã
$5800 - inicio de zona de atributos

Estes endereços prestam-se bem a fazer testes simples de comparação apenas com o byte alto, i.e., testes eficientes em assembly.

Os pixels estão numa área separada das cores (atributos em terminologia Sinclair). A cada grupo de 8x8 pixels correspondem um atributo (dai o "famoso" problema de colisão de cores), e mais estranhamente ainda, o mapeamento da área de pixels de posicionamento na memória RAM para o ecrã, não é linear.



código fonte em assembly     TAP

A disposição em binário destes endereços é também algo fácil de mostrar graficamente.

Veja-se a área dos pixels, em que vemos a disposição binário das posições X e Y. Note que:
  • Y7 e Y6 definem qual das três secções do ecrã,
  • Y2 a Y0 qual das 8 "linhas"/blocos de "texto" dentro de cada secção 
  • Y5 a Y3 qual das 8 linhas de pixels dentro de bloco
  • X4 a X0 define a coluna/byte de 0 a 31.

Em cada byte definido por essa posição, temos então 8 bits, que definem a 0 ou 1, se o pixel correspondente é visível ou não no ecrã.

Voltando à organização da área de pixels, note-se nas seguintes particularidades com que esta foi desenhada para optimizar o processamento:
  • 1 bit por pixel, 8 pixels por byte
  • teste de inicio de ecrã e da 1a sessão testar por byte alto $40
  • teste da 2a secção e fim da 1a, testar por byte alto $48
  • teste da 3a secção, testar por byte alto $50
  • teste do fim da área de pixels, testar pelo início da área de atributos byte alto $58
  • a posição X entre 0 a 31, manipula-se facilmente incrementando ou decrementando o endereço
  • tendo a ROM antes, que não é passível de escrita, permite ter verificações mais latas em rotinas a sair da zona de video para baixo (isto é algo a ter em atenção a quem esteja a escrever emuladores em que (ainda) deixem escrever na ROM, ou a mapear RAM em cima da ROM em hardware, já que alguns jogos podem tentar escrever nas últimas posições de RAM, principalmente a $FFFE e $FFFF).
A posição da linha inferior de ecrã/pixels dentro do bloco de 8x8, também se obtém incrementando o byte alto, i.e., se for HL, INC H, ou seja uma simples instrução para fazer  transferência de memória rápida para o ecrã de um caracter em ROM ou de um sprite de um jogo.

A titulo de exemplo, a seguinte rotina assembly, vai sempre obter a posição da linha de pixels inferior à que estamos em HL:

INC H ; Go down onto the next pixel line LD A,H ; Gone onto next character boundary?
AND 7
RET NZ ; No, so skip the next bit
LD A,L ; Go onto the next character line
ADD A,32
LD L,A
RET C ; Gone onto next third of screen?
LD A,H ; Yes, so go onto next third
SUB 8
LD H,A
RET

Usando pois esta rotina, teríamos a nossa rotina anterior de preenchimento a comportar-se de uma forma diferente:



código fonte em assembly    TAP

Fazem-se, portanto, menos cálculos aritméticos e transferem-se menos quantidade de dados com a estrutura complexa que o ecrã tem.

Ver aqui mais rotinas para tratamentos do ecrã

Zona de memória de atributos

Quanto à disposição de atributos/cores, esta é bem mais simples, e é a partir de $5800,  usando 768 bytes. A área de atributos / cores começa em $5800 e acaba em $5AFF (antes de $5B00, a área das variáveis de sistema).  Note-se que mais uma vez, por coincidência ou não, fazem-se testes simples em assembly com esta disposição de memória.


A área de memória dos atributos, mapeia-se em cima da área dos pixels em 32 colunas e 24 linhas, definindo cores para um bloco de 8x8 pixels.


Ou seja, o endereço de atributos de um bloco de 8x8 em RAM calcula-se com:

$5800 + linha * 32 + coluna                     (assumindo a 1ª linha e coluna a começar em 0)

Quanto à informação de cores em cada byte de atributo, é a seguinte:


Em cada bit do atributo:
  • F corresponde a Flash (0 ou 1)
  • B a bright (0 ou 1)
  • P2-P0 é a cor de fundo - em ausência de pixels (PAPER, 0-7)
  • I2 a I0 é a cor dos pixels (INK, 0-7).
Note-se que, considerando o bright, o ecrã do Spectrum é capaz de mostrar 16 cores diferentes. 

ver este link para outro artigo sobre atributos

Outras peculiaridades

Existem outras questões interessantes da arquitectura, exploradas para evitar flicker em jogos comerciais, ou produzir efeitos multi-cores no ecrã e no border.

O ecrã começa a ser tratado pela ULA em sincronia com o início de tratamento da rotina de interrupts. Também podemos calcular em que ponto está / quando terminou (ab)usando do comportamento que é conhecido como floating bus do Spectrum. Tais temas poderemos abordar em futuros artigos.

Existem clones de ZX Spectrum que têm modos extra de ecrã, notavelmente os nossos "compatriotas" Timex TC 2048 / TC 2068.

De salientar também que em modo 128K, os modelos de ZX Spectrum 128K +2 e +2A têm um banco de RAM com o vídeo normal (banco 5) e um banco de video shadow (banco 7), com a mesma estrutura que descrevemos.

Como última nota, lembramos que hoje em dia, existem modos mais avançados e com  mais cores em emulações por software ou hardware.

Sem comentários:

Publicar um comentário