Urządzenie HardDoom™

Grafika w grze Doom

Rzutowanie dowolnej geometrii trójwymiarowej na dwuwymiarową płaszczyznę jest dość skomplikowane obliczeniowo. Jednym z ważniejszych elementów jest obliczenie współczynnika perspektywy dla każdego rysowanego piksela – wymaga to obliczenia odwrotności pierwiastka kwadratowego. Jest to bardzo kosztowna operacja i wykonanie jej dla każdego piksela ekranu jest niemożliwe przy zachowaniu rozsądnej ilości ramek na sekundę, nawet dla najnowocześniejszych i napotężniejszych procesorów i486, nie mówiąc już o procesorach i386 używanych obecnie przez większość graczy.

Gra Doom zalicza się do tzw. gier 2.5D – choć grafika wygląda na trójwymiarową (i jest bardzo realistyczna), użyta geometria oraz punkt widzenia są bardzo ograniczone. Użytkownik może patrzeć tylko przed siebie (a nie w górę czy w dół), a w świecie gry spotkamy tylko 3 typy obiektów:

  • pionowe ściany – po zrzutowaniu na ekran, każda pokryta kolumna pikseli składa się z pikseli o równym współczynniku perspektywy, pozwalając na wyliczenie go tylko raz na każdą kolumnę.
  • poziome płaszczyzny (podłogi i sufity) – po zrzutowaniu na ekran, każda pokryta linia pikseli składa się z pikseli o równym współczynniku perspektywy.
  • obiekty ruchome, rysowane przez skopiowanie prosto na ekran przeskalowanych tzw. sprite’ów, czyli przygotowanych przez autorów zdjęć, zrobionych z 8 różnych kątów.

Główna praca rysowania świata gry spada na dwie gorące funkcje:

  • R_DrawColumn – rysuje kolumnę pikseli z podanej kolumny tekstury i z podanym współczynnikiem skalowania. Używane do rysowania ścian, obiektów oraz grafiki interfejsu.
  • R_DrawSpan – rysuje poziomy pasek pikseli, teksturując podaną teksturą płaszczyzny (wycinając z tej tekstury linię pod dowolnym kątem i nakładając ją na rysowany pasek).

Urządzenie HardDoom™ dostarcza sprzętową implementację powyższych funkcji, przejmując większość obciążenia z głównego procesora i pozwalając na grę w wysokich rozdzielczościach (płynna rozgrywka na 640×480!) nawet na starszych procesorach. Oprócz tych funkcji, urządzenie dostarcza również kilka pomniejszych funkcji pomocniczych, pozwalając na rysowanie całej grafiki gry na urządzeniu.

Urządzenie

Urządzenie jest podłączane do komputera przez szynę PCI – identyfikator producenta to 0x0666, a identyfikator urządzenia to 0x1993.

Urządzenie nie posiada własnej pamięci i operuje na buforach w głównej pamięci komputera przez bezpośredni dostęp do pamięci (DMA). Ponieważ główna pamięć komputera podlega fragmentacji, a bufory mogą być całkiem duże (urządzenie obsługuje 2048×2048 pikseli), urządzenie wykonuje dostęp do większości buforów przez tablicę stron w swoim własnym formacie.

Urządzeniem steruje się przez rejestry wejścia/wyjścia zmapowane do pamięci (MMIO). Ten obszar MMIO jest pierwszym i jedynym obszarem BAR używanym przez urządzenie (BAR0). Urządzenie wykorzystuje również jedną linię przerwania PCI.

Obszar MMIO ma wielkość 4kiB, ale tylko niektóre bajty z tego zakresu są używane na rejestry. Wszystkie udokumentowane rejestry są 32-bitowe w formacie little-endian i powinny być używane tylko przez wyrównane 32-bitowe odczyty i zapisy.

Urządzenie sterowane jest w większości przez wysyłanie mu poleceń. Każde polecenie jest 32-bitowym słowem, z którego wysokie 6 bitów to typ polecenia, a niskie 26 bitów to parametry polecenia. Polecenia mogą być wysyłane bezpośrednio przez rejestr MMIO (jest to mniej wydajna opcja), bądź przez wskazanie bufora w pamięci, z którego urządzenie powinno wczytywać polecenia (bardziej wydajna opcja). W przypadku wysłania niepoprawnego polecenia, wyzwalane jest przerwanie FE_ERROR, a blok FE zostanie wyłączony w rejestrze ENABLE.

Bufory i blok stronicowania

Operacje rysowania używają następujących buforów (opisanych bardziej szczegółowo niżej):

  • bufory ramki (“surface”) – stronicowane, zmiennego rozmiaru.
  • tekstury kolumnowe (“texture”) – stronicowane, zmiennego rozmiaru.
  • tekstury płaskie (“flat”) – niestronicowane, zawsze rozmiaru 2**12 bajtów i wyrównane do 2**12 bajtów.
  • mapy kolorów (“colormap”) – niestronicowane, zawsze rozmiaru 2**8 bajtów i wyrównane do 2**8 bajtów.

Urządzenie pisze tylko do bufora ramki – pozostałe bufory są używane tylko do odczytu.

Bufory stronicowane wybiera się przez podanie adresu ich tabeli stron. W każdej chwili wybrane są 3 bufory stronicowane:

  • SURF_DST: bufor ramki, do którego urządzenie będzie rysować.
  • SURF_SRC: bufor ramki, z którego urządzenie będzie kopiować dane (dla polecenia COPY_RECT).
  • TEXTURE: tekstura kolumnowa, z której urządzenie będzie rysować (dla polecenia DRAW_COLUMN).

Do zmiany wybranych buforów stronicowanych służą następujące polecenia:

Polecenia SURF_DST_PT, SURF_SRC_PT, TEXTURE_PT:

Ustawia adres tabeli stron opisującej dany bufor. Pola:

  • bity 0-25: ADDR – bity 6-31 adresu początkowego tabeli stron (bity 0-5 są zawsze równe 0).
  • bity 26-31: TYPE – typ polecenia, równy:
    • 0x20: SURF_DST_PT
    • 0x21: SURF_SRC_PT
    • 0x22: TEXTURE_PT

Wykonanie tego polecenia resetuje jednocześnie odpowiedni bufor TLB. Wykonanie polecenia TEXTURE_PT czyści jednocześnie pamięć podręczną tekstury kolumnowej.

Urządzenie używa stron o wielkości 4kiB. Tabele stron mogą zawierać maksymalnie 1024 wpisy (czyli opisywać 4MiB pamięci) i muszą być wyrównane do 64 bajtów. Wpisy tabeli stron mają 4 bajty i odpowiadają bezpośrednio kolejnym stronom bufora. Wpisy tabeli stron są 32-bitowymi słowami o następującym formacie:

  • bit 0: VALID – jeśli ustawiony, wpis jest poprawny i może być użyty. Jeśli nie jest ustawiony, próba użycia wpisu spowoduje błąd PAGE_FAULT i wyłączenie odpowiedniego bloku w ENABLE (XY dla SURF_*, TEX dla TEXTURE).
  • bity 1-11: nieużywane, powinny być równe 0.
  • bity 12-31: ADDR – bity 12-31 adresu fizycznego strony.

Rozmiar tabeli stron nie jest nigdzie podawany – urządzenie w żaden sposób nie sprawdza, czy użyty offset w buforze zmieści się w tabeli stron. Sterownik powinien zapewnić, że tabela będzie odpowiednio duża (maksymalny możliwy offset wynika z SURF_DIMS w przypadku bufora ramki, TEXTURE_DIMS w przypadku tekstury).

Aby nie czytać ciągle tabeli stron, urządzenie może zapamiętywać dane z tabeli stron w buforach TLB. Wysłanie polecenia *_PT czyści bufory TLB odpowiadające danemu buforowi, a użycie bitu TLB w rejestrze RESET czyści wszystkie bufory TLB.

W razie wystąpienia błędu PAGE_FAULT, urządzenie można zrestartować przez wpisanie odpowiedniej wartości w tabelę stron, skasowanie TLB, oraz włączenie z powrotem odpowiedniego bloku w ENABLE – kontynuuje wtedy pracę od momentu w którym trafiło na błąd strony.

