Urządzenie Final HardDoom™

Grafika w grze Final 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 Final 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 Final 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 0x1996.

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.

Bufory i blok stronicowania

Wszystkie dane i polecenia używane przez urządzenie są zawarte w stronicowanych buforach. Urządzenie może jednocześnie używać do 64 buforów — blok stronicowania ma 64 “sloty” (identyfikowane przez liczby 0..63), w które można wpiąć bufor za pomocą polecenia BIND_SLOT. Każdy slot ma następujące atrybuty, wybierane przy wpięciu bufora:

  • PRESENT: jeśli ustawiony, do slota wpięty jest bufor, ma tabelę stron, i może być użyty przez urządzenie; jeśli nie jest ustawiony, każda próba użycia tego slotu spowoduje błąd

  • WRITABLE: jeśli ustawiony, bufor podpięty przez ten slot jest dostępny do odczytu i zapisu; jeśli nie jest ustawiony, odczyty są dozwolone, ale zapisy będą powodować błąd

  • USER: jeśli ustawiony, ten slot może być wykorzystywany przez wszystkie polecenia; jeśli nie jest ustawiony, dozwolne jest wyłącznie użycie go jako głównego bufora poleceń bądź jako parametr polecenia CALL, a użycie go przez polecenia użytkownika spowoduje błąd

  • PT_ADDR: adres fizyczny tabeli stron

  • PITCH: przeskok pomiędzy kolejnymi liniami (pitch): istotne tylko, gdy dany slot zostanie użyty jako bufor ramki bądź bufor tekstur płaskich; musi być wielokrotnością 64 bajtów i być mniejszy niż 4 MiB

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

  • jądro podaje urządzeniu adres fizyczny katalogu stron przy podpinaniu bufora

  • bity 12-21 adresu wirtualnego wybierają wpis w tabeli stron (page table), który zawiera adres fizyczny strony

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

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 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).

  • 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 nie czytać ciągle tabeli stron, urządzenie może zapamiętywać dane z tabel i katalogów stron w buforach TLB. Wykonanie polecenia BIND_SLOT 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 w rejestrze RESET.

Jeśli urządzenie napotka niepoprawny adres wirtualny (bez flagi PRESENT w slocie bądź w tabeli stron), 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 tabelę stron) i wznowić zadanie, bądź też przerwać zadanie przez wykonanie procedury resetu urządzenia. Aby wznowić zadanie, należy poprawić odpowiednie wpisy w tabeli 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_CMD_MAIN: blok CMD, wczytywanie poleceń z głównego bufora

  • PAGE_FAULT_CMD_SUB: blok CMD, wczytywanie poleceń z bufora wywołanego przez CALL

  • 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 + 0x0540: MMU_CLIENT_VA[MMU_CLIENT_CMD_MAIN]

Adres wirtualny odpowiadający błędowi PAGE_FAULT_CMD_MAIN. Bity 0-21 to właściwy adres wirtualny, bity 24-29 to numer slotu.

BAR0 + 0x0544: MMU_CLIENT_VA[MMU_CLIENT_CMD_SUB]

Adres wirtualny odpowiadający błędowi PAGE_FAULT_CMD_SUB.

BAR0 + 0x0548: MMU_CLIENT_VA[MMU_CLIENT_SRD]

Adres wirtualny odpowiadający błędowi PAGE_FAULT_SRD.

BAR0 + 0x054c: MMU_CLIENT_VA[MMU_CLIENT_SWR_DST]

Adres wirtualny odpowiadający błędowi PAGE_FAULT_SWR_DST.

BAR0 + 0x0550: MMU_CLIENT_VA[MMU_CLIENT_COL_CMAP_B]

Adres wirtualny odpowiadający błędowi PAGE_FAULT_COL_CMAP_B.

BAR0 + 0x0554: MMU_CLIENT_VA[MMU_CLIENT_COL_SRC]

Adres wirtualny odpowiadający błędowi PAGE_FAULT_COL_SRC.

BAR0 + 0x0558: MMU_CLIENT_VA[MMU_CLIENT_SPAN_SRC]

