Apetyt rośnie w miarę jedzenia (a chyba szczególnie u programistów:). Skoro więc czujesz niedosyt po przyswojeniu trybu 13h - czas na VESĘ. Jest to pewien standard w dziedzinie obsługi karty graficznej i praktycznie każda nowa (teraźniejsza) karta ma zaimplementowaną jego obsługę. Domyślasz się zapewne, że Vesa umożliwia otrzymywanie wysokich rozdzielczości. Zgadza się. Od wersji 1.2 oferuje nam ona nawet tryb 1280x1024 przy 16 milionach kolorów, a to już jest coś. Do obsługi tego standardu służy przerwanie o numerze 10h, którego funkcje za chwilę poznamy. Zdecydowanie najłatwiejszą rzeczą jaką możesz zrobić z Vesą to zainicjować tryb graficzny, który Cię interesuje. Służy do tego funkcja 4F02h. Aby ją wywołać do rejestru AX podajesz właśnie 4F02h, natomiast do BX numer trybu. Możesz wybierać spośród:

00h-FFh |standardowe tryby (również tekstowe)
100h |640x400 256 kolorów
101h |640x480 265 kolorów
102h |800x600 16 kolorów
103h |800x600 256 kolorów
104h |1024x768 16 kolorów
105h |1024x768 256 kolorów
106h |1280x1024 16 kolorów
107h |1280z1024 256 kolorów
108h |tryb tekstowy 80x60
109h |tryb tekstowy 132x25
10Ah |tryb tekstowy 132x43
10Bh |tryb tekstowy 132x50
10Ch |tryb tekstowy 132x60
-----------VBE v1.2-----------
10Dh |320x200 32K kolorów
10Eh |320x200 64K kolorów
10Fh |320x200 16M kolorów
110h |640x480 32K kolorów
111h |640x480 64K kolorów
112h |640x480 16M kolorów
113h |800x600 32K kolorów
114h |800x600 64K kolorów
115h |800x600 16M kolorów
116h |1024x768 32K kolorów
117h |1024x768 63K kolorów
118h |1024x768 16M kolorów
119h |1280x1024 32K kolorów
11Ah |1280x1024 64K kolorów
11Bh |1280x1024 16M kolorów

Jak już mówiłem inicjacja jest najprostsza gdyż wystarczy np. taka procedura:

procedure init (tryb:word); assembler;
asm
mov ax, 4F02h
mov bx, tryb
int 10h
end;

I nawet nie wygląda to groźnie. Jednak inne operacje będą już bardziej skomplikowane. Wypływa to z faktu, iż komputer na informacje o tym, jak wygląda ekran i co się na nim znajduje rezerwuje w swojej pamięci zaledwie 64kB (słynny segment A000h). Jest to mało, albowiem wystarczy sobie policzyć ile pamięci potrzebuje ekran np. w trybie 640x480x256. W tym przypadku jeden piksel na ekranie zajmuje w pamięci 1 bajt. Pikseli jest 640x480=307200, a więc brakuje nam dokładnie 236kB wolnej pamięci. Nie wesoło. Na szczęście z pomocą przychodzi nam Vesa, która wykorzystuje pamięć zamontowaną na karcie graficznej. Oczywiście komputer jest uparty i za nic nie będzie chciał czytać danych bezpośrednio z pamięci karty, lecz ze swojej. Dlatego segment A000h posłuży nam jako okienko (dziurka od klucza), przez które będziemy podglądać pamięć karty graficznej. Jako, że nasze okienko ma rozmiar 64kB podglądana przez nas pamięć jest podzielona na tzw. banki (strony). W skutek pewnej standaryzacji w większości przypadków kart graficznych banki te również są tej wielkości (co wydaje się nawet logiczne). Schematycznie pamięć karty graficznej wygląda wtedy tak:

Bank 0 - 64kB
---------------
Bank 1 - 64kB
---------------
...

Bank n - 64kB


w sumie pamięć karty
= (n+1) * 64kB