Bufory niestronicowane wybiera się bezpośrednio przez podanie ich adresu.

Bufory ramek

Bufor ramki to obszar w pamięci służący do rysowania. Jest to po prostu dwuwymiarowa tablicą pikseli. Ponieważ urządzenie było projektowane zanim wynaleziono kolor 24-bitowy (albo nawet 16-bitowy), każdy piksel jest po prostu bajtem – odpowiadającym jakiemuś kolorowi z palety (obsługą palety zajmuje się urządzenie wyświetlające i nie musimy się nią przejmować). Urządzenie obsługuje bufory ramki o dowolnej dodatniej szerokości podzielnej przez 64 i nie większej niż 2048 piksele. Wysokość natomiast może być dowolna z zakresu 1 .. 2048. Bufor ramki jest prostą tablicą bajtów – adres piksela (x, y) wewnątrz bufora to po prostu x * width + y.

Polecenie SURF_DIMS:

Ustawia wymiary buforów ramek. Pola:

  • bity 0-5: WIDTH – bity 6-11 szerokości ramek (bity 0-5 szerokości są równe 0).
  • bity 8-19: HEIGHT – wysokość ramek.
  • bity 6-7, 20-25: nieużywane, powinny być równe 0.
  • bity 26-31: TYPE – typ polecenia, równy 0x26.

Po wysłaniu ustawień buforów ramek, urządzenie używa ich dla wszystkich następnych poleceń rysowania, aż do wysłania innych ustawień.

Urządzenie sprawdza współrzędne pikseli, których używa – przekroczenie szerokości lub wysokości bufora spowoduje błąd SURF_DST_OVERFLOW lub SURF_SRC_OVERFLOW i wyłączenie bloku XY w ENABLE.

Tekstury kolumnowe

Tekstury kolumnowe zawierają dane obrazu, który polecenie DRAW_COLUMN będzie rysowało do bufora ramki (po przeskalowaniu i przetworzeniu). Mają dość skomplikowany format i składają z tekseli pogrupowanych w kolumny (które nie muszą pokrywać całej powierzchni tekstury – tekstury kolumnowe mogą mieć dziury). Urządzenie nie przejmuje się dokładnym formatem tych tekstur – znalezienie w nim początku odpowiedniej kolumny jest zadaniem silnika gry. Tekstury kolumnowe mogą mieć dość dowolny rozmiar (w praktyce od kilkudziesięciu bajtów do kilkudziesięciu kilobajtów, zazwyczaj ok. 2 kiB). Urządzenie obsługuje tekstury o wielkościach do 4MB.

Polecenie TEXTURE_DIMS:

Ustawia rozmiar tekstury. Pola:

  • bity 0-9: HEIGHT – wysokość tekstury w tekselach, jeśli tekstura ma być powtarzana w pionie (współrzędne w kolumnie tekstury będą brane modulo wysokość) bądź 0, jeśli tekstura nie ma być powtarzana.
  • bity 12-25: SIZE_M1 – rozmiar tekstury w 256-bajtowych blokach, pomniejszony o 1 (dla tekstury 1024-bajtowej należy tu wpisać 3).
  • bity 10-11: nieużywane, powinny być równe 0.
  • bity 26-31: TYPE – typ polecenia, równy 0x27.

Próba czytania adresu z poza zakresu 0 .. SIZE_M1 << 8 | 0xff zostanie zablokowana przez urządzenie i spowoduje użycie do teksturowania tekseli zerowych.

Urządzenie może zapamiętywać dane z używanej tekstury w swojej pamięci podręcznej – aby wyczyścić tą pamięć, wystarczy wysłać polecenie TEXTURE_PT (to zresetuje pamięć podręczną nawet, jeśli nie zmieni się przez to adres aktywnej tabeli stron).

Tekstura (i wiele innych parametrów) nie jest w żaden sposób używana w przypadku rysowania z opcją FUZZ.

Note

Aby nie mylić pojęć z odpowiadającymi pojęciami w buforach ramki, w grafice komputerowej przyjęło się nazywać współrzędne w teksturze U oraz V (zamiast X i Y), a pojedynczy element – tekselem (zamiast piksela).

Tekstury płaskie

Tekstury płaskie są używane przez polecenie DRAW_SPAN do rysowania podłóg i sufitów. Mają zawsze wymiary 64×64 teksele, zajmują 2**12 bajtów, a ich adres również musi być wyrównany do 2**12 bajtów. Nie używają bloku stronicowania. W każdym momencie aktywna jest dokładnie jedna tekstura płaska, którą można zmienić poleceniem:

Polecenie FLAT_ADDR:

Ustawia aktywną teksturę płaską i czyści pamięć podręczną tekstury płaskiej. Pola:

  • bity 0-19: ADDR – bity 12-31 adresu tekstury (bity 0-11 adresu są zawsze równe 0).
  • bity 20-25: nieużywane, powinny być równe 0.
  • bity 26-31: TYPE – typ polecenia, równy 0x23.

Analogicznie do tekstury kolumnowej, urządzenie może zapamiętywać dane z tekstury płaskiej w pamięci podręcznej. Wysłanie polecenia FLAT_ADDR czyści tą pamięć.

Mapy kolorów

Mapy kolorów są po prostu tablicami mapującymi kolory z palety na inne kolory – mają przez to zawsze dokładnie 256 bajtów. Nie są stronicowane, a ich adres musi być wyrównany do 256 bajtów. Są używane do wielu efektów:

  • zamiana palety kolorów, aby umożliwić użycie tej samej tekstury do kilku wersji kolorystycznych (tzw. palette swap) – w końcu pamięć jest droga
  • ściemnianie kolorów (do symulacji słabego oświetlenia)
  • zmiana skali kolorów (np. przefarbowanie wszystkiego na niebiesko pod wodą)

Urządzenie ma dwie aktywne mapy kolorów, które można zmienić poleceniami:

Polecenie COLORMAP_ADDR, TRANSLATION_ADDR:

Ustawia aktywną mapę kolorów i czyści odpowiadającą pamięć podręczną. Pola:

  • bity 0-23: ADDR – bity 8-31 adresu mapy (bity 0-7 adresu są zawsze równe 0).
  • bity 24-25: nieużywane, powinny być równe 0.
  • bity 26-31: TYPE – typ polecenia:
    • 0x24: COLORMAP_ADDR – ustawia główną mapę kolorów, używaną do efektów.
    • 0x25: TRANSLATION_ADDR – ustawia mapę kolorów używaną do zamiany palety.

Podobnie do tekstur, urządzenie może kopiować dane map do pamięci podręcznej. Wysłanie polecenia *_ADDR czyści zawartość odpowiadającej pamięci.

Ustawione mapy kolorów są używane przez polecenia DRAW_COLUMN i DRAW_SPAN tylko wtedy, gdy ustawione są odpowiednie flagi – COLORMAP lub FUZZ dla mapy COLORMAP_ADDR, TRANSLATE dla mapy TRANSLATION_ADDR.

Blok rysujący

Blok rysujący potrafi wykonywać następujące operacje:

  • COPY_RECT: kopiuje prostokąt z jednego bufora ramki do drugiego.
  • FILL_RECT: wypełnia prostokąt w buforze ramki jednolitym kolorem.
  • DRAW_LINE: rysuje linię między podanymi punktami jednolitym kolorem.
  • DRAW_BACKGROUND: wypełnia cały bufor ramki powtarzającymi się kopiami tekstury płaskiej.
  • DRAW_COLUMN: rysuje teksturowaną kolumnę pikseli.
  • DRAW_SPAN: rysuje teksturowany pasek pikseli.

Parametry

Następujące polecenia służą do ustawiania parametrów używanych przez wiele typów operacji:

Polecenie FILL_COLOR:

Ustawia kolor wypełnienia dla FILL_RECT i/lub kolor linii dla DRAW_LINE.

  • bity 0-7: COLOR – kolor
  • bity 8-25: nieużywane, powinny być równe 0.
  • bity 26-31: TYPE – typ polecenia, równy 0x28.
Polecenie DRAW_PARAMS:

Ustawia parametry rysowania dla operacji DRAW_COLUMN oraz DRAW_SPAN.

  • bit 0: FUZZ – używany tylko przez DRAW_COLUMN, włącza rysowanie efektu cienia, używając mapy kolorów z COLORMAP_ADDR do przyciemniania pikseli już znajdujących się w buforze ramki. Jeśli włączony, flagi TRANSLATION i COLORMAP powinny być wyłączone, a tekstura i związane z nią parametry nie są używane.
  • bit 1: TRANSLATE – włącza zamianę palety kolorów. Jeśli włączony, kolory z tekstury są tłumaczone przez mapę kolorów z TRANSLATION_ADDR.
  • bit 2: COLORMAP – włącza mapowanie rysowanych kolorów. Jeśli włączony, kolory z tekstury są tłumaczone przez mapę kolorów z COLORMAP_ADDR (po TRANSLATION_ADDR, jeśli obydwie mapy są włączone).
  • bity 3-25: nieużywane, powinny być równe 0.
  • bity 26-31: TYPE – typ polecenia, równy 0x29.
Polecenie XY_A, XY_B:

Ustawia współrzędne punktu będącego parametrem operacji.

  • bity 0-10: X
  • bity 12-22: Y
  • bity 11, 23-25: nieużywane, powinny być równe 0.
  • bity 26-31: TYPE – typ polecenia:
    • 0x2a: XY_A – pierwszy (lub jedyny) punkt.
    • 0x2b: XY_B – drugi punkt.
Polecenia USTART, VSTART:

Ustawia współrzędne w teksturze używane dla skrajnie lewego piksela paska (dla DRAW_SPAN) bądź górnego piksela kolumny (dla DRAW_COLUMN).

  • bity 0-25: COORD – współrzędna w teksturze dla pierwszego rysowanego piksela. Liczba stałoprzecinkowa – 10 wysokich bitów to część całkowita, a 16 niskich to część ułamkowa. Nie jest używana, jeśli flaga FUZZ jest włączona.
  • bity 26-31: TYPE – typ polecenia:
    • 0x2c: USTART – współrzędna U w teksturze (używane przez DRAW_COLUMN i DRAW_SPAN).
    • 0x2d: VSTART – współrzędna V w teksturze (używane tylko przez DRAW_SPAN).
Polecenia USTEP, VSTEP:

Ustawia pochodną współrzędnych w teksturze względem współzędnej x lub y w buforze ramki.

  • bity 0-25: DELTA – pochodna współrzędnej w teksturze względem x (DRAW_SPAN) lub y (DRAW_COLUMN) w buforze ramki. Liczba stałoprzecinkowa – 10 wysokich bitów to część całkowita, a 16 niskich to część ułamkowa. Nie jest używana, jeśli flaga FUZZ jest włączona.
  • bity 26-31: TYPE – typ polecenia:
    • 0x2e: USTEP – pochodna współrzędnej U w teksturze (używane przez DRAW_COLUMN i DRAW_SPAN).
    • 0x2f: VSTEP – pochodna współrzędnej V w teksturze (używane tylko przez DRAW_SPAN).

Każdy wysłany parametr jest zapamiętywany do momentu nadpisania go przez kolejne polecenie tego samego typu – jeśli wykonujemy kilka operacji z taką samą wartością danego parametru, nie ma potrzeby wysyłac go wielokrotnie.

Operacja COPY_RECT

Polega na skopiowaniu zadanego prostokąta z jednego bufora ramki (SURF_SRC_PT) do drugiego (SURF_DST_PT). Obydwa bufory ramki muszą być tych samych rozmiarów. Jej parametry to: pozycja lewego górnego rogu prostokąta docelowego (XY_A), pozycja lewego górnego rogu prostokąta źródłówego (XY_B), wymiary prostokąta (podane przy wywołaniu operacji).

Polecenie COPY_RECT:

Wykonuje operację kopiowania prostokąta.

  • bity 0-11: WIDTH – szerokość prostokąta.
  • bity 12-23: HEIGHT – wysokość prostokąta.
  • bity 24-25: nieużywane, powinny być równe 0.
  • bity 26-31: TYPE – typ polecenia, równy 0x30.

Operacja COPY_RECT nie powinna być wykonywana, gdy kopiujemy wewnątrz jednego bufora, a prostokąt źródłowy ma część wspólną z prostokątem docelowym – wynik jest wtedy niedeterministyczny.

Warning

Urządzenie może wczytywać dane źródłowe dla COPY_RECT asynchronicznie do procesu rysowania. Aby zapewnić, że urządzenie zakończyło wcześniejesze operacje rysowania do źródłowego prostokąta zanim zacznie z niego czytać, należy wysłać polecenie INTERLOCK (opisane niżej).

Operacja jest używana w grze do skopiowania przygotowanego wcześniej panelu dolnego oraz do efektu przejścia (“melt”).

Operacja FILL_RECT

Polega na wypełnieniu zadanego prostokąta w buforze ramki (SURF_DST_PT) jednym kolorem. Jej parametry to: pozycja lewego górnego rogu prostokąta (XY_A), wymiary prostokąta, kolor (FILL_COLOR).

Polecenie FILL_RECT:

Wykonuje operację wypełniania prostokąta.

  • bity 0-11: WIDTH – szerokość prostokąta.
  • bity 12-23: HEIGHT – wysokość prostokąta.
  • bity 24-25: nieużywane, powinny być równe 0.
  • bity 26-31: TYPE – typ polecenia, równy 0x31.

Jest używana w grze do rysowania tła mapy (dostępnej pod klawiszem Tab).

Operacja DRAW_LINE

Rysuje jednokolorową linię prostą o szerokości 1 piksela między podanymi dwoma punktami w zadanym buforze ramki (SURF_DST_PT). Jej parametry to: pozycje punktu początkowego (XY_A) i końcowego (XY_B) oraz kolor (FILL_COLOR).

Polecenie DRAW_LINE:

Rysuje linię.

  • bity 0-25: nieużywane, powinny być równe 0.
  • bity 26-31: TYPE – typ polecenia, równy 0x32.

Jest używana w grze do rysowania mapy (dostępnej pod klawiszem Tab).

Operacja DRAW_BACKGROUND

Wypełnia cały bufor ramki (SURF_DST_PT) powtarzającymi się kopiami tekstury płaskiej (FLAT_ADDR). Nie przyjmuje parametrów.

Polecenie DRAW_BACKGROUND:

Rysuje powtarzające się tło.

  • bity 0-25: nieużywane, powinny być równe 0.
  • bity 26-31: TYPE – typ polecenia, równy 0x33.

Jest używana w grze do rysowania tła do ekranu opcji, tła do tekstu “fabuły” wyświetlanego po ukończeniu niektórych poziomów, oraz jako obramowanie ekranu w przypadku wybrania pola widzenia mniejszego niż ekran (klawisz -).

Operacja DRAW_COLUMN

Polecenie DRAW_COLUMN:

Rysuje teksturowaną kolumnę do bufora ramki (SURF_DST_PT). Używa parametrów XY_A, XY_B, DRAW_PARAMS (i odpowiednich map kolorów). Jeśli flaga FUZZ nie jest włączona, używa również USTART, USTEP i aktywnej tekstury kolumnowej.

  • bity 0-21: COLUMN_OFFSET – offset początku danych kolumny w teksturze w bajtach. Nieużywane, jeśli flaga FUZZ jest ustawiona.
  • bity 22-25: nieużywane, powinny być równe 0.
  • bity 26-31: TYPE – typ polecenia, równy 0x34.

Polecenie rysuje kolumnę pikseli do bufora ramki na współrzędnych XY_A.X, XY_A.Y do XY_B.X, XY_B.Y. XY_A.X powinno być równe XY_B.X, a XY_A.Y powinno być mniejsze od lub równe XY_B.Y (inaczej zachowanie urządzenia jest niezdefiniowane).

Jeśli flaga FUZZ jest włączona, urządzenie rysuje efekt cienia używając mapy kolorów z COLORMAP_ADDR i nie używa tekstury (ani żadnych parametrów z nią związanych), W przeciwnym wypadku, rysowana kolumna jest teksturowana za pomocą następującego algorytmu:

# Obliczenie koloru dla piksela (x, y).
coord = (USTART + USTEP * (y - XY_A.Y)) >> 16
if TEXTURE_DIMS.HEIGHT != 0:
    coord %= TEXTURE_DIMS.HEIGHT
