Urządzenie Ultimate HardDoom™

Grafika w grze Ultimate 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 Ultimate 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 Ultimate 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 0x1995.

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 urządzenie może używać całkiem dużo pamięci, urządzenie wykonuje dostęp do pamięci przez tablicę stron w swoim własnym formacie.

Urządzenie jest zaprojektowane tak, by można było je obsługiwać z minimalną ingerencją jądra w przesyłane polecenia — wszystkie polecenia są walidowane przez urządzenie i operują tylko tylko na adresach wirtualnych, tłumaczonych przez tabele stron — użytkownik przygotowujący bufor poleceń nie ma możliwości zawieszenia urządzenia ani dostępu do nie swojej pamięci. Co najwyżej, może spowodować zgłoszenie błędu przez urządzenie, który może być obsłużony przez jądro systemu.

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ść 64kiB, 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.

Przestrzeń adresowa i blok stronicowania

Wszystkie dane i polecenia używane przez urządzenie są obsługiwane przez adresy wirtualne, które są tłumaczone przez tabele stron urządzenia. Urządzenie obsługuje dwie przestrzenie adresowe, odpowiadające dwóm zestawom tabeli stron:

  • przestrzeń adresowa jądra: używana przez blok BATCH do wczytywania zadań

  • przestrzeń adresowa użytkownika: używana do wczytywania poleceń, czytania tekstur i map kolorów, czytania i pisania buforów ramki

Urządzenie pozwala na szybką zmianę przestrzeni adresowej użytkownika (przez wybranie nowego zestawu tabeli stron), pozwalając na bezpieczną obsługę wielu użytkowników przez jądro.

Urządzenie używa 40-bitowych adresów fizycznych (zarówno dla buforów jak i dla tabel stron), 32-bitowych adresów wirtualnych, i stron o rozmiarze 4kiB. Tabele stron używane przez urządzenie są dwupoziomowe:

  • jądro podaje urządzeniu adres fizyczny katalogu stron

  • bity 22-31 adresu wirtualnego wybierają wpis w katalogu stron (page directory), który zawiera adres fizyczny tablicy stron

  • bity 12-21 adresu wirtualnego wybierają wpis w tabeli stron (page table), który zawiera adres fizyczny strony i jej prawa dostępu (do odczytu i zapisu, bądź tylko do odczytu)

  • bity 0-11 adresu wirtualnego to offset wewnątrz strony

Zarówno katalogi stron jak i tabele stron mają rozmiar 4kiB (taki jak strona) i składają się z 1024 wpisów, gdzie każdy wpis jest 32-bitowym słowem w formacie little-endian. Wpis w katalogu stron ma następujący format:

  • bit 0: PRESENT — jeśli ustawiony, dany wpis jest obecny i może być użyty. Jeśli wyzerowany, wpis jest nieobecny i użycie go spowoduje błąd braku strony (PAGE_FAULT).

  • bity 4-31: PA — bity 12-39 adresu fizycznego tabeli stron (bity 0-11 adresu są zawsze równe 0 — tabele i katalogi stron muszą być wyrównane).

Wpis w tabeli stron ma następujący format:

  • bit 0: PRESENT — jeśli ustawiony, dany wpis jest obecny i może być użyty. Jeśli wyzerowany, wpis jest nieobecny i użycie go spowoduje błąd braku strony (PAGE_FAULT).

  • bit 1: WRITABLE — jeśli ustawiony, dana strona jest dostępna do odczytu i zapisu. Jeśli wyzerowany, strona jest dostępna tylko do odczytu, a próba zapisu spowoduje błąd strony (PAGE_FAULT).

  • bity 4-31: PA — bity 12-39 adresu fizycznego strony (bity 0-11 adresu są zawsze równe 0 — strony muszą być wyrównane).

Katalog stron wybiera się, podając bity 12-39 jego adresu fizycznego (czyli adres przesunięty w prawo o 12 bitów). Aby wybrać katalog stron jądra, należy zapisać tak przesunięty adres do rejestru BATCH_PDP. Wybór katalogu stron użytkownika następuje podczas wysyłania zadania do urządzenia.

Aby nie czytać ciągle tabeli stron, urządzenie może zapamiętywać dane z tabel i katalogów stron w buforach TLB. Zmiana aktywnego katalogu stron spowoduje automatyczne wyczyszczenie odpowiadających buforów TLB. Sterownik może również w dowolnym momencie wyczyścić bufory TLB ręcznie, używając bitu TLB_KERNEL bądź TLB_USER w rejestrze RESET. W szczególności, należy to zrobić po odmapowaniu strony z przestrzeni adresowej, a przed jej zwolnieniem (aby użytkownik nie mógł użyć sterego mapowania, by dotknąć nie swojej pamięci).

Jeśli urządzenie napotka niepoprawny adres wirtualny (bez flagi PRESENT w katalogu stron czy tabeli stron, bądź bez flagi WRITABLE przy próbie zapisu), zgłaszane jest jedno z przerwań PAGE_FAULT_*, a odpowiadający blok urządzenia jest wyłączany w rejestrze ENABLE. W takiej sytuacji można naprawić brak strony (przez wstawienie nowych wartości w katalogi i tabele stron) i wznowić zadanie, bądź też przerwać zadanie przez wykonanie procedury resetu urządzenia. Aby wznowić zadanie, należy poprawić odpowiednie wpisy w katalogach czy tabelach stron, wyczyścić TLB, oznaczyć przerwanie jako obsłużone, po czym włączyć z powrotem odpowiedni blok w ENABLE — urządzenie kontynuuje wtedy pracę od momentu w którym trafiło na błąd strony.

