Z cyklu "Moja Amiga..."

Blitter

Jego działanie i użycie

Część 1: Przesuw danych

1. Wprowadzenie

Blitter to jednostka wyspecjalizowana w przesuwie (ang. shift) danych zlokalizowanych w pamięci graficznej CHIP komputera Amiga oraz rysowaniu odcinków. Blitter potrafi również - równocześnie podczas swojej głównej pracy - wypełniać obszary na kilka sposobów.

Ten artykuł traktuje tylko o przesuwie danych. O rysowaniu odcinków i wypełnianiu będą odrębne artykuły.

Termin "przesuw danych" to uogólnione określenie działania Blittera. Blitter wyśmienicie nadaje się m.in. do operacji rysujących polegających na kopiowaniu prostokątnych obiektów, również o nieregularnym kształcie.

1.1. Podstawy

Blitter podczas swojej pracy potrafi następujące rzeczy:

Jak się wkrótce przekonasz wszystkie te funkcje będą niezbędne do wykonania podstawowych czynności rysujących.

Każdy z kanałów może wskazywać na inną mapę bitową (ang. bitmap), z których każda może mieć inną szerokość (liczbę bajtów w wierszu - ang. bytes per row) oraz wysokość. Jak wiadomo pamięć graficzna w Amidze składa się z bitplanów, gdzie każdy bit odpowiada za jeden piksel na ekranie. Blitter pracuje na jednostkach pamięci zwanych słowami, gdzie każde słowo to 16 bitów, czyli 16 pikseli.

Blitter może wykonać dowolną operację spośród 256 możliwych. Każdy typ operacji polega na alternatywie bitowej (OR) wybranych koniunkcji bitowych (AND) wszystkich trzech kanałów, bądź ich negacji bitowej (NOT). Oto tabela przedstawiająca poszczególne kombinacje. Kreska nad literą kanału oznacza negację bitową tego kanału:

KanałBit mintermKoniunkcja kanałów
ABC
0000ABC
0011ABC
0102ABC
0113ABC
1004ABC
1015ABC
1106ABC
1117ABC

Obliczenie wartości minterm polega na ustawieniu bitów z odpowiedniej kolumny tabeli. Nas najbardziej będzie interesować następująca funkcja logiczna: AB+AC nazywana po angielsku cookie-cut. Jak wyznaczyć jej wartość minterm? Otóż należy włączyć pozostałe kanały do funkcji, ale tak, by nie wpływały na wynik, czyli w następujący sposób:

AB+AC=AB(C+C)+A(B+B)C= ABC+ABC+ABC+ABC. Teraz ustawiamy bity odczytane z tabeli: %11001010 binarnie = $CA szesnastkowo.

Dlaczego ta operacja jest najciekawsza? Otóż pozwala ona kopiować obiekt o dowolnym kształcie z dowolnej pozycji do dowolnej innej pozycji tak, by tło nie zostało zamazane. Wówczas:

1.2. Podstawowe rejestry Blittera

Najpierw warto nauczyć się podstawowych rejestrów Blittera. Blitter posiada szereg rejestrów, za pomocą których możemy sterować jego działaniem.

Żeby wskazać Blitterowi skąd ma pobrać dane i gdzie umieścić wynik, ustawiamy rejestry BLTxPT, gdzie x to A, B, C i D. Każdy taki rejestr składa się z dwóch słów (opisujących jeden adres pamięci CHIP), ale możemy go załadować za pomocą jednej instrukcji procesora lub dwóch instrukcji koprocesora graficznego Coppera. Każdy adres jest parzysty, gdyż odnosi się do 16-bitowych słów.

Nie musimy ustawiać wszystkich adresów, ponieważ kanały można włączać lub wyłączać. Gdy kanał jest wyłączony, dane nie są z niego pobierane. Jeśli bierze on jednak udział w operacji logicznej, wówczas brana jest jedna wartość stała, którą należy umieścić w rejestrach BLTxDAT po załadowaniu rejestrów kontrolnych (o rejestrach kontrolnych jest mowa poniżej).

W rejestrach kontrolnych umieszcza się m.in. wartość minterm funkcji logicznej, ale też przesunięcia i flagi sterujące.

Żeby rozpocząć pracę Blittera należy ustawić rozmiar operacji. Służy do tego rejestr BLTSIZE oraz dwa rejestry dostępne od wersji ECS chipsetu BLTSIZV (wysokość) i BLTSIZH (szerokość). Szerokość podaje się w 16-bitowych słowach, zaś wysokość w wierszach.

2. Sposób użycia

Poniżej opisuję sposób użycia Blittera w poszczególnych przypadkach. Wszystkie przykłady w tym dziale dotyczą operacji na obszarach nie pokrywających się (nie nachodzących na siebie). W przypadku gdy obszary mają część wspólną, a bitmapa docelowa ma adres wyższy niż bitmapa źródłowa, wówczas należy skorzystać z trybu Descending, w której Blitter działa "od tyłu". Tryb ten zostanie opisany później.