tex_offset = COLUMN_OFFSET + coord
if tex_offset < 0 or tex_offset > TEXTURE_DIMS.SIZE_M1:
    color = 0;
else:
    color = read_paged(TEXTURE_PT, tex_offset)
if TRANSLATE:
    color = TRANSLATION_ADDR[color]
if COLORMAP:
    color = COLORMAP_ADDR[color]

Operacja DRAW_SPAN

Polecenie DRAW_SPAN:

Rysuje teksturowany pasek do bufora ramki (SURF_DST_PT). Używa parametrów XY_A, XY_B, DRAW_PARAMS (bez bitu FUZZ), USTART, VSTART, USTEP, VSTEP, aktywnej tekstury płaskej i odpowiednich map kolorów.

  • bity 0-25: nieużywane, powinny być równe 0.
  • bity 26-31: TYPE – typ polecenia, równy 0x35.

Polecenie rysuje pasek pikseli do bufora ramki na współrzędnych XY_A.X, XY_A.Y do XY_B.X, XY_B.Y. XY_A.Y powinno być równe XY_B.Y, a XY_A.X powinno być mniejsze od lub równe XY_B.X (inaczej zachowanie urządzenia jest niezdefiniowane).

Rysowany pasek jest teksturowany za pomocą następującego algorytmu:

# Obliczenie koloru dla piksela (x, y).
u = (USTART + USTEP * (x - XY_A.X)) >> 16 & 0x3f
v = (VSTART + VSTEP * (x - XY_A.X)) >> 16 & 0x3f
color = FLAT_ADDR[u | v << 6]
if TRANSLATE:
    color = TRANSLATION_ADDR[color]
if COLORMAP:
    color = COLORMAP_ADDR[color]

Synchronizacja

Blok rysujący działa wysoce asynchronicznie i równolegle. W szczególności, urządzenie może:

  • wczytywać nowe polecenia przed zakończeniem wykonania poprzednich.
  • wczytywać dane źródłowe dla polecenia przed zakończeniem wykonywania poprzednich poleceń (należy na to uważać przy operacji COPY_RECT!).
  • rozpocząć rysowanie pikseli dla nowej operacji przed zakończeniem poprzedniej operacji (ale tylko, jeśli operacje te operują na rozłącznych pikselach w buforze ramki).
  • opóźnić rozpoczęcie wykonywania operacji do momentu wysłania kolejnych poleceń (aby spróbować połączyć je w większą paczkę wewnętrznych operacji).

Mamy jednak następujące gwarancje:

  • każda operacja będzie używała stanu parametrów (w tym adresów buforów) z momentu jej rozpoczęcia.
  • jeśli wyślemy polecenie resetujące pamięć podręczną bądź bufor TLB, każda operacja wysłana później będzie widziała dane wczytane już po wykonaniu tego resetu.
  • dla konkretnego piksela w docelowym buforze ramki, operacje będą wykonywane zgodnie z kolejnością wysyłania (czyli wygra ostatni zapis).
  • polecenie COPY_RECT wczyta wszystkie swoje dane źródłowe przed rozpoczęciem rysowania późniejszych poleceń.
  • urządzenie nie może mieć więcej niż 2048 poleceń FENCE w locie (wczytanych do FIFO, ale jeszcze nie wykonanych) – dopóki nasz bufor poleceń jest mniejszy niż 2**26 - 2048, nie musimy się przejmować, że skończą nam się wolne identyfikatory do FENCE.

W praktyce, daje to efekty identyczne do wykonania sekwencyjnego, z jednym ważnym wyjątkiem – operacja COPY_RECT może wczytać piksele danego bufora ramki zanim poprzednie operacje zdążą je zapisać. Aby uniknąć tego problemu, należy wykryć taką możliwość w sterowniku i użyć następującego polecenia:

Polecenie INTERLOCK:

Blokuje rozpoczęcie wczytywania danych przez późniejsze polecenia COPY_RECT do momentu pełnego zakończenia wcześniejszych operacji rysujących.

  • bity 0-25: nieużywane, powinny być równe 0.
  • bity 26-31: TYPE – typ polecenia, równy 0x3f.

Aby wszystko działało poprawnie, wystarczy zapewnić, że między operacją rysowania do danego bufora a operacją COPY_RECT czytającą z niego znajdzie się co najmniej jedno polecenie INTERLOCK.

Do elastycznego oczekiwania na wykonanie operacji przez urządzenie służy następujące polecenie i rejestry MMIO:

Polecenie FENCE:

Zapewnia pełne wykonanie wszystkich wysłanych wcześniej poleceń, a następnie zapisuje do rejestru FENCE_LAST podany parametr. Jednocześnie z tym zapisem, jeżeli podany parametr jest równy wartości rejestru FENCE_WAIT, wyzwala przerwanie FENCE. Pola:

  • bity 0-25: VALUE – dowolna 26-bitowa wartość.
  • bity 26-31: TYPE – typ polecenia, równy 0x3c.
BAR0 + 0x10: FENCE_LAST
Rejestr do odczytu i zapisu, 26-bitowy (wysokie 6 bitów jest nieużywane i zawsze równe 0). Polecenie FENCE zapisuje tutaj swój parametr.
BAR0 + 0x14: FENCE_WAIT
Rejestr do odczytu i zapisu, 26-bitowy (tak jak FENCE_LAST). Gdy zakończy się polecenie FENCE z parametrem równym wartości tego rejestru, zostanie wyzwolone przerwanie FENCE.

FENCE_WAIT pozwala na zaawansowaną implementację oczekiwania na wysłane wcześniej polecenie FENCE. Przykładowy mechanizm użycia wygląda następująco:

  • po każdym poleceniu, na które potencjalnie możemy chcieć oczekiwać, wysyłamy polecenie FENCE z kolejnymi liczbami (modulo 2**26).
  • nie dopuszczamy, by kiedykolwiek było więcej niż 2**26 poleceń FENCE “w locie” (w razie potrzeby czekając na zakończenie poprzednich poleceń przed wysłaniem kolejnego).
  • jeśli w pewnym momencie uznamy, że chcemy oczekiwać na wykonanie wysłanego wcześniej polecenia:
    • czyścimy przerwanie FENCE.
    • ustawiamy FENCE_WAIT na numer polecenia, na które chcemy oczekiwać.
    • czytamy FENCE_LAST i sprawdzamy, czy polecenie już się wykonało.
    • jeśli się nie wykonało, czekamy na wyzwolenie przerwania FENCE.

Przed rozpoczęciem pracy, sterownik może zainicjować FENCE_LAST na pożądaną wartość początkową (prawdopodobnie 0). Pisanie do tego rejestru, gdy urządzenie jest już aktywne, jest prawdopodobnie złym pomysłem.

Sterownik ma również do dyspozycji proste polecenia wyzwalające przerwania:

Polecenie PING_SYNC:

Zapewnia zakończenie wykonania wszystkich wcześniejszych poleceń, po czym wyzwala przerwanie PONG_SYNC.

  • bity 0-25: nieużywane, powinny być równe 0.
  • bity 26-31: TYPE – typ polecenia, równy 0x3d.
Polecenie PING_ASYNC:

Wyzwala przerwanie PONG_ASYNC, gdy tylko zostanie wczytane z kolejki przez blok rysowania.

  • bity 0-25: nieużywane, powinny być równe 0.
  • bity 26-31: TYPE – typ polecenia, równy 0x3e.

Wysyłanie poleceń

Bezpośredni dostęp

Polecenia można wysyłać do urządzenia bezpośrednio przez MMIO, używając następujących rejestrów:

BAR0 + 0x30: FIFO_FREE
Rejestr tylko do odczytu. Odczytana wartość jest liczbą wolnych miejsc w kolejce poleceń (tyle zapisów do FIFO_SEND można wykonać natychmiast bez obaw o FIFO_OVERFLOW). Kolejka ta ma maksymalną pojemność 512 poleceń.
BAR0 + 0x30: FIFO_SEND
Rejestr tylko do zapisu. Zapis powoduje dopisanie zapisanego słowa na koniec kolejki poleceń. Jeśli kolejka jest już pełna (FIFO_FREE == 0), zamiast tego wyzwalane jest przerwanie FIFO_OVERFLOW, a blok FIFO jest wyłączany w ENABLE.