Urządzenie ma 8 różnych przerwań PAGE_FAULT, odpowiadających 8 wewnętrznym ścieżkom dostępu do pamięci. Są to:

  • PAGE_FAULT_BATCH: blok BATCH, wczytywanie zadań (jako jedyne używa przestrzeni adresowej jądra)

  • PAGE_FAULT_CMD: blok CMD, wczytywanie poleceń

  • PAGE_FAULT_SRD: blok SRD, wczytywanie mapy kolorów, bufora źródłowego do polecenia BLIT, bądź bufora docelowego do polecenia DRAW_FUZZ

  • PAGE_FAULT_SWR_DST: blok SWR, dostęp do docelowego bufora ramki (jako jedyne wykonuje zapisy do pamięci)

  • PAGE_FAULT_COL_CMAP_B: blok COL, wczytywanie mapy kolorów

  • PAGE_FAULT_COL_SRC: blok COL, wczytywanie tekstury

  • PAGE_FAULT_SPAN_SRC: blok SPAN, wczytywanie tekstury

  • PAGE_FAULT_SWR_TRANSMAP: blok SWR, wczytywanie mapy przezroczystości

Aby dowiedzieć się, jaki adres wirtualny spowodował błąd strony, można przeczytać jeden z następujących rejestrów:

BAR0 + 0x0480: TLB_CLIENT_VA[TLB_CLIENT_BATCH]

Adres wirtualny odpowiadający błędowi PAGE_FAULT_BATCH.

BAR0 + 0x0484: TLB_CLIENT_VA[TLB_CLIENT_CMD]

Adres wirtualny odpowiadający błędowi PAGE_FAULT_CMD.

BAR0 + 0x0488: TLB_CLIENT_VA[TLB_CLIENT_SRD]

Adres wirtualny odpowiadający błędowi PAGE_FAULT_SRD.

BAR0 + 0x048c: TLB_CLIENT_VA[TLB_CLIENT_SWR_DST]

Adres wirtualny odpowiadający błędowi PAGE_FAULT_SWR_DST.

BAR0 + 0x0490: TLB_CLIENT_VA[TLB_CLIENT_COL_CMAP_B]

Adres wirtualny odpowiadający błędowi PAGE_FAULT_COL_CMAP_B.

BAR0 + 0x0494: TLB_CLIENT_VA[TLB_CLIENT_COL_SRC]

Adres wirtualny odpowiadający błędowi PAGE_FAULT_COL_SRC.

BAR0 + 0x0498: TLB_CLIENT_VA[TLB_CLIENT_SPAN_SRC]

Adres wirtualny odpowiadający błędowi PAGE_FAULT_SPAN_SRC.

BAR0 + 0x049c: TLB_CLIENT_VA[TLB_CLIENT_SWR_TRANSMAP]

Adres wirtualny odpowiadający błędowi PAGE_FAULT_SWR_TRANSMAP.

Przydatne mogą być też następujące rejestry:

BAR0 + 0x0400: TLB_KERNEL_PDP

Adres aktywnego katalogu stron jądra, przesunięty w prawo o 12 bitów.

BAR0 + 0x0404: TLB_USER_PDP

Adres aktywnego katalogu stron użytkownika, przesunięty w prawo o 12 bitów.

Wysyłanie zadań

Urządzenia używa się przez zlecanie mu wykonania zadań. Są dwa sposoby na zlecanie wykonania zadań — bezpośrednio przez rejestry, bądź przez blok BATCH, który wczytuje zadania do wykonania z pamięci. Zadanie składa się z trzech słów:

  • PDP: bity 12-39 adresu fizycznego katalogu stron użytkownika, który będzie używany przez to zadanie.

  • CMD_PTR: wskaźnik wirtualny (w przestrzeni użytkownika), pod którym znajdują się polecenia do wykonania przez urządzenie. Wskaźnik ten musi być wielokrotnością 4.

  • CMD_SIZE: rozmiar bufora poleceń do wykonania w bajtach. Musi być wielokrotnością 4.

Aby zlecić zadanie bezpośrednio, należy użyć nastęþujących rejestrów:

BAR0 + 0x0040: JOB_PDP

Bity 12-39 adresu fizycznego katalogu stron użytkownika.

BAR0 + 0x0044: JOB_CMD_PTR

Wskaźnik na polecenia.

BAR0 + 0x0048: JOB_CMD_SIZE

Rozmiar poleceń.

BAR0 + 0x004c: JOB_TRIGGER

Rejestr tylko do zapisu. Zapisanie dowolnej wartości spowoduje natychmiastowe rozpoczęcie wykonywania zadania zdefiniowanego przez powyższe rejestry. Nie należy używać tego rejestru, gdy jakieś zadanie jest już wykonywane przez urządzenie.

Poprawne zakończenie wykonywania zadania spowoduje zgłoszenie przerwania JOB_DONE. Zadanie może również spowodować błąd, zgłaszając inne przerwanie — CMD_ERROR, FE_ERROR, PAGE_FAULT_*. W takim wypadku konieczne będzie wykonanie procedury resetu urządzenia przed wysłaniem kolejnego polecenia.

Błąd FE_ERROR

Błąd FE_ERROR jest zgłaszany przez blok FE w przypadku napotkania problemu przez firmware — oznacza to wykryte błędne polecenie, bądź wewnętrzny błąd firmware’u. Można się dowiedzieć więcej o źródle błędu, czytając następujące rejestry:

BAR0 + 0x0120: FE_ERROR_DATA_A

Informacja o błędzie (zależna od kodu błędu).

BAR0 + 0x0124: FE_ERROR_DATA_B

Informacja o błędzie (zależna od kodu błędu).

BAR0 + 0x0128: FE_ERROR_CODE

Kod błędu. Jeden z:

  • 0x00: UNK_USER_COMMAND — nieznany typ polecenia od użytkownika. DATA_A to wskaźnik na błędne polecenie, DATA_B to pierwsze słowo polecenia.

  • 0x01: DST_PTR_UNALIGNED — wskaźnik na bufor ramki nie jest wielokrotnością 64 bajtów. DATA_A to wskaźnik na błędne polecenie, DATA_B to błędny wskaźnik na bufor ramki.

  • 0x02: DST_PITCH_UNALIGNED — przeskok bufora ramki nie jest wielokrotnością 64 bajtów. DATA_A to wskaźnik na błędne polecenie, DATA_B to błędny przeskok.

  • 0x03: COLORMAP_UNALIGNED — wskaźnik na mapę kolorów nie jest wielokrotnością 64 bajtów. DATA_A to wskaźnik na błędne polecenie, DATA_B to błędny wskaźnik na mapę kolorów.

  • 0x04: DRAW_COLUMNS_Y_REV — początkowa współrzędna y w poleceniu DRAW_COLUMNS bądź DRAW_FUZZ jest większa niż końcowa wspołrzędna. DATA_A to wskaźnik na błędne polecenie, DATA_B to błędne słowo polecenia.

  • 0x05: DRAW_SPANS_X_REV — początkowa współrzędna x w poleceniu DRAW_SPANS jest większa niż końcowa wspołrzędna. DATA_A to wskaźnik na błędne polecenie, DATA_B to błędne słowo polecenia.

  • 0x80: ILLEGAL_INSTRUCTION — wewnętrzny błąd firmware’u. Nigdy nie powinno się zdarzyć.

  • 0x81: UNALIGNED_INSTRUCTION — wewnętrzny błąd firmware’u.

  • 0x82: BUS_ERROR_LOAD — wewnętrzny błąd firmware’u.

  • 0x83: BUS_ERROR_STORE — wewnętrzny błąd firmware’u.

  • 0x84: BUS_ERROR_EXEC — wewnętrzny błąd firmware’u.

Blok BATCH

Blok BATCH pozwala na automatyczne wczytywanie zadań przez urządzenie — sterownik zapisuje zadania do wykonania w cyklicznym buforze w przestrzeni adresowej jądra, a urządzenie będzie automatycznie wczytywać i rozpoczynać kolejne zadanie gdy tylko skończy obecne. Aby użyć tego bloku, należy stworzyć bufor zadań, zmapować go do przestrzeni jądra, po czym użyć następujących rejestrów:

BAR0 + 0x0020: BATCH_PDP

Adres fizyczny katalogu stron jądra, przesunięty w prawo o 12 bitów.

BAR0 + 0x0024: BATCH_GET

Adres wirtualny, z którego urządzenie ma wczytać następne zadanie do wykonania. Po udanym wykonaniu wczytanego zadania, ten wskaźnik zostanie automatycznie zwiększony o 16 przez urządzenie.

BAR0 + 0x0028: BATCH_PUT

Adres wirtualny, pod którym jądro ma zapisać następne zadanie do wykonania. Jeśli BATCH_GET będzie równy BATCH_PUT, oznacza to, że urządzenie obecnie nie ma żadnych zadań do wczytania (i będzie czekać aż jądro zapisze kolejne zadanie i zwiększy BATCH_PUT).

BAR0 + 0x002c: BATCH_WAIT

Adres wirtualny, którego osiągnięcie przez BATCH_GET spowoduje wyzwolenie przerwania BATCH_WAIT. Sterownik może użyć tego mechanizmu, by czekać na zakończenie wykonania określonego polecenia.

BAR0 + 0x0030: BATCH_WRAP_FROM

Adres wirtualny końca bufora zadań. Jeśli BATCH_GET po zwiększeniu osiągnął by wartość BATCH_WRAP_FROM, zamiast tego zostanie ustawiony na BATCH_WRAP_TO (by działać jak bufor cykliczny).

BAR0 + 0x0034: BATCH_WRAP_TO

Adres wirtualny początku bufora zadań (patrz wyżej).

Opis zadania ma 16 bajtów i składa się z czterych słów 32-bitowych w formacie litle-endian:

  • bajty 0-3: wskaźnik fizyczny na katalog stron (zostanie zapisany do JOB_PDP)

  • bajty 4-7: wskaźnik wirtualny na polecenia (JOB_CMD_PTR)

  • bajty 8-11: rozmiar poleceń w bajtach (JOB_CMD_SIZE)

  • bajty 12-15: (nieużywane)

Wszystkie adresy używane przez blok BATCH muszą być wielokrotnością 16 bajtów (czyli rozmiaru struktury opisującej zadanie).

Blok BATCH można opisać następującym pseudokodem:

while True:
    # Gdy nie ma aktywnego zadania, blok BATCH jest włączony, i bufor zadań nie jest pusty...
    if ENABLE.BATCH and not STATUS.JOB and BATCH_GET != BATCH_PUT:
        # Wczytaj 16-bajtowe zadanie z przestrzeni adresowej jądra
        cur_batch = mem_read(BATCH_PD, BATCH_GET, 0x10)
        # I uruchom je
        JOB_PD = cur_batch[0:4]
        JOB_CMD_PTR = cur_batch[4:8]
        JOB_CMD_SIZE = cur_batch[8:12]
        # Bajty 12:16 to padding
        JOB.TRIGGER()
        # czekaj na zakończenie zadania
        while STATUS.JOB:
            pass
        # Podbij BATCH_GET, być może zastosuj wrap.
        BATCH_GET += 0x10
        if BATCH_GET == BATCH_WRAP_FROM:
            BATCH_GET = BATCH_WRAP_TO
        # Zgłoś przerwanie, jeśli osiągnięta została odpowiednia wartość wskaźnika.
        if BATCH_GET == BATCH_WAIT:
            INTR |= INTR_BATCH_WAIT

Rejestry sterujące

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

BAR0 + 0x0000: 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: BATCH — blok wczytywania zadań.

  • bit 2: CMD — wczytywanie poleceń użytkownika.

  • bit 3: FE — przetwarzanie poleceń przez firmware.

  • bit 4: SRD — proste wczytywanie danych

  • bit 5: SPAN — teksturowanie pasków

  • bit 6: COL — teksturowanie kolumn

  • bit 7: FX — efekty specjalne (FUZZ, mapy kolorów)

  • bit 8: SWR — mapy przezroczystości i 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 któryś z bloków urządzenia, urządzenie wyzeruje odpowiedni bit w tym rejestrze, zatrzymując blok do momentu naprawienia problemu przez sterownik (być może przez reset całego urządzenia).

Warning

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

  • załadowaniu firmware’u przez FE_CODE_ADDR i FE_CODE_WINDOW

  • zresetowaniu wszystkich bloków w RESET,

  • zainicjowaniu rejestrów bloku BATCH (jeśli go używamy)

  • wyzerowaniu przerwań (w INTR)

BAR0 + 0x0004: RESET