Adres wirtualny odpowiadający błędowi PAGE_FAULT_SPAN_SRC.

BAR0 + 0x055c: MMU_CLIENT_VA[MMU_CLIENT_SWR_TRANSMAP]

Adres wirtualny odpowiadający błędowi PAGE_FAULT_SWR_TRANSMAP.

Wysyłanie poleceń

Urządzenia używa się przez wysyłanie mu poleceń. Polecenia składają się ze zmiennej liczby 32-bitowych słów w formacie little-endian. Istnieje 12 różnych typów poleceń, rozróżnianych po niskich 4 bitach pierwszego słowa.

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

Istnieją trzy sposoby na wysłanie poleceń do urządzenia:

  1. CMD_MANUAL — ręczne wysyłanie, przez zapis kolejnych słów polecenia do rejestru MMIO urządzenia.

  2. CMD_MAIN — urządzenie automatycznie wczytuje polecenia z głównego bufora poleceń, który jest cyklicznym buforem 32-bitowych słów z dwoma rejestrami wskaźnikowymi opisującymi obecne zajęcie bufora. Nie może być używany jednocześnie z CMD_MANUAL.

  3. CMD_SUB — urządzenie automatycznie wczytuje polecenia z bufora poleceń użytkownika, który jest prostą tablicą słów opisaną przez początek i koniec. Aby użyć tego sposobu, należy wysłać polecenie CALL opisujące bufor używając CMD_MANUAL bądź CMD_MAIN — urządzenie wtedy natychmiast zawiesi przetwarzanie kolejnych poleceń po CALL i zacznie zamiast tego przetwarzać polecenia z wywołanego bufora, po czym (po dojściu do końca) wróci do przetwarzania poprzedniego strumienia poleceń. W tym buforze dozwolone jest używanie wyłącznie poleceń użytkownika — próba wysłania przez CMD_SUB poleceń BIND_SLOT, CLEAR_SLOTS, CALL, bądź FENCE spowoduje błąd.

Ręczne wysyłanie poleceń

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

BAR0 + 0x008c: CMD_MANUAL_FREE

Rejestr tylko do odczytu. Odczytana wartość jest liczbą wolnych miejsc w kolejce poleceń (tyle słów można natychmiast zapisać do CMD_MANUAL_FEED bez obaw o FEED_OVERFLOW). Kolejka ta ma maksymalną pojemność 255 słów. Ten rejestr będzie zawsze równy 0, jeśli włączony jest bufor CMD_MAIN. Można założyć, że ten rejestr będzie równy 255 bezpośrednio po resecie urządzenia bądź gdy zostaną przetworzone wszystkie wcześniej wysłane polecenia.

BAR0 + 0x008c: CMD_MANUAL_FEED

Rejestr tylko do zapisu. Każdy zapis do tego rejestru wysyła jedno słowo do kolejki poleceń. Jeśli kolejka jest już pełna (CMD_MANUAL_FREE == 0), zamiast tego wyzwalane jest przerwanie FEED_ERROR.

Automatyczne wczytywanie poleceń

Blok CMD_MAIN pozwala na automatyczne wczytywanie polecań przez urządzenie — sterownik zapisuje polecenia do wykonania w cyklicznym buforze, a urządzenie będzie je automatycznie wczytywać do swojej kolejki. Aby użyć tego bloku, należy stworzyć bufor zadań, wpiąć go do jednego ze slotów (przez wysłanie polecenia BIND_SLOT za pomocą CMD_MANUAL_FEED i oczekiwania na zakończenie go), po czym użyć następujących rejestrów:

BAR0 + 0x0080: CMD_MAIN_SETUP

Ustawienia głównego bufora poleceń:

  • bity 0-21: WRAP: adres końca bufora poleceń; gdy urządzenie dojdzie do tego adresu, automatycznie wróci do adresu 0.

  • bity 24-29: SLOT: numer slotu zawierającego główny bufor poleceń.

  • bit 31: ENABLE: włączenie automatycznego wczytywania poleceń. Gdy ten bit jest ustawiony, automatyczne wczytywanie poleceń jest aktywne i urządzenie będzie wczytywało polecenia zawsze, gdy CMD_MAIN_GET != CMD_MAIN_PUT. Ustawienie tego bitu jednocześnie wyłącza możliwość użycia CMD_MANUAL_FEED.