Blok wczytywania poleceń

Blok wczytywania poleceń pozwala na efektywne sterowanie urządzeniem (bez konieczności ręcznego wysyłania każdego polecenia przez MMIO) przez samodzielne wczytywanie poleceń z pamięci. Rejestry bloku wczytywania poleceń sa następujące:

BAR0 + 0x18: CMD_READ_PTR
32-bitowy wskaźnik na następne polecenie do wczytania. Zawsze musi być wyrównany do wielokrotności 4 bajtów (niskie 2 bity są zawsze równe 0).
BAR0 + 0x1c: CMD_WRITE_PTR
32-bitowy wskaźnik na miejsce, gdzie procesor ma zapisać następne polecenie. Jeśli CMD_WRITE_PTR == CMD_READ_PTR, blok wczytywania poleceń zostanie zatrzymany, aż procesor załaduje więcej poleceń i przesunie CMD_WRITE_PTR. Zawsze musi być wyrównany do wielokrotności 4 bajtów.

Blok wczytywania poleceń będzie wczytywał polecenia zawsze, gdy zachodzą wszystkie z następujących warunków:

  • blok wczytywania poleceń jest włączony w ENABLE
  • są jakieś nieprzetworzone polecenia, tzn. CMD_READ_PTR != CMD_WRITE_PTR
  • jest miejsce w kolejce poleceń, tzn. FIFO_FREE != 0

Polecenie wczytywane jest z adresu CMD_READ_PTR, jest on zwiększany o 4, po czym polecenie jest wykonywane (jeśli jest poleceniem JUMP) lub dopisywane do kolejki (tak, jakby zostało wpisane do FIFO_SEND).

Aby wrócić na początek bufora poleceń, bądź przejść do innego bufora, używane jest polecenie JUMP:

Polecenie JUMP:

Ustawia rejestr CMD_READ_PTR na zadaną wartość, powodując wczytanie następnych poleceń z nowego adresu. Jest poprawne tylko w buforze poleceń (nie można go wysłać bezpośrednio do FIFO_SEND). Pola:

  • bity 0-29: PTR – bity 2-31 nowej wartości CMD_READ_PTR.
  • bity 30-31: TYPE_HI – musi być równe 0x0.

Warning

Po resecie urządzenia, CMD_READ_PTR nie musi być równe CMD_WRITE_PTR – blok wczytywania poleceń może więc próbować wczytać jakieś polecenia po włączeniu go w ENABLE. Aby tego uniknąć, należy zainicjować te rejestry.

Rejestry sterujące

Blok sterujący zajmuje się nadzorowaniem pracy całego urządzenia. Jego rejestry to:

BAR0 + 0x00: ENABLE

Rejestr kontrolujący pracę pozostałych bloków, dostępny do odczytu i zapisu. Ma wiele bitów, z których każdy kontroluje pracę jednego bloku urządzenia (1 – blok jest aktywny i może wykonywać pracę, 0 – blok jest nieaktywny). Wyłączenie bloku w tym rejestrze nie spowoduje jego resetu – po ponownym włączeniu, blok kontynuuje pracę od momentu, w którym skończył. Bity:

  • bit 0: FETCH_CMD – blok wczytywania poleceń.
  • bit 1: FIFO – blok rysujący, dostęp do kolejki poleceń przez FIFO_SEND.
  • bit 2: FE – blok rysujący, przetwarzanie poleceń.
  • bit 3: XY – blok rysujący, przeliczanie współrzędnych na adresy.
  • bit 4: TEX – blok rysujący, obsługa tekstur kolumnowych.
  • bit 5: FLAT – blok rysujący, obsługa tekstur płaskich.
  • bit 6: FUZZ – blok rysujący, efekt FUZZ.
  • bit 7: SR – blok rysujący, wczytywanie pikseli z bufora ramki.
  • bit 8: OG – blok rysujący, zbieranie danych i mapy kolorów
  • bit 9: SW – blok rysujący, zapis do bufora ramki.

Rejestr jest ustawiany na 0 przez reset maszyny, blokując urządzenie do momentu załadowania sterownika. W przypadku zgłoszenia błędu przez blok rysujący, urządzenie wyzeruje odpowiedni bit w tym rejestrze, zatrzymując blok do momentu naprawienia problemu przez sterownik.

Warning

Przed włączeniem urządzenia, należy pamiętać o:

  • załadowaniu mikrokodu przez FE_CODE_ADDR i FE_CODE_WINDOW
  • zresetowaniu wszystkich bloków w RESET,
  • zainicjowaniu rejestrów CMD_*_PTR (jeśli używamy bloku wczytywania poleceń)
  • wyzerowaniu przerwań (w INTR)
BAR0 + 0x04: RESET

Rejestr resetowania stanu urządzenia, dostępny tylko do zapisu. Ma wiele bitów, odpowiadających blokom urządzenia. Przy zapisie, wszystkie bloki, których bity są równe 1 w zapisanej wartości, są resetowane: wszystkie polecenia w trakcie wykonywania są przerywane i kasowane, kolejki poleceń i pamięci podręczne są czyszczone. Bity:

  • bit 1: FIFO – główna kolejka poleceń bloku rysującego.
  • bit 2: FE – blok rysujący, przetwarzanie poleceń.
  • bit 3: XY – blok rysujący, przeliczanie współrzędnych na adresy.
  • bit 4: TEX – blok rysujący, obsługa tekstur kolumnowych.
  • bit 5: FLAT – blok rysujący, obsługa tekstur płaskich.
  • bit 6: FUZZ – blok rysujący, efekt FUZZ.
  • bit 7: SR – blok rysujący, wczytywanie pikseli z bufora ramki.
  • bit 8: OG – blok rysujący, zbieranie danych i mapy kolorów
  • bit 9: SW – blok rysujący, zapis do bufora ramki.
  • bit 10: FE2XY – wewnętrzna kolejka urządzenia.
  • bit 11: FE2TEX – wewnętrzna kolejka urządzenia.
  • bit 12: FE2FLAT – wewnętrzna kolejka urządzenia.
  • bit 13: FE2FUZZ – wewnętrzna kolejka urządzenia.
  • bit 14: FE2OG – wewnętrzna kolejka urządzenia.
  • bit 15: XY2SW – wewnętrzna kolejka urządzenia.
  • bit 16: XY2SR – wewnętrzna kolejka urządzenia.
  • bit 17: SR2OG – wewnętrzna kolejka urządzenia.
  • bit 18: TEX2OG – wewnętrzna kolejka urządzenia.
  • bit 19: FLAT2OG – wewnętrzna kolejka urządzenia.
  • bit 20: FUZZ2OG – wewnętrzna kolejka urządzenia.
  • bit 21: OG2SW – wewnętrzna kolejka urządzenia.
  • bit 22: OG2SW_C – wewnętrzna kolejka urządzenia.
  • bit 23: SW2XY – wewnętrzna kolejka urządzenia.
  • bit 24: STATS – blok statystyk. Wszystkie liczniki są ustawiane na 0.
  • bit 25: TLB – bufory TLB. Wszystkie wpisy tabeli stron zapisane w TLB są kasowane i, w razie potrzeby, zostaną wczytane na nowo.
  • bit 26: TEX_CACHE – pamięć podręczna tekstury kolumnowej.
  • bit 27: FLAT_CACHE – pamięć podręczna tekstury płaskiej.

Użycie bitów TLB, STATS i *_CACHE w dowolnym momencie nie wpłynie negatywnie na pracę urządzenia. Wszystkie pozostałe bity powinny być używane w zasadzie tylko wtedy, gdy resetujemy całe urządzenie (gdyż w przeciwnym przypadku stan poszczególnych bloków się rozsynchronizuje).

Warning

Przed użyciem urządzenia (włączeniem bloku rysującego w ENABLE) należy zresetować cały blok rysujący (zapisując 0xffffffe do tego rejestru) – w przeciwnym wypadku, wewnętrzne rejestry stanu urządzenia mogą zawierać śmieci, powodując wykonanie nieokreślonych poleceń przez urządzenie (w tym zapisu do dowolnej pamięci) bądź zawieszenie urządzenia.