W jednej chwili możemy podpatrywać tylko jeden, określony przez nas, bank. Na początku zajmiemy się trybami 256-kolorowymi, a dla przykładu posłużę się trybem o numerze 101h. Tryby wysoko-kolorowe omówimy później ponieważ żądzą się one nieco innymi zasadami (inaczej byłoby to wszystko zbyt piękne i proste:). Podstawowy problem przy zapalaniu punktu na ekranie to wyliczenie banku i pozycje w tym banku jaką należy zmodyfikować, aby zaświecić konkretny pixel [x,y]. Jak już nieraz wspomniałem, jeden piksel przy 256 kolorach zajmuje w pamięci jeden bajt, a więc możemy się posłużyć znanym już nam wzorem na ofset jaki trzeba zmodyfikować: ofset=y*640+x (640 bo taka jest szerokość ekranu w omawianym dla przykładu trybie 101h). Wzór ten działa i można go stosować jednak musimy jeszcze jakoś wyliczyć numer banku. Jest to chyba najtrudniejsza czynność, już wyjaśniam. Wyobraź sobie, że numer ofsetu jest wyświetlany na takim liczniku jak mamy np. w samochodach czy wodomierzach (z takimi obracającymi się cyferkami). W samochodzie licznik przekręciłby się po przejechaniu dajmy na to 100 000 kilometrów, a nasz licznik przekręca się po dojściu do wartości 65535, no bo tyle bajtów mieści jeden bank. Teraz za pomocą naszego wzoru obliczamy ofset dla kolejnych punktów na ekranie, a wynik na bieżąco wskazuje licznik. Dla lewego górnego rogu (punkt [0.0]) licznik wskaże 0. W miarę jak zaczniemy przesuwać się po ekranie w prawo wynik będzie rósł. Jeśli dojdziemy do prawego brzegu ekranu to przechodzimy na początek następnego wiersza, a licznik nadal rośnie. Oczywiste jest, że w pewnym momencie licznik osiągnie wartość graniczną i przekręci się. Dla nas oznacza to, że doszliśmy już do końca banku i aby wyświetlać punkty dalej musimy się przełączyć na bank następny. W tym momencie ofset będzie równy 0, ale zmieniając właśnie pierwszy bajt (które liczymy jak od zera) następnego banku nie powrócimy do punktu [0.0] ale będziemy kontynuować kolorowanie ekranu kolejnymi pikselami. Możesz już otworzy oczy :)). Być może wygląda to niepozornie ale kryje się tutaj świetna metoda, która jest skuteczna i wykorzystywana. Mając współrzędne punktu wystarczy policzyć ze wzoru ofset i sprawdzić ile razy przekręcił się przy tym nasz licznik - jest to nasz numer banku. Całą tą operację łatwo się przeprowadza na rejestrze AX, który ma wielkość właśnie równą jednemu bankowi, dzięki czemu może pełnić rolę licznika. Gotowa procedura putpixel może zatem wyglądać tak:

procedure putpixel (x,y:word;color:byte); assembler;
asm
mov ax, 640
mov bx, y
mul bx
add ax, x
{jak do tej pory wyliczyliśmy ofset}
{AX=640*y+x}
adc dx, 0
{teraz wiemy ile razy przekręcił się licznik, a}
{wynik został umieszczony w DX}
push ax
{wrzucamy nasz ofset na stos, później się przyda}
mov ax, 4F05h
mov bx, 0
int 10h
{właśnie ustawiliśmy odpowiedni bank za pomocą przerwań}
mov ax, 0A000h
mov es, ax
mov ah, color
pop bx {pobranie ofsetu ze stosu}
mov es:[bx], ah
{no i znane już stawianie piksela}
end;

Na początku za pomocą wszechmogącego wzoru obliczyliśmy ofset. Następnie za pomocą instrukcji adc sprawdziliśmy który bank należy ustawić. Instrukcja ta sprawdza czy podczas wykonywania poprzedniej instrukcji nie wystąpiło przepełnienie (u nas mógł się przepełnić, czyli przekręcić, rejestr AX) i zwiększa argument (u nas był nim DX) o tyle ile razy to przepełnienie miało miejsce. Jako wartość początkową DX przyjmuje zero, co jest chyba zrozumiałe (numer pierwszego banku). Kiedy znamy już numer banku i ofset jaki trzeba w nim zmodyfikować, musimy przełączyć się (czyli ustawić naszą dziurkę od klucza) na odpowiedni bank. Służy do tego funkcja 4F05h przerwania 10h. W rejestrze BX podajemy na wejściu 0, co oznacza, że chcemy przełączyć bank, a nie sprawdzić jaki jest aktualnie ustawiony, natomiast w rejestrze DX podajemy numer banku jaki chcemy włączyć. Teraz już wiesz dla czego przy wywoływaniu instrukcji adc jako argument podałem właśnie DX. Po tych wszystkich zabiegach przystępujemy do standardowej operacji modyfikowania odpowiedniej komórki pamięci, podobnie jak przy trybie 13h. Aha używałem stosu do tymczasowego zapamiętania ofsetu, ponieważ rejestr AX, który go przechowywał, był wykorzystywany w dalszej części procedury.

Mając putpixela reszta już jest banalna. Linię można rysować identycznie jak w przypadku 13h, a również inne procedury (np. kwadraty czy okręgi) nie powinny stanowić większego problemu, chociaż okrąg jest dosyć ciekawy... Ale to już inna historia.