BAR0 + 0x0084: CMD_MAIN_GET

Adres wirtualny, z którego urządzenie ma wczytać następne słowo do kolejki poleceń. Po wczytaniu tego słowa, ten wskaźnik zostanie automatycznie zwiększony o 4 przez urządzenie. Gdyby to zwiększenie miało spowodować, że CMD_MAIN_GET == CMD_MAIN_SETUP.WRAP, wskaźnik jest zamiast tego ustawiany z powrotem na 0.

BAR0 + 0x0088: CMD_MAIN_PUT

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

Wszystkie adresy używane przez blok CMD_MAIN muszą być wielokrotnością 4 bajtów (czyli rozmiaru słowa).

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

while True:
    # Gdy jest miejsce w kolejce, automatyczne wczytywanie jest włączone, a bufor
    # nie jest pusty...
    if internal_fifo.free != 0 and CMD_MAIN_GET != CMD_MAIN_PUT and CMD_MAIN_SETUP.ENABLE:
        # Wczytaj słowo z adresu GET.
        cmd = mem_read_le32(CMD_MAIN_SETUP.SLOT, CMD_MAIN_GET)
        # Zapisz do kolejki.
        internal_fifo.push(cmd)
        # Podbij GET, być może zastosuj wrap.
        CMD_MAIN_GET += 4
        if CMD_MAIN_GET == CMD_MAIN_SETUP.WRAP:
            CMD_MAIN_GET = 0

Polecenie NOP

Nic nie robi. Składa się z 1 słowa. Może być użyte do zapchania nieużywanych słów w buforze poleceń, jeśli czujemy taką potrzebę.

  • słowo 0: nagłówek

    • bity 0-3: typ polecenia (0x0)

Polecenie BIND_SLOT

Służy do wpięcia bufora do slotu. Składa się z 2 słów:

  • słowo 0: nagłówek

    • bity 0-3: typ polecenia (0x8)

    • bity 4-9: SLOT — numer slotu, który przepinamy

    • bity 10-25: PITCH — przeskok bufora, przesunięty w prawo o 6 bitów (jeśli nie zamierzamy używać tego bufora jako bufora ramki ani tekstury płaskiej, możemy tu wpisać 0).

  • słowo 1:

    • bit 0: PRESENT

    • bit 1: WRITABLE

    • bit 2: USER

    • bity 4-31: PA: adres fizyczny tabeli stron, przsunięty w prawo o 12 bitów

To polecenie może być wysłane tylko przez CMD_MANUAL bądź CMD_MAIN.

Jeśli flaga PRESENT jest ustawiona na 0, wartość reszty pól nie ma znaczenia.

Polecenie CLEAR_SLOTS

Służy do wyczysczenia slotów. Składa się z 3 słów:

  • słowo 0: nagłowek

    • bity 0-3: typ polecenia (0x9)

  • słowo 1: bity 0-31 maski slotów do wyczyszczenia

  • słowo 2: bity 32-63 maski

Polecenie czyści wpięcia wszystkich slotów, których odpowiadający bit w masce jest zapalony. Jest równoważne wysłaniu odpowiedniej liczby poleceń BIND_SLOT z wyłączoną flagą PRESENT.

To polecenie może być wysłane tylko przez CMD_MANUAL bądź CMD_MAIN.

Polecenie CALL

Zawiesza przetwarzanie poleceń z głównej kolejki poleceń, rozpoczyna wczytywanie i przetwarzanie poleceń z podanego bufora użytkownika. Składa się z 2 słów.

  • słowo 0: nagłówek

    • bity 0-3: typ polecenia (0xa)

    • bity 4-9: SLOT — wybiera slot zawierający polecenia do wykonania

    • bity 10-29: ADDR — adres wirtualny początku poleceń w buforze, przesunięty w prawo o 2 bity

  • słowo 1: długość poleceń, w bajtach; musi być wielokrotnością 4 i być nie większa niż 4MiB.

