VBXEFXULA:
Biblioteka emulująca mapę atrybutów układu ULA
(wykorzystywanego w komputerach Sinclair ZX Spectrum 48k) za
pomocą VBXE z rdzeniem FX 1.2x.
I. Organizacja ekranu ZX Spectrum
Ekran rysowany jest na ZX Spectrum w oparciu o dwie struktury
ulokowane w pamięci RAM:
- pamięć danych ekranu ($4000..$57FF),
- pamięć atrybutów ($5800..$5AFF).
Dane obrazu:
Pamięć danych ekranu zawiera informację o punktach
wyświetlanych na obrazie. Każdy bajt pamięci przechowuje
informację o 8 punktach linii ekranowej. Bit o wartości 0
oznacza że punkt będzie miał "kolor papieru" (ang. paper color),
bit o wartości 1 - "kolor atramentu" (ang. ink color). Kolejnym
punktom odpowiadają bity uszeregowane od najstarszego do
najmłodszego.
Ekran podzielony jest na linie zawierające kolejne 32 bajty, co
odpowiada 256 kolejnym punktom linii.
Całkowity obszar ekranu liczący 192 linie podzielony jest na 3
bloki (po 64 linie każdy), które położone są w pamięci jeden
po drugim.
Każdy blok z kolei podzielony jest na logiczne wiersze ekranowe
złożone z 8 linii. Adresy danych dla kolejnych linii w obrębie
bloku przyporządkowane są następująco:
Obszar danych pamięci ekranu zajmuje łącznie $1800 bajtów pamięci i
jest ulokowany począwszy od adresu $4000 aż do $57FF.
Adres danej na ekranie:
data address = %01CCAAABBBXXXXX
gdzie:
X - numer bajtu w linii powstały z podzielenia współrzędnej x
piksela przez 8
ABC - numer linii ekranu %CCBBBAAA
Atrybuty:
Pamięć atrybutów zawiera dane o kolorach używanych przez fragmenty
ekranu o rozmiarze 8x8 pikseli.
Inaczej niż pamięć ekranu jest ona zorganizowana w sposób liniowy,
tak więc atrybuty kolejnych wierszy ekranu (złożonych z 8 linii
każdy) następują bezpośrednio po sobie, każdy wiersz natomiast
zawiera atrybuty kolejnych obszarów 8x8 pikseli począwszy od lewej
strony ekranu do prawej.
Adres danej atrybutu:
attribute addres = %010110CCBBBXXXXX
gdzie:
X - numer atrybutu w wierszu (powstały z podzielenia współrzędnej x
piksela przez 8)
BC - numer wiersza ekranu %CCBBB (powstały z podzielenia numeru
linii, czyli współrzędnej y punktu, przez 8)
Pamięć atrybutów zajmuje obszar $300 bajtów i jest ulokowana
począwszy od adresu $5800 aż do $5AFF.
Kolory:
Poszczególne bity w bajcie atrybutu %FBPPPIII odpowiadają za:
- III - kolor atramentu (ang. ink),
- PPP - kolor papieru (ang. paper),
- B - rozjaśnienie (ang. bright),
- F - migotanie (ang. flash).
Każdy punkt na ekranie może przybierać dowolny kolor z palety
8 barw definiowanych za pomocą składowych RGB:
%000 - BLACK #000000
%001 - BLUE #0000CD
%010 - RED #CD0000
%011 - MAGENTA #CD00CD
%100 - GREEN #00CD00
%101 - CYAN #00CDCD
%110 - YELLOW #CDCD00
%111 - WHITE #CDCDCD
Kolor punktu o "kolorze atramentu" (bit w pamięci ekranu o wartości
1) określa składnik III w odpowiadającym mu atrybucie, "kolor
papieru" (bit o wartości 0) określa składnik PPP atrybutu.
Kiedy atrybut zawiera ustawiony składnik F, wtedy składniki PPP i
III zamieniane są z sobą z półsekundowym interwałem, co powoduje
migotanie punktów w obszarze całego atrybutu.
Przykładowo mając zdefiniowany fragment ekranu o rozmiarze 8x8:
i atrybut o wartości:
%10000111
ustalający czarny kolor papieru i biały kolor atramentu spowoduje,
że ULA wyświetli czarno-białą szachownicę, która cyklicznie będzie
zamieniać miejscami barwy punktów:
Składnik B atrybutu powoduje rozjaśnienie wszystkich kolorów w
obrębie atrybutu. Wybierane są one wtedy z jaśniejszej
palety:
%000 - BLACK #000000
%001 - BLUE #0000FF
%010 - RED #FF0000
%011 - MAGENTA #FF00FF
%100 - GREEN #00FF00
%101 - CYAN #00FFFF
%110 - YELLOW #FFFF00
%111 - WHITE #FFFFFF
Kolor ramki ekranu może być wybrany jedynie z palety
podstawowej za pomocą zapisania pierwszych 3 bitów rejestru
I/O o adresie $xxFE.
I. Emulacja układu ULA za pomocą VBXE
Biblioteka przeprowadza emulację mapy atrybutów układu ULA
używając tylko mapy atrybutów oraz funkcji blittera rdzenia FX
karty VBXE.
Przy projektowaniu biblioteki poczyniono pewne założenia:
1. Za rysowanie treści obrazu oraz jego organizację w pamięci
komputera odpowiada wyłącznie ANTIC, natomiast funkcje nakładania
kolorów przejmuje VBXE.
3. Atrybuty ekranu ULA znajdują się w stałym miejscu pamięci w
obszarze $5800..$5AFF.
4. Obszar emulacji atrybutów rozpoczyna się od 32 linii ekranu
(zakładając, że ANTIC kreśli obraz począwszy od linii 8) i rozciąga
się na wąskim ekranie przez kolejne 192 linie.
2. Emulacja nie zajmuje czasu CPU, ponieważ wszystkie operacje
realizowane są za pomocą blittera rdzenia FX.
4. Tryb emulacji można dowolnie włączać lub wyłączać wedle
upodobania.
5. Ilość pamięci zajmowanej przez bibliotekę a niezbędnej do
poprawnej emulacji powinna być jak najmniejsza.
W wersji minimalnej po inicjalizacji biblioteki użytkownik powinien
tylko co pewien czas (np. w przerwaniu VBLK) wywoływać funkcję
uruchamiającą blitter, który powoduje odświeżenie zawartości mapy
atrybutów VBXE na podstawie atrybutów ULA.
Dodatkowo biblioteka umożliwia:
1. Ustawianie koloru ramki.
2. Dowolne włączanie i wyłączanie trybu emulacji.
3. Przeliczanie wartości kolorów ULA na wartości kolorów GTIA
(kiedy tryb emulacji jest wyłączony) z uwzględnieniem używanego
systemu TV (PAL/NTSC).
Dodatkowa funkcjonalność może zostać usunięta (jeśli jest
zbędna) bez szkody dla samej emulacji.
Sama biblioteka nie używa żadnych przerwań pozostawiając ich
wykorzystanie w gestii programisty.
Atrybuty:
Podczas generowania obrazu para ANTIC+GTIA pobiera wartości kolorów
dla punktów odpowiednio z rejestrów COLPF1 (punkt zapalony - 1) i
COLPF2 (punkt zgaszony - 0). W efekcie na całym ekranie monitora
otrzymujemy dwubarwny obraz.
Mapa atrybutów FX pozwala podzielić generowany przez parę
ANTIC+VBXE obraz na mniejsze fragmenty (w naszym przypadku 8x8
punktów) i przedefiniować wartości kolorów dla punktów w
każdym fragmencie z osobna.
Mapa atrybutów FX składa się z 4-bajtowych elementów:
$00: PF0 - indeks koloru dla rejestru COLPF0
$01: PF1 - indeks koloru dla rejestru COLPF1
$02: PF2 - indeks koloru dla rejestru COLPF2
$03: PAL - numer palety używanej przez kolory GTIA
Każdy z takich elementów (zwany atrybutem) pozwala na redefinicję
kolorów używanych do generowania punktów obrazu w obszarze 8x8.
Ponieważ w trybie hi-res używane są wyłącznie kolory
COLPF1 i COLPF2, blitter na podstawie atrybutów ULA ustawia
wyłącznie pola PF1 i PF2 atrybutu indeksami kolorów ULA.
Atrybuty zorganizowane są w matrycę 32x24 o rozmiarze 8x8
pikseli hi-res i pokrywają obszar wąskiego ekranu ANTIC-a (256
punktów w linii) począwszy od 32 linii ekranowej (pierwsza linia
display list rysowana jest w 8 linii ekranu) przez 192 kolejne
linie.
Mapa atrybutów FX zajmuje w pamięci VBXE obszar $00000..$00BFF.
Kolory:
Wszystkie kolory GTIA (czyli COLPMx, COLPFx oraz COLBAK) wybierane
są z palety VBXE o numerze 3. Paleta ta zawiera definicje
wszystkich kolorów ULA w indeksach:
%.BPPPIII
gdzie:
B - rozjaśnienie
PPP - kolor papieru
III - kolor atramentu
Emulacja używa wyłącznie 30 kolorów o indeksach:
- %0x000xxx - dla atrybutów "koloru atramentu"
- %0xxxx000 - dla atrybutów "koloru papieru"
czyli:
- $00..$07, $08, $10, $18, $20, $28, $30, $38 oraz
- $40..$47, $48, $50, $58, $60, $68, $70, $78.
Pozostałe kolory w palecie mogą być użyte do innych celów
(np. do kolorowania PMG czy ramki).
Flash:
Ustawienie składowej F atrybutu ULA powoduje okresową zamianę
kolorów atramentu z kolorem papieru skutkując naprzemiennym
migotaniem punktów w obszarze atrybutu.
Program blittera zlicza ilość wywołań procedury
odświeżającej i cyklicznie powoduje zamianę wartości rejestrów
kolorów PF1 i PF2 w odpowiednim atrybucie FX.
Mając więc początkowo:
$00: PF0
$01: PF1 - %00000000 - indeks koloru czarnego w palecie VBXE
$02: PF2 - %00111000 - indeks koloru białego w palecie VBXE
$03: PAL
po upływie pół sekundy zawartość atrybutu FX zmieni
się na:
$00: PF0
$01: PF1 - %00111000
$02: PF2 - %00000000
$03: PAL
by po upływie kolejnej połowy sekundy powrócić do stanu
początkowego.
Funkcjonalność ta realizowana jest przez
bibliotekę całkowicie za pomocą programu blittera, a
interwał zmian wyznaczony jest na 16 wywołań programu blittera
(jeśli odbywa się to co ramkę, wtedy co 16 ramek).
Blitter:
Jak już wspomniano za pomocą blittera realizowane są dwa
zadania:
- konwersja atrybutów ULA na atrybuty FX,
- zamiana kolorów atrybutu FX przy ustawionej składowej F
atrybutu ULA.
Zanim nastąpi opis ich realizacji, warto
przedstawić garść informacji pozwalających poznać
specyfikę blittera VBXE i jego możliwości.
Każdy program blittera złożony jest z co najmniej jednego bloku
BCB. Struktura BCB opisuje zadanie przesłania danych z jednego
obszaru pamięci VRAM do innego, oraz operacje które podczas każdego
przesłania zostaną wykonane po pobraniu bajtu z obszaru źródłowego
i przy zapisie do obszaru docelowego.
Uproszczona struktura BCB:
$00..$02: srcad
$03..$04: srcdy
$05: srcdx
$06..$08: dstad
$09..$0A: dstdy
$0B: dstdx
$0C..$0D: width
$0E: height
$0F: and
$10: xor
$11..$13: ---
$14: mode
gdzie:
- srcad - adres źródłowy
- srcdx - odstęp między bajtami linii bloku źródłowego
- srcdy - odstęp między liniami bloku źródłowego
- dstad - adres docelowy
- dstdx - odstęp między bajtami linii bloku docelowego
- dstdy - odstęp między liniami bloku docelowego
- width - szerokość przesyłanego bloku danych
- height - wysokość przesyłanego bloku danych
- and - maska dla operacji AND na bajcie źródłowym
- xor - wartość dla operacji XOR na bajcie
źródłowym
- mode - tryb pracy blittera oraz znacznik końca listy BCB
Pozostałe parametry bloku BCB nie są istotne z punktu widzenia
operacji biblioteki, więc ich opis zostanie pominięty - można go
zresztą znaleźć w podręczniku programisty VBXE.
Kolejne bloki BCB w liście ułożone są jeden za drugim i
wykonywane przez blitter sekwencyjnie począwszy od bloku wskazanego
przez rejestr BLADR ($D650) aż do bloku oznaczonego, jako
ostatni.
Ponieważ blitter został zaprojektowany z myślą o
kopiowaniu obszarów dwuwymiarowych na ekranie graficznym, dlatego
przesłania odbywają się na blokach o podanej szerokości
oraz wysokości (rozłożonych jednakże w jednowymiarowej pamięci
VRAM).
Przetwarzanie danych odbywa się linia po linii aż do
przetworzenia wszystkich danych źródłowych - ich ilość określa
wzór:
operation count = width * height
Po pobraniu danej blitter wykonuje na niej kolejno dwie binarne
operacje maskujące:
1. AND ze stałą and,
2. XOR ze stałą xor.
Teraz na obliczonej wartości w miejscu docelowym wykonywana jest
jeszcze operacja określona w polu mode:
0 - COPY - zapis,
1 - OVER - zapis tylko jeśli wartość niezerowa,
2 - ADD - suma arytmetyczna i zapis,
3 - OR - binany OR i zapis,
4 - AND - binarny AND i zapis,
5 - XOR - binarny XOR i zapis.
Następnie blitter oblicza kolejne adresy danej źródłowej i
docelowej po czym powtarza wszystkie kroki aż do przetworzenia
wszystkich danych.
UWAGA!
W trybie OVER blitter zapisuje w miejscu docelowym wyliczoną
wartość _tylko_wtedy_ kiedy jest ona różna od 0 - w przeciwnym
wypadku wartość zostaje utracona a bajt w miejscu docelowym
nie ulega zmianie!
Zamiana kolorów:
Mruganie kolorów w obrębie atrybutu odbywa się z interwałem co
16 wywołań programu blittera. Musi on więc za każdym razem kiedy
podejmuje pracę zwiększać licznik FX_FLASHCNTR po czym na
podstawie jego wartości ustalić znacznik odwracania kolorów w
atrybutach ULA, które mają ustawiony komponent F.
UWAGA!
Dla zwiększenia czytelności opisu w blokach BCB pominięto pola nie
wpływające na realizowane zadania.
Samo zadanie zwiększania licznika odbywa się za
pomocą następującego BCB:
bcb_flashadvance:
FX_FLASHCNTR:
?srcad .long 0
?srcdy .word 0
?srcdx .byte 0
?dstad .long FX_FLASHCNTR
?dstdy .word 0
?dstdx .byte 0
?width .word 1-1
?height .byte 1-1
?and .byte %00000000
?xor .byte 8
?mode .byte ADDMODE
Pole and ma wartość 0, dzięki czemu dowolna dana źródłowa po
wykonaniu AND da w wyniku 0. Blitter optymalizuje
swoją pracę i w tym wypadku nawet nie pobiera danej z
pamięci, lecz bierze wartość bezpośrednio z pola xor (0 XOR V daje
w wyniku V).
Taki blok zawsze więc realizuje operację określoną polem
mode ze stałą podaną w polu xor. Stąd też nie ma
znaczenia zawartość pól srcad, srcdx oraz srcdy.
Stała wartość (8) dodawana (ADD) jest do bajtu pod adresem
FX_FLASHCNTR i tamże zapisywana. Po przekroczeniu wartości 255
następuje przepełnienie licznika FX_FLASHCNTR i zaczyna on znowu
zliczać w górę od 0.
Ten BCB powoduje przetwarzanie tylko jednego bajtu danych na co
wskazują pola width i height (rzeczywiste rozmiary muszą
być w BCB zmniejszane o 1).
Zadanie określania znacznika zamiany kolorów atrybutu FX_FLASHFLAG
realizuje następujący blok BCB:
bcb_flashflag:
?src .long FX_FLASHCNTR
?srcdy .word 0
?srcdx .byte 0
?dst .long FX_FLASHFLAG
?dstdy .word 0
?dstdx .byte 0
?width .word 1-1
?height .byte 1-1
?and .byte %10000000
?xor .byte %00000000
?mode .byte COPYMODE
Zawartość FX_FLASHCNTR jest maskowana z
wartością %10000000 i zapisywana w FX_FLASHFLAG.
Bit 7 FX_FLASHCNTR zmienia swój stan co 16 wywołań programu
blittera ponieważ:
1. Licznik zwiększany jest o 8 co każde wywołanie.
2. Przepełnienie licznika następuje co 256 / 8 = 32 wywołania.
3. Zmiana wartości bitu 7 występuje dokładnie w połowie, czyli po
32 / 2 = 16 wywołaniach blittera.
Modyfikując stałą inkrementacji licznika FX_FLASHCNTR można
wpłynąć zatem na interwał, z którym zmieniać się będzie
flaga FX_FLASHFLAG.
Konwersja atrybutów:
Zadanie konwersji atrybutów jest nieco bardziej złożone głównie ze
względu na konieczność uwzględnienia zamiany kolorów w
atrybutach.
Sama idea obliczania atrybutów FX na podstawie atrybutów ULA
zasadza się na określeniu indeksu koloru w palecie FX osobno
dla "koloru atramentu" i osobno dla "koloru papieru".
Ponieważ za kolory odpowiednich pikseli
odpowiadają rejestry COLPF1 oraz COLPF2, to w atrybucie FX
należy po prostu ustawić indeks koloru bazując na składowych III
oraz PPP atrybutu ULA (z dodatkiem składowej B).
Jak poprzednio wspomniano w palecie FX zdefiniowano jedynie 30
kolorów. Indeksy tych kolorów w palecie odpowiadają zamaskowanym
składowym:
- "koloru atramentu": %01000111
a więc indeksom $00..$07 oraz $40..$47,
- i "koloru papieru": %01111000
czyli indeksom:
- $00, $08, $10, $18, $20, $28, $30 i $38, oraz
- $40, $48, $50, $58, $60, $68, $70 i $78.
Tak więc kompletne zadanie konwersji atrybutów potrafiłyby wykonać
dwa bloki BCB zdefiniowane tak:
bcb_copyink:
?src .long FX_ULA_ATR
?srcdy .word 32
?srcdx .byte 1
?dst .long FX_ATR+PF1 ;COLPF1
?dstdy .word 4*32
?dstdx .byte 4*1
?width .word 32-1
?height .byte 24-1
?and .byte %01000111
?xor .byte %00000000
?mode .byte COPYMODE
bcb_copypaper:
?src .long FX_ULA_ATR
?srcdy .word 32
?srcdx .byte 1
?dst .long FX_ATR+PF2 ;COLPF2
?dstdy .word 4*32
?dstdx .byte 4*1
?width .word 32-1
?height .byte 24-1
?and .byte %01111000
?xor .byte %00000000
?mode .byte COPYMODE
Sprawę jednak komplikuje składowa F atrybutu ULA ponieważ
zależnie od jej stanu do odpowiednich pól atrybutu FX
odpowiadających za kolory COLPF1 i COLPF2 musi raz
trafić odpowiednio "kolor atramentu" oraz "kolor papieru",
innym razem zaś odwrotnie - "kolor papieru" oraz "kolor
atramentu".
Ponadto zamiana odbywa się dynamicznie - przez 16 wywołań
programu blittera kolory nie mogą być odwracane nawet jeśli w
atrybucie ULA składowa F jest ustawiona, a przez kolejne 16 wywołań
muszą zostać odwrócone tylko dla atrybutów z ustawionym F
(dla pozostałych muszą pozostać nieodwrócone).
Przyda się więc teraz znacznik FX_FLASHFLAG.
Zadanie polega na obliczeniu dwóch obszarów maskujących odpowiednie
bity atrybutu ULA zależnie od stanu składowej F atrybutu ULA i
wartości znacznika FX_FLASHFLAG.
Na początek ustalmy wartości masek dla "koloru atramentu" tak, jak
gdyby na całym ekranie nie była używana składowa F:
bcb_maskinkfill:
?src .long 0
?srcdy .word 0
?srcdx .byte 0
?dst .long FX_ATR+PF1 ;COLPF1
?dstdy .word 4*32
?dstdx .byte 4*1
?width .word 32-1
?height .byte 24-1
?and .byte %00000000
?xor .byte %10111111
?mode .byte COPYMODE
Powód dla którego maska ustalona jest na %10111111 (czyli tak,
jakby miały zostać przeniesione obydwie składowe III oraz PPP,
a co gorsza F!) zostanie wyjaśniony za moment.
Następnie należy określić, które atrybuty ULA mają
ustawioną składową F.
bcb_maskinkmake:
?src .long FX_ULA_ATR
?srcdy .word 32
?srcdx .byte 1
?dst .long FX_ATR+PF1 ;COLPF1
?dstdy .word 4*32
?dstdx .byte 4*1
?width .word 32-1
?height .byte 24-1
FX_FLASHFLAG:
?and .byte %10000000
?xor .byte %00000000
?mode .byte OVERMODE
Składowa F znajduje się na ostatnim bicie atrybutu ULA, więc
and ma wartość %10000000.
Blitter zamaskuje składową F i skopiuje wynik do pola COLPF1
atrybutu FX.
UWAGA!
Tryb OVER, jak wspomniano wcześniej, zapisze daną w miejscu
docelowym _wyłącznie_wtedy_ gdy ma ona wartość niezerową!
Tak więc w atrybutach FX zostaną zmodyfikowane tylko pola,
które mają w odpowiednich atrybutach ULA _ustawiony_bit_F_!
Cała reszta pozostanie bez zmian.
W efekcie pola COLPF1 atrybutów FX będą miały tylko dwie możliwe
wartości:
- %10111111 gdy F=0
- %10000000 gdy F=1
Ponieważ w polu COLPF1 chcemy mieć:
- %01000111, czyli "kolor atramentu" wtedy kiedy kolory
mają być nieodwrócone,
- %01111000, czyli "kolor papieru" kiedy kolory muszą być
odwrócone
to aby otrzymać żądany wynik wystarczy na wartościach pola COLPF1
wykonać operację XOR z wartością %11111000, co
też robi właśnie następny blok BCB:
bcb_maskinkinvert:
?src .long 0
?srcdy .word 0
?srcdx .byte 0
?dst .long FX_ATR+PF1 ;COLPF1
?dstdy .word 4*32
?dstdx .byte 4*1
?width .word 32-1
?height .byte 24-1
?and .byte %00000000
?xor .byte %11111000
?mode .byte XORMODE
Skoro w polu COLPF1 mamy maskę dla odpowiedniego "koloru" atrybutu
ULA, to w COLPF2 powinna znaleźć się maska dla "koloru"
przeciwnego. Należy więc tylko odwrócić bity maskujące składniki i
zapisać je w polach COLPF2:
bcb_maskpapermake:
?src .long FX_ATR+PF1 ;COLPF1
?srcdy .word 4*32
?srcdx .byte 4*1
?dst .long FX_ATR+PF2 ;COLPF2
?dstdy .word 4*32
?dstdx .byte 4*1
?width .word 32-1
?height .byte 24-1
?and .byte %11111111
?xor .byte %00111111
?mode .byte COPYMODE
Maski mamy zatem przygotowane - pozostało przenieść wartości
atrybutów ULA do pól COLPF1 i COLPF2 i zamaskować je wyliczonymi
wartościami dla "kolorów" ULA.
bcb_attrink:
?src .long FX_ULA_ATR
?srcdy .word 32
?srcdx .byte 1
?dst .long FX_ATR+PF1 ;COLPF1
?dstdy .word 4*32
?dstdx .byte 4*1
?width .word 32-1
?height .byte 24-1
?and .byte %11111111
?xor .byte %00000000
?mode .byte ANDMODE
bcb_attrpaper:
?src .long FX_ULA_ATR
?srcdy .word 32
?srcdx .byte 1
?dst .long FX_ATR+PF2 ;COLPF2
?dstdy .word 4*32
?dstdx .byte 4*1
?width .word 32-1
?height .byte 24-1
?and .byte %11111111
?xor .byte %00000000
?mode .byte ANDMODE
Gotowe!
Warto jeszcze wyjaśnić gdzie znajduje się w pamięci znacznk
FX_FLASHFLAG.
Kiedy przyjrzeć się bliżej blokowi BCB odpowiadającemu za
przenoszenie składników F atrybutów ULA do pól COLPF1 atrybutów
FX:
bcb_maskinkmake:
?src .long FX_ULA_ATR
?srcdy .word 32
?srcdx .byte 1
?dst .long FX_ATR+PF1 ;COLPF1
?dstdy .word 4*32
?dstdx .byte 4*1
?width .word 32-1
?height .byte 24-1
FX_FLASHFLAG:
?and .byte %10000000
?xor .byte %00000000
?mode .byte OVERMODE
można dojrzeć, że znacznik FX_FLASHFLAG został umieszczony w polu
and bloku BCB. Mamy więc do czynienia z kodem
samomodyfikowalnym!
Taka sztuczka pozwala za pomocą jednego bloku BCB
przenieść wszystkie atrybuty ULA z ustawionym F pod
równoczesnym warunkiem, że znacznik FX_FLASHFLAG
_też_każe_odwracać_ kolory w atrybucie (FX_FLASHFLAG=%10000000 oraz
zamaskowany F również ma wartość %10000000)!
Gdzie zatem znajduje się FX_FLASHCNTR?
bcb_flashadvance:
FX_FLASHCNTR:
?srcad .long 0
?srcdy .word 0
?srcdx .byte 0
?dstad .long FX_FLASHCNTR
?dstdy .word 0
?dstdx .byte 0
?width .word 1-1
?height .byte 1-1
?and .byte %00000000
?xor .byte 8
?mode .byte ADDMODE
Ten blok dodaje stałą z pola xor do wyniku, więc nie ma
znaczenia zawartość pól srcad, srcdx i srcdy, a skoro tak to
pierwszy bajt srcad wykorzystany został właśnie do przechowania
wartości licznika.
Pamięć:
Biblioteka rezerwuje do swoich potrzeb pamięć VBXE w obszarach:
$00000..$00BFF - atrybuty FX
$00C00..$00C0E - XDL
$00C0F..$00CB6 - program blittera
Te obszary są wykorzystywane wyłącznie przez VBXE i w trakcie
działania emulacji nie są nigdy widoczne w pamięci Atari.
Zupełnie inaczej ma się rzecz z obszarem:
$05800..$05AFF - atrybuty ULA
Jest on włączony na stałe w obszarze $4000..$7FFF pamięci Atari
poprzez okno MEMACB VBXE jako bank 1 ($04000..$07FFF) i jest
widoczny dla CPU oraz ANTIC-a dzięki czemu użytkownik może
wykorzystywać tę pamięć do dowolnych celów.
Atrybuty ULA w pamięci Atari widoczne są również w
obszarze $5800..$5AFF.
UWAGA!
Wyłączenie emulacji nie powoduje odłączenia okna MEMACB,
gdyż mogłoby to skutkować zniknięciem danych zapisanych przez
użytkownika i pojawieniem się w tym miejscu zwykłej pamięci
RAM Atari.
Biblioteka wykorzystuje banki 0 i 1 do własnych celów, aby
niezależnie od użytego wariantu rdzenia FX (wersje A lub R) nie
ryzykować kolizji z emulowanym rozszerzeniem RAMBO (i
potencjalnego zniszczenia hipotetycznego ramdysk-u).
Interfejs:
Biblioteka wyposażona jest w zestaw podstawowych procedur
umożliwiających jej konfigurację i sprawne działanie.
Inicjalizacja biblioteki:
Po załadowaniu biblioteki do pamięci należy przeprowadzić jej
inicjalizację za pomocą bezparametrowej funkcji:
ula_init
Powoduje ona wykonanie następujących działań:
1. Ustalana jest mapa kolorów GTIA zależnie od systemu TV
(PAL/NTSC).
2. Przeprowadzana jest detekcja VBXE z rdzeniem FX.
3. Tworzona jest paleta kolorów VBXE mapująca kolory ULA.
4. Tworzona jest mapa atrybutów VBXE o rozmiarze 32x24 pola 8x8
pikseli.
5. Tworzona jest XDL podkładająca atrybuty na obszarze ekranu o
rozmiarze 256x192 piksele hi-res.
6. Tworzony jest program blittera w pamięci VBXE.
7. Włączany jest bank 1 VBXE w obszarze $4000..$7FFF widoczny przez
CPU i ANTIC.
8. Inicjalizowany jest program blittera i XDL oraz włączane
wyświetlanie obrazu przez VBXE.
Po powrocie znacznik Z informuje o pomyślnym wykonaniu zadania
(Z=0), co oznacza że system emulacji jest gotowy do działania. W
przeciwnym wypadku (Z=1) nie wykryto VBXE lub rdzenia FX.
Na stronie zerowej znajdują się dwa rejestry:
- vbxefx - znacznik włączenia systemu emulacji ULA ($FF-włączony,
$00-wyłączony),
- vbxead - adres basowy rejestrów VBXE ($0000 - brak VBXE lub
rdzenia FX).
UWAGA!
Obydwa rejestry są niezbędne do działania procedur
biblioteki.
Po wykonaniu kod inicjalizacyjny nie jest już potrzebny i można go
z powodzeniem usunąć z pamięci Atari.
Aktualizacja atrybutów:
Najważniejszą funkcją biblioteki jest:
ula_refresh
Uruchamia blitter VBXE realizujący odświeżanie zawartości atrybutów
FX na podstawie atrybutów ULA.
Procedura ta powinna być wywoływana okresowo n.p. na
przerwaniu VBLK.
Jeżeli tryb emulacji jest włączony przez cały czas działania
programu, to jest to jedyna funkcja, która jest niezbędna do
funkcjonowania emulacji. Pozostałe mogą zostać usunięte z
pamięci RAM.
Włączenie/wyłączenie emulacji:
W przypadku kiedy zachodzi potrzeba dezaktywacji emulacji ULA
należy wywołać procedurę:
ula_off
Wyłącza ona ekran VBXE oraz ustawia flagę vbxefx, która
automatycznie blokuje uruchamianie blittera przy wywoływaniu
ula_refresh.
UWAGA!
Funkcja nie odłącza okna MEMAC, gdyż groziłoby to zniknięciem
danych użytkownika (a być może programu obsługi
jakiegoś przerwania).
Aktywacja emulacji odbywa się poprzez wywołanie:
ula_on
która włącza ekran VBXE i odblokowuje uruchamianie blittera przy
wywołaniach ula_refresh.
Manipulacja kolorem
Rejestry COLPMx, COLPFx i COLBAK układu GTIA
przechowują informacje o kolorze. Kiedy emulacja ULA jest
wyłączona znajdują się tam informacje o kolorze GTIA:
%CCCCLLLL
gdzie:
- CCCC - barwa
- LLLL - jasność (w trybie hi-res najmłodszy bit jest
ignorowany)
Wartości te są zależne od używanego systemu TV (PAL/NTSC).