Blok wczytywania poleceń nie ma swojego bitu resetującego – jeśli sterownik chce go użyć, powinien zamiast tego zainicjować rejestry CMD_*_PTR.

Przerwania

Urządzenie wewnętrznie używa 10 przerwań (które są agregowane w jedno przerwanie PCI):

  • FENCE – przerwanie używane do powiadomienia sterownika o wykonaniu polecenia. Wyzwalane, gdy zostanie wykonane polecenie FENCE z parametrem równym FENCE_WAIT.
  • PONG_SYNC – wyzwalane przez polecenie PING_SYNC.
  • PONG_ASYNC – wyzwalane przez polecenie PING_ASYNC.
  • FE_ERROR – wyzwalane, gdy blok rysujący zauważy błędne polecenie.
  • FIFO_OVERFLOW – wyzwalane, gdy sterownik spróbuje pisać do FIFO_SEND, gdy nie ma już wolnego miejsca w kolejce, lub blok FIFO jest wyłączony.
  • SURF_DST_OVERFLOW – wyzwalane, gdy blok rysujący spróbuje pisać lub czytać piksel o współrzędnych poza granicami docelowego bufora ramki (ustalonych przez SURF_DIMS).
  • SURF_SRC_OVERFLOW – wyzwalane, gdy blok rysujący spróbuje lub czytać piksel o współrzędnych poza granicami źródłowego bufora ramki (ustalonych przez SURF_DIMS).
  • PAGE_FAULT_SURF_DST – wyzwalane, gdy blok rysujący spróbuje użyć strony oznaczonej w tabeli stron jako nieobecna przy pisaniu lub czytaniu docelowego bufora ramki.
  • PAGE_FAULT_SURF_SRC – jak wyżej, ale do źródłowego bufora ramki.
  • PAGE_FAULT_TEXTURE – jak wyżej, ale do czytania tekstury kolumnowej.

Warning

Wyzwolenie przerwania SURF_OVERFLOW_* jest zasadniczo błędem krytycznym – nie ma sensownej możliwości obsługi tego błędu innej niż pełny reset urządzenia. To przerwanie (tak jak i FE_ERROR czy FIFO_OVERFLOW) nie powinno się nigdy zdarzyć z poprawnym sterownikiem. Przerwania PAGE_FAULT_* teoretycznie mogą zostać użyte do ładowania stron na żądanie, ale w praktyce nie jest to zbyt przydatne i w zasadzie służą również do wykrywania błędów sterownika.

Każde z powyższych przerwań może być w danej chwili aktywne bądź nie. Przerwanie staje się aktywne, gdy zajdzie odpowiednie zdarzenie. Przerwanie staje się nieaktywne, gdy sterownik zapisze 1 do odpowiedniego bitu w rejestrze INTR. Aby sprawdzić, które przerwania są aktywne, należy przeczytać rejestr INTR.

Niezależnie, każde z powyższych przerwań może być w danej chwili włączone bądź nie. Sterownik może ustawić włączony podzbiór przerwań przez zapis odpowiedniej maski do rejestru INTR_ENABLE. Urządzenie zgłasza przerwanie na swojej linii przerwań PCI wtedy i tylko wtedy, gdy istnieje włączone i aktywne przerwanie.

BAR0 + 0x08: INTR

Rejestr statusu przerwań. Ma 10 bitów, każdy odpowiadający jednemu rodzajowi przerwań:

  • bit 0: FENCE,
  • bit 1: PONG_SYNC,
  • bit 2: PONG_ASYNC,
  • bit 3: FE_ERROR,
  • bit 4: FIFO_OVERFLOW
  • bit 5: SURF_DST_OVERFLOW
  • bit 6: SURF_SRC_OVERFLOW
  • bit 7: PAGE_FAULT_SURF_DST
  • bit 8: PAGE_FAULT_SURF_SRC
  • bit 9: PAGE_FAULT_TEXTURE

Odczyt tego rejestru zwróci 1 dla aktywnych przerwań, 0 dla nieaktywnych. Zapis spowoduje wyzerowanie (ustawienie na nieaktywne) wszystkich przerwań, dla których został zapisany bit 1. Przykładowo, zapisanie 0x12 spowoduje wyzerowanie przerwań FIFO_OVERFLOW oraz PONG_SYNC.

BAR0 + 0x0c: INTR_ENABLE
Rejestr włączania przerwań, dostępny do odczytu i zapisu. Ma takie same bity jak INTR. 1 oznacza przerwanie włączone, a 0 – przerwanie wyłączone. Przy resecie maszyny, rejestr zostaje ustawiony na 0, blokując możliwość zgłaszania przerwania PCI przez urządzenie do momentu załadowania sterownika.

Mikrokod

Blok rysujący opiera się na zaawansowanym procesorze odpowiadającym za przetwarzanie poleceń. Przed włączeniem bloku rysującego w ENABLE, sterownik musi załadować odpowiedni mikrokod na urządzenie – w przeciwnym wypadku, zachowanie urządzenia jest kompletnie niezdefiniowane. Mikrokod jest tablicą 32-bitowych słów, maksymalnie 4096-elementową. Jest dostarczany w pliku https://github.com/koriakin/harddoom/blob/master/doomcode.bin w formacie little-endian, a jego format i działanie jest tajemnicą handlową firmy DoomDevices® (i nie należy zadawać niewygodnych pytań na jego temat). Można go również pobrać jako plik nagłówkowy gotowy do załączenia do kodu w C: https://github.com/koriakin/harddoom/blob/master/doomcode.h . Do ładowania mikrokodu służą następujące rejestry:

BAR0 + 0x20: FE_CODE_ADDR
Indeks w tablicy, który będziemy widoczny przez FE_CODE_WINDOW (dostępny do odczytu i zapisu).
BAR0 + 0x24: FE_CODE_WINDOW
Okno do tablicy mikrokodu – odczytuje bądź zapisuje komórkę wybrana przez FE_CODE_ADDR, po czym zwiększa FE_CODE_ADDR o 1 (pozwalając na szybki sekwencyjny odczyt bądź zapis wielu komórek tablicy).

Uruchomienie urządzenia

Poprawna procedura uruchamiania urządzenia jest następująca (użycie dowolnej innej procedury spowoduje utratę gwarancji i brak możliwości zwrotu urządzenia):

  • zapisać 0 do FE_CODE_ADDR,
  • wczytać zawartość pliku doomcode.bin jako tablicę 32-bitowych słów little-endian,
  • kolejno zapisać wszystkie wczytane słowa do FE_CODE_WINDOW,
  • zresetować wszystkie bloki urządzenia przez zapis 0xffffffe do RESET,
  • zainicjować CMD_*_PTR, jeśli chcemy użyć bloku wczytywania pleceń,
  • wyzerować INTR przez zapis 0x3ff,
  • włączyć używane przez nas przerwania w INTR_ENABLE,
  • zainicjować FENCE_*, jeśli czujemy taką potrzebę,
  • włączyć wszystkie bloki urządzenia w ENABLE (być może z wyjątkiem FETCH_CMD).

Po wykonaniu tej procedury można rozpocząć wysyłanie poleceń do urządzenia (przez FIFO_SEND lub CMD_WRITE_PTR).

Żeby wyłączyć urządzenie, wystarczy zapisać 0 do ENABLE oraz INTR_ENABLE, po czym przeczytać dowolny rejestr urządzenia.

Inne rejestry

Dokumentacja sprzętowa nigdy nie mówi całej prawdy (i rzadko mówi tylko prawdę). Urządzenie może mieć inne rejestry i polecenia ponad wyżej wymienione, lecz używanie ich w ostatecznym sterowniku jest złym pomysłem – są to po prostu szczegóły implementacyjne. Mogą jednak być przydatne przy debugowaniu…

Poniższe rejestry nie są potrzebne do napisania sterownika, ale mają sporą szansę się przydać (oprócz nich, urządzenie zawiera wiele nieudokumentowanych rejestrów).

Rejestry błędów

W przypadku popełnienia błędu przez sterownik, przydatne może być przeczytanie następujących rejestrów:

BAR0 + 0x04: STATUS