Wskazane polecenia zostaną wykonane bezpośrednio po poleceniu CALL i przed kolejnym poleceniem wysłanym po CALL przez CMD_MAIN bądź CMD_MANUAL.

To polecenie może być wysłane tylko przez CMD_MANUAL bądź CMD_MAIN.

Polecenie FENCE

Służy do powiadamiania sterownika o zakończeniu wykonywania wszystkich poprzednich poleceń. Składa się z 1 słowa.

  • słowo 0: nagłówek

    • bity 0-3: typ polecenia (0xb)

    • bity 4-31: VAL — parametr; nie ma zdefinowanej semantyki przez urządzenie, zalecamy użycie go jako numeru kolejnej serii poleceń (modulo 2**28).

To polecenie może być wysłane tylko przez CMD_MANUAL bądź CMD_MAIN.

To polecenie ma następujące efekty:

  1. Oczekuje na pełne zakończenie wykonania wszystkich wcześniejszych poleceń.

  2. Ustawia wartość rejestru CMD_FENCE_LAST na parametr VAL

  3. Jeśli parametr VAL jest równy rejestrowi CMD_FENCE_WAIT, wyzwala przerwanie FENCE_WAIT.

BAR0 + 0x0090: CMD_FENCE_LAST

28-bitowy rejestr do odczytu i zapisu, ustawiany na parametr VAL przy wykonaniu polecenia FENCE. Może być użyty do ustalenia, które polecenia już zostały wykonane. Zalecamy zapisanie go ze sterownika dokładnie raz, przed uruchomieniem urządzenia, aby nadać mu wartość początkową.

BAR0 + 0x0094: CMD_FENCE_WAIT

Rejestr do odczytu i zapisu, służy do wyzwolenia przerwania gdy podane polecenie FENCE zostanie wykonane.

  • bity 0-27: VAL, wartość na którą sterownik czeka. Gdy zostanie wykonane polecenie FENCE z równą wartością, a bit DISABLE nie jest ustawiony, zostje wyzwolone przerwanie FENCE_WAIT.

  • bit 31: DISABLE — jeśli ustawiony, powyższa wartość jest ignorowana, a przerwanie FENCE_WAIT nie jest wyzwalane.

Błąd CMD_ERROR

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

BAR0 + 0x0098: CMD_ERROR_CODE

Kod błędu. Jeden z:

  • 0x0: SUB_INCOMPLETE — polecenie wysłane przez bufor użytkownika (CMD_SUB) zostało ucięte przez koniec listy poleceń. CMD_ERROR_DATA to adres wirtualny końca listy poleceń.

  • 0x1: UNK_COMMAND — nieznany typ polecenia. CMD_ERROR_DATA nie jest ustawiany.

  • 0x2: PRIV_COMMAND — uprzywilejowane polecenie wysłane przez bufor użytkoenika. CMD_ERROR_DATA nie jest ustawiany.

  • 0x3: INVALID_SLOT — użycie niepodpiętego slotu. CMD_ERROR_DATA zawiera numer slotu.

  • 0x4: KERNEL_SLOT — użycie slotu bez atrybutu USER w poleceniu użytkownika. CMD_ERROR_DATA zawiera numer slotu.

  • 0x5: RO_SLOT — użycie slotu bez atrybutu WRITABLE do zapisu. CMD_ERROR_DATA zawiera numer slotu.

  • 0x6: 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. CMD_ERROR_DATA zawiera błędne słowo polecenia.

  • 0x7: DRAW_SPANS_X_REV — początkowa współrzędna x w poleceniu DRAW_SPANS jest większa niż końcowa wspołrzędna. CMD_ERROR_DATA zawiera błędne słowo polecenia.

BAR0 + 0x009c: CMD_ERROR_DATA

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

BAR0 + 0x00a0: CMD_INFO

Informacja o źródle błędnego polecenia:

  • bity 0-21: PTR, adres wirtualny nagłówka polecenia

  • bity 24-29: SLOT, z którego polecenie było wczytane

  • bit 30: SUB — jeśli ustawione, polecenie zostało wczytane z bufora użytkownika (wywołanego przez CALL).

  • bit 31: MANUAL — jeśli ustawione, polecenie było wysłane przez CMD_MANUAL_FEED i pola SLOT oraz PTR są nieważne.