2.1. Proste kopiowanie ikon i kafli

Jako pierwszy przykład podam proste kopiowanie kanału A do kanału D bez przesunięcia i maskowania.

A=A(B+B)(C+C)=ABC+ABC+ABC+ABC. Wartość funkcji logicznej: %11110000 binarnie = $F0 szesnastkowo.

Na początek ustawimy rejestry kontrolne Blittera: BLTCON0 i BLTCON1. Pierwszy z nich odpowiada m.in. za włączenie kanałów. Ustawmy w nim flagi SRCA i DEST odpowiadające odpowiednio za włączenie kanału źródłowego A i kanału docelowego D. W ten rejestr wpisujemy też wartość przesunięcia (w naszym przykładzie 0) oraz minterm, czyli w naszym przypadku $F0 (lub alternatywę stałych symbolicznych ABC | ANBC | ABNC | ANBNC). Do drugiego rejestru kontrolnego wpisujemy w tej chwili wartość $0000.

Ten tryb najlepiej spisuje się, gdy mamy do wklejenia ikony o szerokości będącej wielokrotnością liczby 16. Rejestr adresu kanału źródłowego A BLTAPT ustalamy na adres pierwszego słowa ikony, zaś adres kanału docelowego D BLTDPT na adres pierwszego słowa miejsca, w który chcemy ikonę wkleić.

Następnie musimy ustalić tzw. modulo. Co to jest? Otóż obszary objęte operacją, na które wskazują adresy kanałów mogą być częścią większej całości. Blitter musi wiedzieć o ile bajtów ma zwiększyć adres by dostać się do następnego wiersza z danymi. Jeżeli znamy wartość bytes per row mapy bitowej to wystarczy odjąć od niej szerokość operacji w słowach pomnożoną przez 2, a następnie wstawić tą wartość do odpowiedniego rejestru BLTxMOD, gdzie x to A, B, C i D. Rejestry masek pierwszego (BLTAFWM) i ostatniego (BLTALWM) słowa kanału A ustawiamy na wartości standardowe, czyli $FFFF.

Poniższa tabela obrazuje zagadnienie wyznaczania wartości modulo. W tym przypadku jako szerokość operacji należy wstawić 1 (szerokość operacji liczymy w słowach), zaś jako modulo: 2 słowa * 2 bajty = 4 (modulo liczymy w bajtach). Suma modulo i szerokości ikony daje nam szerokość mapy bitowej. Żeby rozpocząć operację rysowania wpisujemy najpierw wysokość operacji (ilość wierszy objętych operacją) do rejestru BLTSIZV, a następnie długość jednego wiersza danych, objętego operacją kopiowania do BLTSIZH (w naszym przypadku wstawiamy tu liczbę 1).

Szerokość mapy bitowej = 3 słowa = 6 bajtów
Szerokość ikony = 1 słowoModulo = 2 słowa = 4 bajty
Pierwsze słowoDrugie słowoTrzecie słowo
0101 0110 0011 10000001 0000 1110 10110110 1110 0101 1111

Oto przykład ustawiania rejestrów kontrolnych przez procesor dla kopiowania ikon w języku Asembler:

	INCLUDE	"hardware/custom.i"
	INCLUDE	"hardware/blit.i"

	LEA	$DFF000,A0							; Baza rejestrów
	MOVE.W	#($0<<ASHIFTSHIFT)!SRCA!DEST!ABC!ANBC!ABNC!ANBNC,bltcon0(A0)	; Ustawiamy pierwszy rejestr kontrolny
	MOVE.W	#($0<<BSHIFTSHIFT),bltcon1(A0)
	MOVE.W	#$FFFF,D0
	MOVE.W	D0,bltafwm(A0)
	MOVE.W	D0,bltalwm(A0)

2.2. Kopiowanie dowolnych obiektów

Teraz przyszła pora na zasadnicze zagadnienie: kopiowania dowolnych obiektów niekoniecznie dosuniętych do granicy 16-bitowej. Istnieje tutaj kilka sytuacji. Postaram się jasno przedstawić zasady postępowania w tych sytuacjach. Użyjemy opisanej wcześniej, uniwersalnej operacji cookie-cut. Najważniejsze to ustalić:

Podam przykłady i postaram się je zilustrować. Weźmy taki oto wiersz obiektu oraz maskę i sprawdźmy na nim różne warianty. Warto zauważyć, że bity zapalone w obiekcie zawierają się w masce. To normalne, bo tam gdzie bity maski są zgaszone, stamtąd nie będzie brana grafika obiektu, tylko tło.