Rejestr statusu, tylko do odczytu. Ma wiele bitów, odpowiadających blokom urządzenia. Gdy bit jest równy 1, dany blok ma pracę do wykonania (i, jeśli odpowiedni bit w ENABLE jest równy 1, będzie próbował ją wykonać). Gdy bit jest równy 0, dany blok nie ma nic do zrobienia (ale może się to w każdej chwili zmienić, jeżeli inny blok zleci mu jakieś zadania). Bity:

  • bit 0: FETCH_CMD – blok wczytywania poleceń (ten bit jest efektywnie równy CMD_READ_PTR != CMD_WRITE_PTR).
  • bit 1: FIFO – główna kolejka poleceń bloku rysującego.
  • bit 2: FE – blok rysujący, przetwarzanie poleceń.
  • bit 3: XY – blok rysujący, przeliczanie współrzędnych na adresy.
  • bit 4: TEX – blok rysujący, obsługa tekstur kolumnowych.
  • bit 5: FLAT – blok rysujący, obsługa tekstur płaskich.
  • bit 6: FUZZ – blok rysujący, efekt FUZZ.
  • bit 7: SR – blok rysujący, wczytywanie pikseli z bufora ramki.
  • bit 8: OG – blok rysujący, zbieranie danych i mapy kolorów
  • bit 9: SW – blok rysujący, zapis do bufora ramki.
  • bit 10: FE2XY – wewnętrzna kolejka urządzenia.
  • bit 11: FE2TEX – wewnętrzna kolejka urządzenia.
  • bit 12: FE2FLAT – wewnętrzna kolejka urządzenia.
  • bit 13: FE2FUZZ – wewnętrzna kolejka urządzenia.
  • bit 14: FE2OG – wewnętrzna kolejka urządzenia.
  • bit 15: XY2SW – wewnętrzna kolejka urządzenia.
  • bit 16: XY2SR – wewnętrzna kolejka urządzenia.
  • bit 17: SR2OG – wewnętrzna kolejka urządzenia.
  • bit 18: TEX2OG – wewnętrzna kolejka urządzenia.
  • bit 19: FLAT2OG – wewnętrzna kolejka urządzenia.
  • bit 20: FUZZ2OG – wewnętrzna kolejka urządzenia.
  • bit 21: OG2SW – wewnętrzna kolejka urządzenia.
  • bit 22: OG2SW_C – wewnętrzna kolejka urządzenia.
  • bit 23: SW2XY – wewnętrzna kolejka urządzenia.
BAR0 + 0x28: FE_ERROR_CODE

Kod ostatnio zgłoszonego błędu FE_ERROR. Zdefiniowane są następujące kody:

  • 0x00: RESERVED_TYPE – nieznany typ polecenia.
  • 0x01: RESERVED_BIT – nieużywany bit nie jest ustawiony na 0.
  • 0x02: SURF_WIDTH_ZERO – zerowa szerokość bufora ramki.
  • 0x03: SURF_HEIGHT_ZERO – zerowa wysokość bufora ramki.
  • 0x04: SURF_WIDTH_OVF – bufor ramki szerszy niż 2048 pikseli.
  • 0x05: SURF_HEIGHT_OVF – bufor ramki wyższy niż 2048 pikseli.
  • 0x06: RECT_DST_X_OVF – prostokąt docelowy w COPY_RECT lub FILL_RECT wystaje poza szerokość bufora ramki.
  • 0x07: RECT_DST_Y_OVF – prostokąt docelowy w COPY_RECT lub FILL_RECT wystaje poza wysokość bufora ramki.
  • 0x08: RECT_SRC_X_OVF – prostokąt źródłowy w COPY_RECT wystaje poza szerokość bufora ramki.
  • 0x09: RECT_SRC_Y_OVF – prostokąt źródłowy w COPY_RECT wystaje poza wysokość bufora ramki.
  • 0x0a: DRAW_COLUMN_REV – pierwszy piksel kolumny ma większą współrzędną Y niż drugi.
  • 0x0b: DRAW_SPAN_REV – pierwszy piksel paska ma większą współrzędną X niż drugi.
BAR0 + 0x2c: FE_ERROR_DATA
Polecenie, którego dotyczy ostatnio zgłoszony błąd FE_ERROR.
BAR0 + 0x200: XY_SURF_DIMS

Wymiary bufora ramki obowiązujące w momencie zgłoszenia błędu SURF_OVERFLOW. Bity:

  • bity 0-5: WIDTH – jak w poleceniu SURF_DIMS.
  • bity 8-19: HEIGHT – jak w poleceniu SURF_DIMS.
BAR0 + 0x208: XY_DST_CMD

W przypadku błędów PAGE_FAULT_SURF_DST oraz SURF_DST_OVERFLOW można tu przeczytać współrzędne, które wywołały błąd:

  • bity 0-4: bity 6-10 współrzędnej x (bity 0-5 nie są zapisywane)
  • bity 5-15: współrzędna y
BAR0 + 0x20c: XY_SRC_CMD
Jak wyżej, ale do PAGE_FAULT_SURF_SRC i SURF_SRC_OVERFLOW.
BAR0 + 0x300: TLB_PT_SURF_DST
Adres początku tabeli stron używanej w momencie wyzwolenia błędu PAGE_FAULT_SURF_DST.
BAR0 + 0x304: TLB_PT_SURF_SRC
Adres początku tabeli stron używanej w momencie wyzwolenia błędu PAGE_FAULT_SURF_SRC.
BAR0 + 0x308: TLB_PT_TEXTURE
Adres początku tabeli stron używanej w momencie wyzwolenia błędu PAGE_FAULT_TEXTURE.
BAR0 + 0x320: TLB_VADDR_SURF_DST
Adres wirtualny, który spowodował błąd PAGE_FAULT_SURF_DST. Niskie 6 bitów nie jest zapisywane (jest zawsze równe 0).
BAR0 + 0x324: TLB_VADDR_SURF_SRC
Adres wirtualny, który spowodował błąd PAGE_FAULT_SURF_SRC. Niskie 6 bitów nie jest zapisywane (jest zawsze równe 0).
BAR0 + 0x328: TLB_VADDR_TEXTURE
Adres wirtualny, który spowodował błąd PAGE_FAULT_TEXTURE. Niskie 6 bitów nie jest zapisywane (jest zawsze równe 0).

Blok statystyk