BAR0 + 0x00a4: CMD_HEADER

Kopia nagłówka (słowa 0) błędnego polecenia.

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: CMD — wczytywanie poleceń

  • bit 1: FE — przetwarzanie poleceń przez firmware

  • bit 2: SRD — proste wczytywanie danych

  • bit 3: SPAN — teksturowanie pasków

  • bit 4: COL — teksturowanie kolumn

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

  • bit 6: 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,

  • 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 0: CMD — wczytywanie poleceń

  • bit 1: FE — przetwarzanie poleceń przez firmware

  • bit 2: SRD — proste wczytywanie danych

  • bit 3: SPAN — teksturowanie pasków

  • bit 4: COL — teksturowanie kolumn

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

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

  • bit 7: MMU — blok stronicowania

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

  • bit 9: TLB — wpisy TLB. Wszystkie wpisy tabel i katalogów stron zapisane w TLB są kasowane i, w razie potrzeby, zostaną wczytane na nowo.

  • 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 0x7f7ff3ff 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.

Przerwania

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

  • FENCE_WAIT — przerwanie używane do powiadomienia sterownika o wykonaniu wybranego polecenia FENCE przez blok CMD.

  • FEED_ERROR — użycie rejestru CMD_MANUAL_FEED, gdy nie ma miejsca w kolejce, bądź gdy aktywne jest automatyczne wczytywanie poleceń.

  • CMD_ERROR — wyzwalane, gdy firmware zauważy błędne polecenie.

  • FE_ERROR — wyzwalane, gdy firmware napotka wewnętrzny błąd.

  • PAGE_FAULT_CMD_MAIN — wyzwalane, gdy blok CMD spowoduje błąd strony przy wczytywaniu głównego bufora poleceń

  • PAGE_FAULT_CMD_SUB — wyzwalane, gdy blok CMD spowoduje błąd strony przy wczytywaniu poleceń użytkownika

  • 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: FENCE_WAIT,

  • bit 1: FEED_ERROR,

  • bit 2: CMD_ERROR,

  • bit 3: FE_ERROR,

  • bit 8: PAGE_FAULT_CMD_MAIN

  • bit 9: PAGE_FAULT_CMD_SUB

  • 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 0xa spowoduje wyzerowanie przerwań FE_ERROR oraz FEED_ERROR 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/fharddoom/blob/main/fdoomfw.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 fdoomfw[] do FE_CODE_WINDOW,

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

  • wyzerować INTR przez zapis 0xff0f,

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

  • włączyć wszystkie bloki urządzenia w ENABLE.

  • zainicjować CMD_FENCE_LAST i CMD_FENCE_WAIT, jeśli czujemy taką potrzebę.

Jeśli do tego chcemy użyć automatycznego wczytywania poleceń:

  • skonstruować tabelę stron głównego bufora poleceń

  • wysłać ręcznie (przez CMD_MANUAL_FEED) polecenie BIND_SLOT wpinające ten bufor w któryś slot (w zadaniu sloty 60-63 są zarezerwowane dla jądra)

  • poczekać na zakończenie tego polecenia (przez czekanie na wyzerowanie rejestru STATUS bądź wysłanie polecenia FENCE po nim)

  • zainicjować CMD_MAIN_GET i CMD_MAIN_PUT

  • zainicjować CMD_MAIN_SETUP

Ż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:

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

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

  • wyzerować INTR przez zapis 0xff0f,

  • włączyć wszystkie bloki urządzenia w ENABLE

  • zainicjować CMD_FENCE_LAST i CMD_FENCE_WAIT, jeśli czujemy taką potrzebę.