Rejestr resetowania 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: JOB — kontrola zadań

  • bit 2: CMD — wczytywanie poleceń użytkownika.

  • bit 3: FE — przetwarzanie poleceń przez firmware.

  • bit 4: SRD — proste wczytywanie danych

  • bit 5: SPAN — teksturowanie pasków

  • bit 6: COL — teksturowanie kolumn

  • bit 7: FX — efekty specjalne (FUZZ, mapy kolorów)

  • bit 8: SWR — mapy przezroczystości i zapis do bufora ramki.

  • bit 9: STATS — blok statystyk. Wszystkie liczniki są ustawiane na 0.

  • bit 10: TLB_KERNEL — bufory TLB dla przestrzeni wirtualnej jądra. Wszystkie wpisy tabel i katalogów stron zapisane w TLB są kasowane i, w razie potrzeby, zostaną wczytane na nowo.

  • bit 11: TLB_USER — bufory TLB dla przestrzeni wirtualnej użytkownika.

  • bit 12: CACHE_COL_CMAP_B — pamięć podręczna na mapy kolorów.

  • bit 13: CACHE_COL_SRC — pamięć podręczna na tekstury kolumnowe.

  • bit 14: CACHE_SPAN_SRC — pamięć podręczna na tekstury płaskie.

  • bit 15: CACHE_SWR_TRANSMAP — pamięć podręczna na mapy przezroczystości.

  • bit 16: FIFO_SRDCMD — wewnętrzna kolejka urządzenia.

  • bit 17: FIFO_SPANCMD — wewnętrzna kolejka urządzenia.

  • bit 18: FIFO_COLCMD — wewnętrzna kolejka urządzenia.

  • bit 19: FIFO_FXCMD — wewnętrzna kolejka urządzenia.

  • bit 20: FIFO_SWRCMD — wewnętrzna kolejka urządzenia.

  • bit 21: FIFO_COLIN — wewnętrzna kolejka urządzenia.

  • bit 22: FIFO_FXIN — wewnętrzna kolejka urządzenia.

  • bit 24: FIFO_FESEM — wewnętrzna kolejka urządzenia.

  • bit 25: FIFO_SRDSEM — wewnętrzna kolejka urządzenia.

  • bit 26: FIFO_COLSEM — wewnętrzna kolejka urządzenia.

  • bit 27: FIFO_SPANSEM — wewnętrzna kolejka urządzenia.

  • bit 28: FIFO_SPANOUT — wewnętrzna kolejka urządzenia.

  • bit 29: FIFO_COLOUT — wewnętrzna kolejka urządzenia.

  • bit 30: FIFO_FXOUT — wewnętrzna kolejka urządzenia.

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łe urządzenie (zapisując 0x7f7ffffe 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 BATCH nie ma swojego bitu resetującego — jeśli sterownik chce go użyć, powinien zamiast tego zainicjować rejestry BATCH_*.

Przerwania

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

  • BATCH_WAIT — przerwanie używane do powiadomienia sterownika o wykonaniu wybranego polecenia przez blok BATCH. Wyzwalane, gdy wskaźnik BATCH_GET osiągnie wartość BATCH_WAIT.

  • JOB_DONE — wyzwalane przy poprawnym wykonaniu każdego polecenia.

  • FE_ERROR — wyzwalane, gdy firmware zauważy błędne polecenie bądź napotka wewnętrzny błąd.

  • CMD_ERROR — wyzwalane, gdy blok CMD wyjedzie poza bufor (czyli koniec bufora w środku polecenia).

  • PAGE_FAULT_BATCH — wyzwalane, gdy blok BATCH spowoduje błąd strony

  • PAGE_FAULT_CMD — wyzwalane, gdy blok CMD spowoduje błąd strony

  • PAGE_FAULT_SRD — wyzwalane, gdy blok SRD spowoduje błąd strony

  • PAGE_FAULT_SWR_DST — wyzwalane, gdy blok SWR spowoduje błąd strony przy zapisie do bufora ramki

  • PAGE_FAULT_COL_CMAP_B — wyzwalane, gdy blok COL spowoduje błąd strony przy czytaniu mapy kolorów

  • PAGE_FAULT_COL_SRC — wyzwalane, gdy blok COL spowoduje błąd strony przy czytaniu tekstury

  • PAGE_FAULT_SPAN_SRC — wyzwalane, gdy blok SPAN spowoduje błąd strony

  • PAGE_FAULT_SWR_TRANSMAP — wyzwalane, gdy blok SWR spowoduje błąd strony przy czytaniu mapy przezroczystości

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 + 0x0008: INTR

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

  • bit 0: BATCH_WAIT,

  • bit 1: JOB_DONE,

  • bit 4: FE_ERROR,

  • bit 5: CMD_ERROR,

  • bit 8: PAGE_FAULT_BATCH

  • bit 9: PAGE_FAULT_CMD

  • bit 10: PAGE_FAULT_SRD

  • bit 11: PAGE_FAULT_SWR_DST

  • bit 12: PAGE_FAULT_COL_CMAP_A

  • bit 13: PAGE_FAULT_COL_SRC

  • bit 14: PAGE_FAULT_SPAN_SRC

  • bit 15: PAGE_FAULT_SWR_TRANSMAP

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ń FE_ERROR oraz JOB_DONE bez zmiany stanu pozostałych przerwań.

BAR0 + 0x000c: 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.

Firmware

Blok rysujący opiera się na zaawansowanym procesorze odpowiadającym za przetwarzanie poleceń. Przed włączeniem bloku FE w ENABLE, sterownik musi załadować odpowiedni firmware na urządzenie — w przeciwnym wypadku, zachowanie urządzenia jest kompletnie niezdefiniowane. Firmware jest tablicą 32-bitowych słów, maksymalnie 16384-elementową. Jest dostarczany w pliku https://github.com/mwkmwkmwk/uharddoom/blob/master/udoomfw.h , a jego format i działanie jest tajemnicą handlową firmy DoomDevices® (i nie należy zadawać niewygodnych pytań na jego temat). Do ładowania firmware’u służą następujące rejestry:

BAR0 + 0x0100: FE_CODE_ADDR

Adres w pamięci kodu, który będziemy widoczny przez FE_CODE_WINDOW (dostępny do odczytu i zapisu). Musi być wielokrotnością 4.

BAR0 + 0x0104: FE_CODE_WINDOW

Okno do pamięci kodu — odczytuje bądź zapisuje słowo wybrane przez FE_CODE_ADDR, po czym zwiększa FE_CODE_ADDR o 4 (pozwalając na szybki sekwencyjny odczyt bądź zapis wielu słów kodu).

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,

  • kolejno zapisać wszystkie słowa tablicy udoomfw[] do FE_CODE_WINDOW,

  • zresetować wszystkie bloki urządzenia przez zapis 0x7f7ffffe do RESET,

  • zainicjować BATCH_PDP, BATCH_GET, BATCH_PUT i BATCH_WRAP, jeśli chcemy użyć bloku wczytywania pleceń,

  • wyzerować INTR przez zapis 0xff33,

  • włączyć używane przez nas przerwania w INTR_ENABLE,

  • włączyć wszystkie bloki urządzenia w ENABLE (być może z wyjątkiem BATCH).

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

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

Reset urządzenia

W przypadku zgłoszenia błędu przez urządzenie, należy je zresetować, wykonując następujące kroki:

  • jeśli używamy bloku BATCH:

    • wyłączyć blok BATCH w ENABLE,

    • usunąć zadanie, które spowodowało błąd z bufora BATCH, bądź ręcznie przesunąć wskaźnik BATCH_GET by je pominąć (bez tego, urządzenie natychmiast po rsecie wykona je po raz drugi, co prawdopodobnie również skończy się błędem),

    • dobrym pomysłem będzie również usunięcie wszystkich innych zadań wysłanych przez ten sam kontekst (zadanie można efektywnie usunąć przez zmianę jego pola CMD_SIZE na 0)

  • zatrzymać wszystkie bloki urządzenia przez zapis 0 do ENABLE

  • zresetować wszystkie bloki urządzenia przez zapis 0x7f7ffffe do RESET,

  • wyzerować INTR przez zapis 0xff33,

  • włączyć wszystkie bloki urządzenia w ENABLE (być może z wyjątkiem BATCH).

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 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ą szansę się przydać (oprócz nich, urządzenie zawiera wiele nieudokumentowanych rejestrów).

BAR0 + 0x004: 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: BATCH — wczytywanie zadań

  • bit 1: JOB — kontrola zadań

  • bit 2: CMD — wczytywanie poleceń użytkownika.

  • bit 3: FE — przetwarzanie poleceń przez firmware.

  • bit 4: SRD — proste wczytywanie danych

  • bit 5: SPAN — teksturowanie pasków

  • bit 6: COL — teksturowanie kolumn

  • bit 7: FX — efekty specjalne (FUZZ, mapy kolorów)

  • bit 8: SWR — mapy przezroczystości i zapis do bufora ramki.

  • bit 16: FIFO_SRDCMD — wewnętrzna kolejka urządzenia.

  • bit 17: FIFO_SPANCMD — wewnętrzna kolejka urządzenia.

  • bit 18: FIFO_COLCMD — wewnętrzna kolejka urządzenia.

  • bit 19: FIFO_FXCMD — wewnętrzna kolejka urządzenia.

  • bit 20: FIFO_SWRCMD — wewnętrzna kolejka urządzenia.

  • bit 21: FIFO_COLIN — wewnętrzna kolejka urządzenia.

  • bit 22: FIFO_FXIN — wewnętrzna kolejka urządzenia.

  • bit 24: FIFO_FESEM — wewnętrzna kolejka urządzenia.

  • bit 25: FIFO_SRDSEM — wewnętrzna kolejka urządzenia.

  • bit 26: FIFO_COLSEM — wewnętrzna kolejka urządzenia.

  • bit 27: FIFO_SPANSEM — wewnętrzna kolejka urządzenia.

  • bit 28: FIFO_SPANOUT — wewnętrzna kolejka urządzenia.

  • bit 29: FIFO_COLOUT — wewnętrzna kolejka urządzenia.

  • bit 30: FIFO_FXOUT — wewnętrzna kolejka urządzenia.

BAR0 + 0x800 * i, 0 <= i < 0x80: STATS[i]

Statystyki pracy urządzenia. Każdy indeks w tej tablicy jest osobnym licznikiem, zliczającym pewne zdarzenia na urządzeniu. Dostępne zdarzenia i ich indeksy są wymienione w pliku nagłówkowym.

Podręcznik użytkownika

Ten rozdział opisuje polecenia, których może użyć użytkownik urządzenia. Nie jest on potrzebny do napisania sterownika, ale może wyjaśnić działanie testów.

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 wielkości mieszczącej się w pamięci i wymiarach nie większych niż 65535×65535.

Bufor ramki jest definiowany dwoma parametrami: adresem wirtualnym w przestrzeni użytkownika oraz “przeskokiem” (pitch), czyli liczbą bajtów między początkami kolejnych wierszy. Obydwa te parametry muszą być wielokrotnością 64. Adres piksela (x, y) wewnątrz bufora to po prostu addr + x + y * pitch. Urządzenie nie ma informacji o wysokości ani szerokości bufora ramki — użytkownik powinien sam zapewnić, że używane współrzędne mieszczą się w zakresie.

Tekstury kolumnowe

Tekstury kolumnowe zawierają dane obrazu, który polecenie DRAW_COLUMNS 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. Teksturę kolumnową i miejsce w niej wybiera się podając urządzeniu adres wirtualny kolumny w teksturze i wysokość tej tekstury (maksymalnie 65536) — współrzędne w teksturze wyjeżdżające poza jej wysokość będą automatycznie zawijane modulo wysokość.

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_SPANS do rysowania podłóg i sufitów. W oryginalnej grze, mają zawsze wymiary 64×64 tekseli, i zajmują 2**12 bajtów. W urządzeniu Ultimate HardDoom™, są obsługiwane nieco bardziej ogólnie i mogą mieć dowolne wymiary będące potęgami dwójki z zakresu 1..65536. Są definiowane przez 4 parametry:

  • adres wirtualny w przestrzeni użytkownika

  • przeskok (jak przy buforach ramki)

  • log2 z szerokości (ULOG)

  • log2 z wysokości (VLOG)

Tak jak przy teksturach kolumnowych, wszystkie współrzędne są zawijane modulo wymiary tekstury.

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. Ich adres musi być również 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ą)