Blok rysujący zbiera różnorodne statystyki dotyczące pracy urządzenia. Są one bezpośrednio widoczne jako rejestry MMIO. Każdy taki rejestr jest 32-bitowym licznikiem (który może się przekręcić – urządzenie nie robi wtedy nic specjalnego). Te liczniki zliczają następujące zdarzenia:

  • BAR0 + 0x100: STAT_FE_COPY_RECT_HORIZONTAL – polecenie COPY_RECT realizowane poziomo
  • BAR0 + 0x104: STAT_FE_COPY_RECT_LINE – jedna linia poziomego polecenia COPY_RECT
  • BAR0 + 0x108: STAT_FE_COPY_RECT_VERTICAL – polecenie COPY_RECT realizowane pionowo
  • BAR0 + 0x10c: STAT_FE_FILL_RECT_HORIZONTAL – polecenie FILL_RECT realizowane poziomo
  • BAR0 + 0x110: STAT_FE_FILL_RECT_LINE – jedna linia poziomego polecenia FILL_RECT
  • BAR0 + 0x114: STAT_FE_FILL_RECT_VERTICAL – polecenie FILL_RECT realizowane pionowo
  • BAR0 + 0x118: STAT_FE_DRAW_LINE_HORIZONTAL – polecenie DRAW_LINE realizowane poziomo
  • BAR0 + 0x11c: STAT_FE_DRAW_LINE_VERTICAL – polecenie DRAW_LINE realizowane pionowo
  • BAR0 + 0x120: STAT_FE_DRAW_LINE_H_CHUNK – rysowanie prostokąta N×1 linii poziomej
  • BAR0 + 0x124: STAT_FE_DRAW_LINE_V_CHUNK – rysowanie prostokąta 1×N linii pionowej
  • BAR0 + 0x128: STAT_FE_DRAW_LINE_H_PIXEL – rysowanie piksela linii poziomej
  • BAR0 + 0x12c: STAT_FE_DRAW_LINE_V_PIXEL – rysowanie piksela linii pionowej
  • BAR0 + 0x130: STAT_FE_DRAW_BACKGROUND – polecenie DRAW_BACKGROUND
  • BAR0 + 0x134: STAT_FE_DRAW_COLUMN_TEX_BATCH – grupa poleceń DRAW_COLUMN z teksturą przetwarzana równolegle
  • BAR0 + 0x138: STAT_FE_DRAW_COLUMN_FUZZ_BATCH – grupa poleceń DRAW_COLUMN z efektem FUZZ przetwarzana równolegle
  • BAR0 + 0x13c: STAT_FE_DRAW_SPAN – polecenie DRAW_SPAN
  • BAR0 + 0x140: STAT_FE_CMD – przetworzenie polecenia od użytkownika przez urządzenie
  • BAR0 + 0x144: STAT_XY_CMD – przetworzenie polecenia wewnętrznego przez jednostkę XY
  • BAR0 + 0x148: STAT_TEX_CMD – przetworzenie polecenia wewnętrznego przez jednostkę TEX
  • BAR0 + 0x14c: STAT_FLAT_CMD – przetworzenie polecenia wewnętrznego przez jednostkę FLAT
  • BAR0 + 0x150: STAT_FUZZ_CMD – przetworzenie polecenia wewnętrznego przez jednostkę FUZZ
  • BAR0 + 0x154: STAT_OG_CMD – przetworzenie polecenia wewnętrznego przez jednostkę OG
  • BAR0 + 0x158: STAT_SW_CMD – przetworzenie polecenia wewnętrznego przez jednostkę SW
  • BAR0 + 0x15c: STAT_SR_BLOCK – wczytanie bloku pikseli z bufora ramki
  • BAR0 + 0x160: STAT_TEX_BLOCK – oteksturowanie bloku pikseli teksturą kolumnową
  • BAR0 + 0x164: STAT_FLAT_BLOCK – oteksturowanie bloku pikseli teksturą płaską bądź wczytanie bloku pikseli przez DRAW_BACKGROUND
  • BAR0 + 0x168: STAT_FUZZ_BLOCK – nałożenie efektu FUZZ na blok pikseli
  • BAR0 + 0x16c: STAT_SW_BLOCK – zapisanie bloku pikseli do bufora ramki
  • BAR0 + 0x170: STAT_TLB_SURF_DST_HIT – trafienie w buforze TLB dla docelowego bufora ramki.
  • BAR0 + 0x174: STAT_TLB_SURF_DST_MISS – chybienie w tym buforze (czyli wczytanie wpisu z tabeli stron).
  • BAR0 + 0x178: STAT_TLB_SURF_SRC_HIT – j/w, źródłowy bufor ramki.
  • BAR0 + 0x17c: STAT_TLB_SURF_SRC_MISS – j/w, źródłowy bufor ramki.
  • BAR0 + 0x180: STAT_TLB_TEXTURE_HIT – j/w, tekstura kolumnowa.
  • BAR0 + 0x184: STAT_TLB_TEXTURE_MISS – j/w, tekstura kolumnowa.
  • BAR0 + 0x188: STAT_TLB_REBIND_SURF_DST – zmiana aktywnego docelowego bufora ramki (i spłukanie TLB)
  • BAR0 + 0x18c: STAT_TLB_REBIND_SURF_SRC – zmiana aktywnego źródłowego bufora ramki (i spłukanie TLB)
  • BAR0 + 0x190: STAT_TLB_REBIND_TEXTURE – zmiana aktywnej tekstury kolumnowej (i spłukanie TLB oraz pamięci podręcznej)
  • BAR0 + 0x194: STAT_XY_INTERLOCK – przetworzenie polecenia INTERLOCK
  • BAR0 + 0x198: STAT_TEX_COLUMN – przetworzenie polecenia DRAW_COLUMN z teksturą
  • BAR0 + 0x19c: STAT_TEX_PIXEL – narysowanie piksela przez DRAW_COLUMN z teksturą
  • BAR0 + 0x1a0: STAT_TEX_CACHE_HIT – trafienie teksela w pamięci podręcznej tekstury kolumnowej
  • BAR0 + 0x1a4: STAT_TEX_CACHE_SPEC_HIT – trafienie spekulatywnego teksela w pamięci podręcznej tekstury kolumnowej
  • BAR0 + 0x1a8: STAT_TEX_CACHE_MISS – chybienie teksela (czyli wczytanie 64 bajtów z tekstury)
  • BAR0 + 0x1ac: STAT_TEX_CACHE_SPEC_MISS – chybienie spekulatywnego teksela (czyli porzucenie spekulatywnego teksturowania danej kolumny)
  • BAR0 + 0x1b0: STAT_FLAT_REBIND – zmiana aktywnej tekstury płaskiej (i spłukanie pamięci podręcznej)
  • BAR0 + 0x1b4: STAT_FLAT_READ_BLOCK – wczytanie bloku pikseli przez DRAW_BACKGROUND
  • BAR0 + 0x1b8: STAT_FLAT_SPAN_BLOCK – oteksturowanie bloku pikseli teksturą płaską
  • BAR0 + 0x1bc: STAT_FLAT_SPAN_PIXEL – oteksturowanie piksela teksturą płaską
  • BAR0 + 0x1c0: STAT_FLAT_CACHE_HIT – trafienie teksela w pamięci podręcznej tekstury płaskiej
  • BAR0 + 0x1c4: STAT_FLAT_CACHE_MISS – chybienie teksela (czyli wczytanie 64 bajtów z tekstury)
  • BAR0 + 0x1c8: STAT_FUZZ_COLUMN – rysowanie kolumny z efektem FUZZ
  • BAR0 + 0x1cc: STAT_OG_COLORMAP_FETCH – wczytanie głównej mapy kolorów
  • BAR0 + 0x1d0: STAT_OG_TRANSLATION_FETCH – wczytanie mapy kolorów do zmiany palety
  • BAR0 + 0x1d4: STAT_OG_DRAW_BUF_BLOCK – rysowanie bloku pikseli przez FILL_RECT, DRAW_LINE lub DRAW_BACKGROUND
  • BAR0 + 0x1d8: STAT_OG_DRAW_BUF_PIXEL – rysowanie piksela przez FILL_RECT, DRAW_LINE lub DRAW_BACKGROUND
  • BAR0 + 0x1dc: STAT_OG_COPY_BLOCK – rysowanie bloku pikseli przez COPY_RECT
  • BAR0 + 0x1e0: STAT_OG_COPY_PIXEL – rysowanie piksela przez COPY_RECT
  • BAR0 + 0x1e4: STAT_OG_FUZZ_PIXEL – rysowanie piksela przez DRAW_COLUMN z FUZZ
  • BAR0 + 0x1e8: STAT_OG_TRANSLATE_BLOCK – przetwarzanie bloku pikseli przez flagę TRANSLATE
  • BAR0 + 0x1ec: STAT_OG_COLORMAP_BLOCK – przetwarzanie bloku pikseli przez flagę COLORMAP
  • BAR0 + 0x1f0: STAT_SW_FENCE – przetworzenie polecenia FENCE
  • BAR0 + 0x1f4: STAT_SW_FENCE_INTR – przetworzenie polecenia FENCE powodującego przerwanie
  • BAR0 + 0x1f8: STAT_SW_PIXEL – rysowanie piksela jakiegokolwiek typu
  • BAR0 + 0x1fc: STAT_SW_XFER – rysowanie ciągłej grupy pikseli

Blok pikseli to maksymalnie 64 piksele różniące się tylko niskimi 6 bitami współrzędnej X (urządzenie grupuje wszystkie rysowane oraz wczytywane piksele w takie bloki). Dzieląc statystyki *_PIXEL przez *_BLOCK można zmierzyć efektywność grupowania pikseli przez urządzenie. Dzieląc STAT_TEX_COLUMN przez STAT_FE_DRAW_COLUMN_TEX_BATCH można zmierzyć efektywność grupowania kolumn. Dzieląc statystyki *_HIT przez *_HIT + *_MISS można zmierzyć efektywność działania pamięci podręcznych i buforów TLB.

Zresetowanie bloku statystyk (w rejestrze RESET) jest równoznaczne z zapisem 0 do każdego z wyżej wymienionych liczników.