Maska
0000 0110 1100 11111100 0101 0000 10001110 0101 0110 1110
Obiekt
0000 0010 0100 01101000 0001 0000 10000110 0100 0010 1100
Cel
**** **** **** ******** **** **** ******** **** **** ****

2.2.1. Maskowanie i przesunięcia

W najprostszym przypadku, gdy kopiujemy dane, które rozpościerają się na tyle samo bajtów w źródle jak i miejscu docelowym możemy wyrzucić niechciane bity ze źródła. Przykładowo chcemy skopiować dane od bitu nr 4 (licząc od lewej od jedynki) pierwszego słowa źródła do bitu nr 2 drugiego słowa źródła włącznie (szerokość: 15 pikseli) w miejsce rozpoczynające się od 10 bitu pierwszego słowa celu. Oznakujmy te informacje w tabeli:

Maska
0000 0110 1100 11111100 0101 0000 10001110 0101 0110 1110
Obiekt
0000 0010 0100 01101000 0001 0000 10000110 0100 0010 1100
Cel
**** **** **** ******** **** **** ******** **** **** ****

Ażeby osiągnąć obrany cel musimy zamaskować niechciane bity i przesunąć dane. Oto lista czynnośći, jakie należy podjąć:

  1. Najpierw odrzucamy 3 pierwsze bity maski źródła, czyli wpisujemy wartość $1FFF do rejestru BLTAFWM.
  2. Teraz odrzucamy 14 ostatnich bitów maski źródła, czyli wpisujemy wartość $C000 do rejestru BLTALWM.
  3. Następnie przesuwamy dane w kanałach A i B o 6 bitów (bo tyle wynosi różnica między bitem nr 10 i nr 4) wpisując wartość $6 w odpowiednie miejsce do rejestru BLTCON0 oraz BLTCON1.
  4. Ustalamy rozmiar operacji na 2 słowa szerokości.

Oto wynik operacji. Gwiazdką symbolizowane są bity tła:

Wynik operacji
**** **** ***0 1*01**01 1010 **** ******** **** **** ****

2.2.2. Bit celu wcześniej niż bit źródła

W tej sytuacji bity celu znajdują się na lewo od bitów źródła. O ile zdradzę, że w trybie pracy Descending Blitter może przesuwać w lewo, o tyle w tym przypadku, jeżeli chcemy zachować tryb pracy Ascending (z różnych względów) należy zastosować następującą metodę. Chcemy skopiować dane od piksela nr 9 do piksela nr 15 drugiego słowa źródła (szerokość 7 pikseli) w miejsce rozpoczynające się od bitu nr 1 drugiego słowa celu:

Maska
0000 0110 1100 11111100 0101 0000 10001110 0101 0110 1110
Obiekt
0000 0010 0100 01101000 0001 0000 10000110 0100 0010 1100
Cel
**** **** **** ******** **** **** ******** **** **** ****
  1. Najpierw odrzucamy pierwsze 8 bitów źródła, jak również 1 piksel ostatni, mieszczący się w tym samym słowie, czyli wstawiamy wartość $00FE do rejestru BLTAFWM.
  2. Jako początek celu ustawiamy słowo poprzedzające właściwe słowo, zaś wartość przesunięcia dla kanałów A i B ustalamy na $8 (tak by prawidłowo przesunąć dane do następnego słowa).
  3. W tej sytuacji rejestr BLTALWM zerujemy, ponieważ dotyczy on słowa dodanego.
  4. Co istotne na koniec dodajemy 1 słowo do szerokości operacji (czyli teraz mamy 2 słowa), jak również odejmujemy 2 od modulo źródła.
Wynik operacji
**** **** **** ******** 1*** **** ******** **** **** ****

2.2.3. Długość słowa docelowego mniejsza niż źródłowego

Pozostała nam taka oto ciekawa sytuacja. Jak widać cel zajmuje jedno słowo, podczas gdy dane źródłowe obejmują dwa słowa. W takim przypadku ustawiamy, zgodnie z dotychczas opisanymi zasadami maski pierwszego i ostatniego słowa kanału A, przesunięcia ustalamy na wartość 12 ($C). Za rozmiar bierzemy dwa słowa, jak również ustalamy początek kanału docelowego D na słowo bezpośrednio przed właściwym słowem celu.

Maska
0000 0110 1100 11111100 0101 0000 10001110 0101 0110 1110
Obiekt
0000 0010 0100 01101000 0001 0000 10000110 0100 0010 1100
Wynik operacji
**** **** **** ******** **** 0110 10****** **** **** ****

2.2.3. Długość słowa docelowego większa niż źródłowego

Maska
0000 0110 1100 11111100 0101 0000 10001110 0101 0110 1110
Obiekt
0000 0010 0100 01101000 0001 0000 10000110 0100 0010 1100
Wynik operacji
**** **** **** ******** **** **** ******** **** **** ****