Mapę kolorów definiuje się przez jej adres wirtualny.

Mapy przezroczystości

Aby uzyskać efekt przezroczystości w grafice komputerowej, potrzebna jest jakaś funkcja scalająca dwa kolory — kolor tła oraz kolor rysowanego obiektu. W przypadku grafiki używającej schematu True Color, wystarczyłoby zsumować składowe kolorów z odpowiednimi wagami. Jednak w przypadku grafiki używającej palety kolorów, trzeba to zrobić używając obliczonej wcześniej tabeli — mapy przezroczystości. Mapa przezroczystości jest dwuwymiarową tablicą bajtów o rozmiarze 256×256. Jest to po prostu tablica mapująca pary kolorów na scalony kolor i definiuje się przez jej adres wirtualny.

Polecenia

Użytkownik zleca urządzeniu pracę przez przygotowanie bufora poleceń i przekazanie jego adresu oraz rozmiaru do jądra, które poprosi urządzenie o wykonanie go. Bufor poleceń składa się z 32-bitowych słów w formacie little-endian. Polecenia składają się z wielu słów.

Urządzenie obsługuje 7 różnych poleceń:

  • 0: FILL_RECT — wypełnia prostokątny obszar jednym kolorem.

  • 1: DRAW_LINE — rysuje prostą jednokolorową linię między dwoma punktami.

  • 2: BLIT — kopiuje prostokątny obszar między dwoma buforami ramki.

  • 3: WIPE — rysuje efekt przejścia między ekranami

  • 4: DRAW_COLUMNS — rysuje kolumny pikseli używając tekstury kolumnowej.

  • 5: DRAW_FUZZ — aplikuje efekt rozmytego cienia na kolumnie pikseli.

  • 6: DRAW_SPANS — rysuje paski pikseli używając tekstury płaskiej.

Typ polecenia jest określony przez niskie 8 bitów pierwszego słowa polecenia.

Wszystkie nieużywane pola muszą być ustawione na 0 — w przeciwnym wypadku, zachowanie urządzenia nie jest zdefiniowane.

Polecenie FILL_RECT