Jeśli do tego używamy automatycznego wczytywania poleceń:

  • przewinąć wskaźnik CMD_MAIN_GET tak, by wskazywał na następne zadanie po tym nieudanym

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

  • wysłać ręcznie (przez CMD_MANUAL_FEED) polecenie BIND_SLOT wpinające ten bufor w któryś slot (w zadaniu sloty 60-63 są zarezerwowane dla jądra)

  • poczekać na zakończenie tego polecenia (przez czekanie na wyzerowanie rejestru STATUS bądź wysłanie polecenia FENCE po nim)

  • zainicjować CMD_MAIN_GET i CMD_MAIN_PUT

  • zainicjować CMD_MAIN_SETUP

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: CMD — wczytywanie poleceń użytkownika.

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

  • bit 2: SRD — proste wczytywanie danych

  • bit 3: SPAN — teksturowanie pasków

  • bit 4: COL — teksturowanie kolumn

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

  • bit 6: 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: tabelą stron 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 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 indeks slotu bufora z teksturą, adres wirtualny kolumny tekstury w tym buforze, 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 Final 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:

  • indeks slotu bufora z teksturą

  • przeskok (jak przy buforach ramki)

  • log2 z szerokości (ULOG) — 6 w oryginalnej grze

  • log2 z wysokości (VLOG) — 6 w oryginalnej grze

Tak jak przy teksturach kolumnowych, współrzędne są zawijane modulo wymiary tekstury gdy wyjadą poza ramy zdefiniowane przez ULOG i VLOG. Dokładny sposób zawijania jest jednak inny niż przy teksturach kolumnowych: o ile przy teksturach kolumnowych współrzędna jest po prostu brana modulo wysokość, o tyle przy teksturach płaskich oryginalne współrzędne są brane bez zmian, za to blokowana jest modyfikacja bitów powyżej ULOG/VLOG-tego bitu współrzędnej przy przechodzeniu do kolejnych tekseli (co efektywnie zawija teksturowanie w ramach jednego prostokąta 2**ULOG×2**VLOG w ramach większego prostokąta, pozwalająć na załadowanie całego atlasu tekstur do jednego bufora i wybranie konkretnej tekstury przez wysokie bity współrzędnych).

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. 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:

  • indeks slotu bufora z mapami kolorów

  • indeks konkretnej mapy kolorów w tym buforze (jest mnożony przez 256 aby otrzymać adres wirtualny tej mapy)

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. Definiuje się ją przez:

  • indeks slotu bufora z mapami przezroczystości

  • indeks konkretnej mapy przezroczystości w tym buforze (jest mnożony przez 65536 aby otrzymać adres wirtualny tej mapy)

Polecenie FILL_RECT

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

  • słowo 0: nagłówek

    • bity 0-3: typ polecenia (0x1)

    • bity 4-9: SLOT_DST — numer slotu z docelowym buforem ramki

    • bity 24-31: kolor do wypełnienia

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

    • bity 0-15: X

    • bity 16-31: Y

  • słowo 2: 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 3 słów:

  • słowo 0: nagłówek

    • bity 0-3: typ polecenia (0x2)

    • bity 4-9: SLOT_DST — numer slotu z docelowym buforem ramki

    • bity 24-31: kolor linii

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

    • bity 0-15: X

    • bity 16-31: Y

  • słowo 2: 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 5 słów:

  • słowo 0: nagłówek

    • bity 0-3: typ polecenia (0x3)

    • bity 4-9: SLOT_DST — numer slotu z docelowym buforem ramki

    • bity 16-21: SLOT_SRC — numer slotu ze źródłowym buforem ramki bądź teksturą płaską

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

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

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

    • bity 0-15: X

    • bity 16-31: Y

  • słowo 2: wymiary docelowego prostokąta

    • bity 0-15: szerokość

    • bity 16-31: wysokość

  • słowo 3: 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 4: 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 3 słów nagłówka + 1 słowa na każdą kolumnę przetwarzanego prostokąta. Nagłówek jest następujący:

  • słowo 0:

    • bity 0-3: typ polecenia (0x4)

    • bity 4-9: SLOT_DST — numer slotu z docelowym buforem ramki

    • bity 16-21: SLOT_SRC_A — numer slotu z pierwszym źródłowym buforem ramki

    • bity 24-29: SLOT_SRC_B — numer slotu z drugim źródłowym buforem ramki

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

    • bity 0-15: X

    • bity 16-31: Y

  • słowo 2: wymiary prostokąta

    • bity 0-15: szerokość

    • bity 16-31: wysokość

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-3: typ polecenia (0x5)

    • bity 4-9: SLOT_DST — numer slotu z docelowym buforem ramki

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

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

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

    • bity 16-31: liczba kolumn do narysowania

  • słowo 1 (obecne tylko jeśli CMAP_A_EN lub TRANS_EN):

    • bity 0-5: SLOT_CMAP_A — numer slotu z mapą kolorów A

    • bity 6-19: CMAP_A_IDX — indeks mapy kolorów A w buforze

    • bity 20-25: SLOT_TRANSMAP — numer slotu z mapą przezroczystości

    • bity 26-31: TRANSMAP_IDX — indeks mapy przezroczystości w buforze

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:

    • bity 0-21: TEX_PTR — wskaźnik na kolumnę w teksturze

    • bity 24-29: SLOT_TEX — numer slotu z teksturą

  • 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):

    • bity 0-5: SLOT_CMAP_B — numer slotu z mapą kolorów B

    • bity 6-19: CMAP_B_IDX — indeks mapy kolorów B w buforze

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 = read_byte(SLOT_TEX, TEX_PTR + coord)
if CMAP_A_EN:
    color = read_byte(SLOT_CMAP_A, CMAP_A_IDX << 8 | color)