PAL
NTSC
Podczas włączonej emulacji, kiedy VBXE zajmuje
się generowaniem koloru w tych rejestrach znajduje
się indeks koloru z palety 3 odzwierciedlającej wartości
kolorów w atrybutach ULA:
%.BPPPIII
gdzie, jak opisano wcześniej, wykorzystywane są jedynie
kombinacje:
Aby uniknąć niewłaściwej interpretacji kolorów przez GTIA i VBXE
należy po każdym przełączeniu trybu emulacji ULA
przeliczyć wartości kolorów w rejestrach GTIA za
pomocą funkcji:
ula_color
przyjmującej w A numer koloru ($0..$F), a zwracającej też w A
wartość, którą należy umieścić w rejestrze koloru.
UWAGA!
Ze względu na rozbieżność kolorów w paletach FX i GTIA
ustawienie kolorów ULA w rejestrach GTIA (COLPMx, COLPFx i COLBAK)
spowoduje wyświetlanie innych barw na wyjściu RGB (VBXE) a innych
na wyjściu monitorowym oraz telewizyjnym Atari (GTIA).
Ostatnia funkcja biblioteki służy do ustawiania koloru ramki:
ula_border
i przyjmuje w A numer koloru do ustawienia ($0..$F) nie zwracając
żadnego wyniku. Powoduje ona ustawienie wartości koloru w
rejestrach COLBAKS ($02C8) i COLBAK ($D01A).
Włączanie biblioteki do własnych programów:
Archiwum z biblioteką zawiera pliki:
- hardware.icl - deklaracje rejestrów systemowych używanych przez
bibliotekę
- vbxefxula.icl - stałe deklarowane przez bibliotekę
- vbxefxula.asx - kod źródłowy biblioteki
- fxulademo.asx - kod źródłowy przykładu wykorzystującego
bibliotekę
- lookcat.scr - przykładowy obrazek używany przez
fxulademo
- fxulademo.xex - kod wynikowy przykładu
- vbxefxula.txt - opis biblioteki
Źródło biblioteki zostało przygotowane do assemblacji za
pomocą MADS-a 2.0.3 dostępnego na stronie http://mads.atari8.info/
Aby włączyć bibliotekę do własnego programu należy wykonać
kilka kroków:
1. Włączyć pliki deklaracji:
icl "hardware.icl"
icl "vbxefxula.icl"
2. Zdefiniować adresy bazowe i włączyć źródła biblioteki:
ula_zpg = $80 ;rejestry ZPG
ula_lib = $2000 ;kod biblioteki
icl "vbxefxula.asx"
3. Zainicjalizować bibliotekę:
jsr ula_ini
4. Zdefiniować display list ANTIC-a n.p. z ekranem o
organizacji zgodnej z ZX Spectrum:
EMP8 = $70
LMS = $40
GFXF = $f
JVB = $41
dlist:
:3 .byte EMP8
.rept 192
.byte LMS|GFXF
.word A8_SCR+(((# & %111) << 8)
| ((# & %111000) << 2)
| (# & %11000000) << 5)
.endr
.byte JVB
.word dlist
A8_SCR to stała zdefiniowana w pliku vbxefxula.icl i określa adres
pamięci ekranu w RAM-ie Atari zgodnie z jej położeniem w ZX
Spectrum 48k.
5. Włączyć wąski ekran i display list:
DMACTS = $22f
DLPTRS = $230
lda #%00100001
ldx #< dlist
ldy #> dlist
sta DMACTS
stx DLPTRS
sty DLPTRS+1
6. Zdefiniować handler VBLK uruchamiający blitter VBXE:
JEXITVB = $e462
vblkint:
jsr ula_refresh
jmp JEXITVB
7. Zainstalować handler VBLK:
VBLKD = 7
JSETVBV = $e45c
ldy #< vblkint
ldx #> vblkint
lda #VBLKD
jsr JSETVBV
8. Modyfikować atrybuty ULA w pamięci:
lda #%10111000
sta A8_ATR
A8_ATR to stała zdefiniowana w pliku vbxefxula.icl i określa adres
pamięci atrybutów w RAM-ie Atari zgodnie z jej położeniem w ZX
Spectrum 48k.
Program przykładowy
Program przykładowy prezentuje rysunek lookcat.scr przeniesiony z
ZX Spectrum 48k, którego autorem jest Yerzmyey/H-PRG.
Pod obrazkiem znajduje się linia informacyjna zawierająca
nazwę programu oraz ilość linii skanningowych, które
zostały narysowane w trakcie wykonania programu blittera (pomiar
odbywa się kiedy jest włączony tryb emulacji,
ponieważ tylko wtedy blitter pracuje).
Kod demonstruje użycie:
- okresowego przełączania trybu emulacji,
- efektu mrugania atrybutów,
- cyklicznej zmiany koloru ramki w obydwu trybach,
- pomiaru czasu pracy blittera (poprzez wykorzystanie przerwań DLI,
BLIT oraz nie podłączonego przetwornika POT7 do zliczania linii
skanningowych w celu osiągnięcia większej dokładności pomiaru niż
przy użyciu VCOUNT).
Czas realizacji programu blittera obrazowany jest dwoma
metodami:
1. Wysokością białego paska nad rysunkiem.
2. Liczbą wyświetlaną w postaci szesnastkowej w prawej
części linii informacyjnej pod rysunkiem.
Blitter uruchamiany jest na przerwaniu DLI, a moment ten
sygnalizowany jest zmianą koloru ramki na biały. Realizuje to
następujący fragment kodu:
; dlist interrupt routine
dliint:
bit vbxefx
bpl ?ret
pha
tya
pha
; start ULA refresh and scanlines measurment
lda #%00000000
sta SKCTL
jsr ula_refresh
lda #%00000011
sta SKCTL
sta POTGO
; change of background color to indicate start of blitter work
lda #ULA_WHITE
jsr ula_color
sta COLBAK
pla
tay
pla
?ret rti
Za pomocą zapisu do rejestru POTGO ($D20B) uruchamiany jest
również pomiar czasu, jaki zajmie realizacja programu
blittera.
Po zakończeniu programu blitter generuje przerwanie IRQ obsługiwane
za pomocą kodu:
; blitter interrupt routine
irqint:
bit vbxefx
bpl ?skip1
pha
tya
pha
ldy #IRQ_STATUS
lda (vbxead),y
beq ?skip2
; reset blitter IRQ
sta (vbxead),y
; restore background color to indicate end of blitter work
lda COLBAKS
sta COLBAK
; print number of scanlines passed during blitter work
lda POT7
ldy #29
jsr prnhex
pla
tay
pla
rti
?skip2 pla
tay
pla
?skip1 jmp (store.?vimirq)
Następuje wtedy odczytanie licznika POT7 ($D207), który zlicza
ilość linii ekranowych od momentu jego uruchomienia
(odbywającego się zapisem do POTGO) i zasygnalizowanie
zakończenia pracy blittera przywróceniem koloru ramki.
Kilku słów wyjaśnienia może wymagać metoda użyta do pomiaru czasu
wykonania programu blittera.
Obliczenie czasochłonności procesów zazwyczaj odbywa się z
użyciem rejestru VCOUNT ($D40B) udostępniającego informację o
numerze aktualnie rysowanej przez ANTIC linii ekranowej. Niestety
wadą tego rozwiązania jest to, że VCOUNT zwiększany jest co
drugą linię ekranową, więc nie bardzo nadaje się do pomiaru
krótkotrwałych procesów.
Licznik POT7 jest w rzeczywistości rejestrem przetwornika ADC,
który zlicza czas ładowania kondensatora aż do osiągnięcia wartości
napięcia referencyjnego na jego wejściu. Takich przetworników jest
w Atari 8, a ponieważ akurat przetworniki POT4..POT7 nie są w
serii XL/XE nigdzie podłączone, odpowiadające im liczniki liczą w
nieskończoność (dokładnie od 0 do 228) z okresem pojedynczej linii
skanningowej (przy wykorzystaniu tzw. "trybu wolnego").
Uruchamiając więc przy starcie blittera licznik POT7 zapisem do
POTGO i czytając jego wartość po zakończeniu pracy blittera
uzyskujemy pomiar dokładniejszy niż w przypadku wykorzystania
rejestru VCOUNT (przy założeniu, że wykonanie programu blittera nie
potrwa dłużej niż 228 linii skanningowych).
Assemblacja programu przykładowego za pomocą MADS-a:
$ mads -o:fxulademo.xex fxulademo.asx
Gorąco zachęcam do analizy kodu źródłowego biblioteki, oraz
programu przykładowego. W razie niejasności, jakichkolwiek pytań
lub znalezionych błędów proszę o kontakt
pocztą elektroniczną na adres
mono@atari.pl
Wady:
Jako, że nie istnieje program doskonały należy na koniec wspomnieć
o wadach przedstawionego rozwiązania wynikających z przyjętych
założeń:
1. Ponieważ mapa atrybutów przetwarzana jest w całości, co
zajmuje czas równoważny rysowaniu około 12 linii ekranowych,
biblioteka _nie_nadaje_się_ do wykorzystania kiedy program
modyfikuje atrybuty ULA w trakcie rysowania obrazu (programowe
tryby graficzne pozwalające uzyskać inne kolory atrybutu w każdej
linii ekranowej).
2. Różnice w paletach kolorów VBXE i GTIA prowadzą do wyświetlania
różnych obrazów przez wyjście monitorowe i telewizyjne (GTIA) oraz
wyjście RGB (VBXE).
3. Obszar atrybutów ULA zmapowany jest w stałym miejscu pamięci
Atari, co może utrudniać nieco organizację danych i
programu.
Wady te wynikają z przyjętych uproszczeń i powinny zostać
zniwelowane w przyszłych wersjach biblioteki.
Literatura:
1. Tomasz Piórek: "Podręcznik programisty VBXE" -
spiflash.org/block/15.html
2. Krzysztof Kuryłowicz, Dariusz Madej, Krzysztof Marasek -
"Przewodnik po ZX Spectrum":
www.worldofspectrum.org/infoseekid.cgi?id=2000592