Polega na wypełnieniu zadanego prostokąta w buforze ramki jednym kolorem. Składa się z 5 słów:

  • słowo 0: nagłówek

    • bity 0-7: typ polecenia (0)

    • bity 8-15: kolor do wypełnienia

  • słowo 1: wskaźnik na bufor ramki

  • słowo 2: przeskok bufora ramki

  • słowo 3: współrzędne lewego górnego rogu prostokąta

    • bity 0-15: X

    • bity 16-31: Y

  • słowo 4: wymiary prostokąta

    • bity 0-15: szerokość

    • bity 16-31: wysokość

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

Polecenie DRAW_LINE

Rysuje jednokolorową linię prostą o szerokości 1 piksela między podanymi dwoma punktami w zadanym buforze ramki. Składa się z 5 słów:

  • słowo 0: nagłówek

    • bity 0-7: typ polecenia (1)

    • bity 8-15: kolor linii

  • słowo 1: wskaźnik na bufor ramki

  • słowo 2: przeskok bufora ramki

  • słowo 3: współrzędne jednego końca linii

    • bity 0-15: X

    • bity 16-31: Y

  • słowo 4: współrzędne drugiego końca linii

    • bity 0-15: X

    • bity 16-31: Y

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

Polecenie BLIT

Polega na skopiowaniu zadanego prostokąta z jednego bufora ramki bądź tekstury płaskiej do drugiego bufora ramki, być może ze skalowaniem. Składa się z 9 słów:

  • słowo 0: nagłówek

    • bity 0-7: typ polecenia (2)

    • bity 16-20: ULOG (log2 z szerokości źródłowego bufora — wszystkie współrzędne U będą brane modulo 2**ULOG)

    • bity 24-28: VLOG (log2 z wysokości źródłowego bufora — wszystkie współrzędne V będą brane modulo 2**VLOG)

  • słowo 1: wskaźnik na docelowy bufor ramki

  • słowo 2: przeskok docelowego bufora ramki

  • słowo 3: współrzędne lewego górnego rogu docelowego prostokąta

    • bity 0-15: X

    • bity 16-31: Y

  • słowo 4: wymiary docelowego prostokąta

    • bity 0-15: szerokość

    • bity 16-31: wysokość

  • słowo 5: wskaźnik na źródłowy bufor ramki

  • słowo 6: przeskok źródłowego bufora ramki

  • słowo 7: współrzędne lewego górnego rogu źródłowego prostokąta

    • bity 0-15: U (czyli X)

    • bity 16-31: V (czyli Y)

  • słowo 8: wymiary źródłowego prostokąta

    • bity 0-15: szerokość

    • bity 16-31: wysokość

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

Polecenie jest używane w grze do skopiowania przygotowanego wcześniej panelu dolnego, 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 -).

Polecenie WIPE

Rysuje efekt przejścia między dwoma ekranami (“wipe” albo “melt”). Składa się z 9 słów nagłówka + 1 słowa na każdą kolumnę przetwarzanego prostokąta. Nagłówek jest następujący:

  • słowo 0: typ polecenia (3)

  • słowo 1: wskaźnik na docelowy bufor ramki

  • słowo 2: przeskok docelowego bufora ramki

  • słowo 3: współrzędne lewego górnego rogu prostokąta

    • bity 0-15: X

    • bity 16-31: Y

  • słowo 4: wymiary prostokąta

    • bity 0-15: szerokość

    • bity 16-31: wysokość

  • słowo 5: wskaźnik na pierwszy źródłowy bufor ramki

  • słowo 6: przeskok pierwszego źródłowego bufora ramki

  • słowo 7: wskaźnik na drugi źródłowy bufor ramki

  • słowo 8: przeskok drugiego źródłowego bufora ramki

Dla każdej kolumny (czyli tyle razy, ile wynosi szerokość prostokąta w pikselach):

  • słowo 0: offset Y danej kolumny — pierwsze offset pikseli będzie brane z pierwszego źródłowego bufora, reszta z drugiego źródłowego bufora.

Jest używane w grze przy zmianie poziomów, rozpoczęciu/zakończeniu gry, itp.

Polecenie DRAW_COLUMNS

Rysuje teksturowane kolumny do bufora ramki. Teksturowane piksele mogą być jednocześnie przetwarzane przez dwie mapy kolorów: mapę A, wspólną dla wszystkich kolumn w poleceniu, i mapę B, wybieraną osobno dla każdej kolumny. Składa się z nagłówka wybierającego opcje operacji i bufor docelowy oraz ze zmiennej liczby opisów kolumn. Nagłówek wygląda następująco:

  • słowo 0: nagłówek

    • bity 0-7: typ polecenia (4)

    • bit 8: CMAP_A_EN — jeśli ustawiony, włącza mapę kolorów A

    • bit 9: CMAP_B_EN — jeśli ustawiony, włącza mapę kolorów B

    • bit 12: TRANS_EN — jeśli ustawiony, włącza mapę przezroczystości

    • bity 16-31: liczba kolumn do narysowania

  • słowo 1: wskaźnik na docelowy bufor ramki

  • słowo 2: przeskok docelowego bufora ramki

  • słowo 3 (obecne tylko jeśli CMAP_A_EN): wskaźnik na mapę kolorów A

  • słowo 4 (obecne tylko jeśli TRANS_EN): wskaźnik na mapę przezroczystości