if CMAP_B_EN:
    color = read_byte(SLOT_CMAP_B, CMAP_B_IDX << 8 | color)
if TRANS_EN:
    color = read_byte(SLOT_TRANSMAP, TRANSMAP_IDX << 16 | 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-3: typ polecenia (0x6)

    • bity 4-9: SLOT_DST — numer slotu z docelowym buforem ramki

    • bity 16-31: liczba kolumn do narysowania

  • słowo 1:

    • 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 2:

    • bity 0-5: SLOT_CMAP_A — numer slotu z mapą kolorów

    • bity 6-19: CMAP_A_IDX — indeks mapy kolorów w buforze

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. 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, 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-3: typ polecenia (0x7)

    • bity 4-9: SLOT_DST — numer slotu z docelowym buforem ramki

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

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

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

    • bity 16-21: SLOT_SRC — numer slotu ze źródłowym buforem ramki bądź teksturą płaską

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

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

  • słowo 1 (obecne tylko jeśli CMAP_A_EN lub TRANS_EN):

    • bity 0-5: SLOT_CMAP_A — numer slotu z mapą kolorów A

    • bity 6-19: CMAP_A_IDX — indeks mapy kolorów A w buforze

    • bity 20-25: SLOT_TRANSMAP — numer slotu z mapą przezroczystości

    • bity 26-31: TRANSMAP_IDX — indeks mapy przezroczystości w buforze

  • słowo 2: 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ć.

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 (obecne tylko jeśli CMAP_B_EN):

    • bity 0-5: SLOT_CMAP_B — numer slotu z mapą kolorów B

    • bity 6-19: CMAP_B_IDX — indeks mapy kolorów B w buforze

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

# Obliczenie koloru dla piksela (x, y).
mask_u = (1 << ULOG << 16) - 1
mask_v = (1 << VLOG << 16) - 1
# Wysokie bity współrzędnych są stałe i brane z *START, niskie bity zmieniają się o *STEP.
ufract = (USTART & ~mask_u) | ((USTART + USTEP * (x - X0)) & mask_u)
vfract = (VSTART & ~mask_v) | ((VSTART + VSTEP * (x - X0)) & mask_v)
u = ufract >> 16
v = vfract >> 16
color = read_byte(SLOT_SRC, u + slot_pitch(SLOT_SRC) * v)
if CMAP_A_EN:
    color = read_byte(SLOT_CMAP_A, CMAP_A_IDX << 8 | color)
if CMAP_B_EN:
    color = read_byte(SLOT_CMAP_B, CMAP_B_IDX << 8 | color)
if TRANS_EN:
    color = read_byte(SLOT_TRANSMAP, TRANSMAP_IDX << 16 | orig_color << 8 | color)