Po nagłówku następują opisy kolumn, każdy w następującym formacie:

  • słowo 0:

    • bity 0-15: współrzędna X kolumny

    • bity 16-31: SRC_HEIGHT — wysokość tekstury

  • słowo 1:

    • bity 0-15: początkowa (górna) współrzędna Y kolumny

    • bity 16-31: końcowa (dolna) współrzędna Y kolumny

  • słowo 2: SRC_PTR — wskaźnik na kolumnę w teksturze

  • słowo 3: USTART — początkowa współrzędna w teksturze (dla górnego piksela), zapisana jako zmiennoprzecinkowa liczba z 16 bitami całkowitymi i 16 bitami po przecinku

  • słowo 4: USTEP — przeskok współrzędnej w teksturze dla kolejnych pikseli

  • słowo 5 (obecne tylko jeśli CMAP_B_EN): wskaźnik na mapę kolorów B

Rysowana kolumna jest teksturowana za pomocą następującego algorytmu:

# Obliczenie koloru dla piksela (x, y).
coord = (USTART + USTEP * (y - Y0)) >> 16
    coord %= SRC_HEIGHT
color = *(SRC_PTR + coord)
if CMAP_A_EN:
    color = *(CMAP_A_PTR + color)
if CMAP_B_EN:
    color = *(CMAP_B_PTR + color)
if TRANS_EN:
    color = *(TRANSMAP_PTR + (orig_color << 8) + color)

Polecenie DRAW_FUZZ

Aplikuje efekt rozmytego cienia na kolumnach w buforze ramki. Składa się z nagłówka wybierającego opcje operacji i bufor docelowy oraz ze zmiennej liczby opisów kolumn. Nagłówek wygląda następująco:

  • słowo 0: nagłówek

    • bity 0-7: typ polecenia (5)

    • bity 16-31: liczba kolumn do narysowania

  • słowo 1: wskaźnik na docelowy bufor ramki

  • słowo 2: przeskok docelowego bufora ramki

  • słowo 3:

    • bity 0-15: FUZZSTART — najmniejsza współrzędna Y, którą wolno przeczytać algorytmowi

    • bity 16-31: FUZZEND — największa współrzędna Y, którą wolno przeczytać algorytmowi

  • słowo 4: wskaźnik na mapę kolorów

Po nagłówku następują opisy kolumn, każdy w następującym formacie:

  • słowo 0:

    • bity 0-15: współrzędna X kolumny

    • bity 16-21: FUZZPOS — źródło losowości dla algorytmu (z zakresu 0-49)

  • słowo 1:

    • bity 0-15: początkowa (górna) współrzędna Y kolumny

    • bity 16-31: końcowa (dolna) współrzędna Y kolumny

Polecenie DRAW_SPANS

Rysuje teksturowane paski do bufora ramki. Opcjonalnie aplikuję mapy kolorów na paski. Składa się z nagłówka wybierającego opcje operacji, bufor docelowy, i teksturę płaską oraz ze zmiennej liczby opisów pasków. Nagłówek wygląda następująco:

  • słowo 0: nagłówek

    • bity 0-7: typ polecenia (6)

    • bit 8: CMAP_EN — jeśli ustawiony, włącza mapę kolorów

    • bit 12: TRANS_EN — jeśli ustawiony, włącza mapę przezroczystości

    • bity 16-20: ULOG (log2 z szerokości źródłowego bufora — wszystkie współrzędne U będą brane modulo 2**ULOG)

    • bity 24-28: VLOG (log2 z wysokości źródłowego bufora — wszystkie współrzędne V będą brane modulo 2**VLOG)

  • słowo 1: wskaźnik na docelowy bufor ramki

  • słowo 2: przeskok docelowego bufora ramki

  • słowo 3: współrzędnie Y

    • bity 0-15: Y0 — współrzędna Y pierwszego paska

    • bity 16-31: Y1 — współrzędna Y ostatniego paska

    Współrzędne mogą być podane w dowolnej kolejności — jeśli Y1 > Y0, współrzędne kolejnych pasków będą rosnąć, a jeśli Y1 < Y0, będą maleć.

  • słowo 4: wskaźnik na źródłowy bufor tekstury płaskiej

  • słowo 5: przeskok źródłowego bufora tekstury płaskiej

  • słowo 6 (obecne tylko jeśli TRANS_EN): wskaźnik na mapę przezroczystości

Po nagłówku następuje abs(Y0 - Y1) + 1 opisów pasków, każdy w następującym formacie:

  • słowo 0:

    • bity 0-15: początkowa (lewa) współrzędna X paska

    • bity 16-31: końcowa (prawa) współrzędna X paska

  • słowo 1: USTART — początkowa współrzędna U w teksturze (dla lewego piksela), zapisana jako zmiennoprzecinkowa liczba z 16 bitami całkowitymi i 16 bitami po przecinku

  • słowo 2: VSTART — początkowa współrzędna V w teksturze

  • słowo 3: USTEP — przeskok współrzędnej U w teksturze dla kolejnych pikseli

  • słowo 4: VSTEP — przeskok współrzędnej V w teksturze dla kolejnych pikseli

  • słowo 5 (jeśli CMAP_EN): wskaźnik na mapę kolorów

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

# Obliczenie koloru dla piksela (x, y).
u = (USTART + USTEP * (x - X0)) >> 16 % (1 << ULOG)
v = (VSTART + VSTEP * (x - X0)) >> 16 % (1 << VLOG)
color = *(SRC_PTR + u + SRC_PITCH * v)
if CMAP_EN:
    color = *(CMAP_PTR + color)
if TRANS_EN:
    color = *(TRANSMAP_PTR + (orig_color << 8) + color)