13H jeszcze raz i jeszcze szybciej
-- Sebastian Pawlak, 1995.
Wielokrotnie w dziale "Dla praktyków" pojawiały się
publikacje, które dotyczyły programowania grafiki. Moja
praca jest koleją próbą wsparcia bibliotek BGI o nowe
szybsze procedury operacji na obrazie. Postanowiłem zająć
się dwiema funkcjami, które szczególnie powolne nie mogą
być używane w bardziej profesjonalnych programach. Chodzi
tu o GetImage (zapisanie do bufora wycinka obrazu) i
PutImage (wyświetlenie obrazu z bufora na ekranie).
Główną częścią takich procedur jest obsługa kopiowania
danych z jednego miejsca pamięci do drugiego. Do wykonania
tej operacji stosuje się assemblerową komendę MOVSB, która
ze źródła (podanego w rejestrach DS:SI) kopiuje jeden bajt
do miejsca docelowego (ES:DI). W moich procedurach
GetBitMap i PutBitMap (odpowiednio GetImage i PutImage)
do operacji powielania używam szybszego rozkazu MOVSW,
który w tym samym czasie przenosi nie bajt, a słowo
(2 bajty). Jedyną wadą 2-bajtowego operowania na danych jest
to, że za jego pośrednictwem można kopiować obrazy tylko o
parzystej szerokości. Problem ten rozwiązałem poprzez użycie
MOVSW do powielenia jak największej ilości słów (2 bajtów)
zawartych w linii obrazu, a jeśli ma on szerokość
nieparzystą to ostatni bajt kopiuje komendą MOVSB.
Dzięki temu można bardzo szybko pobrać lub wyświetlić obraz
o dowolnych rozmiarach maksymalnie wykorzystując szybkość
rozkazu MOVSW.
Obie napisane przeze mnie procedury w taki sam sposób
jak GetImage, PutImage zapisują i odczytują dane do (z)
bufora. Pierwsze dwa bajty bufora to szerokość, a dwa
kolejne zawierają wysokość obiektu, reszta to bitmapowe
informacje o samym obrazie.
Aby wywołać procedury należy zastosować się do wskazówek
podanych przed każdą z funkcji (jako komentarz). Do
buforowania może posłużyć zmienna o typie Pointer, wtedy
jako Seg_buf podajemy Seg (Bufor^), a Ofs_buf -
Ofs (Bufor^).
Więcej o technice działania procedur można dowiedzieć się
śledząc poniższy program, jest on możliwie przejrzyście
napisany.
Oto gotowe procedury GetBitMap i PutBitMap.
{Pobieranie wycinka obrazu} {X1, Y1 - lewy górny róg } {X2, Y2 - prawy dolny róg } {Seg_buf, Ofs_buf - segment i offset bufora} Procedure GetBitMap (X1, Y1, X2, Y2, Seg_buf, Ofs_buf:word); Assembler; Asm push ds mov bx,Y1 {Wyliczenie offsetu lewego górnego rogu.} mov cl,6 shl bx,cl mov ax,bx shl bx,1 shl bx,1 add ax,bx add ax,X1 mov si,ax {ds:si = pamięć obrazu.} mov ax,0a000h mov ds,ax mov ax,Seg_buf {es:di = pamięć bufora.} mov es,ax mov di,Ofs_buf mov bx,X2 {Wyliczenie szerokości obiektu.} sub bx,X1 mov es:[di],bx {Zapis szerokości do 1-2 bajtu bufora.} inc di inc di inc bx mov ax,bx {Dzieli szerokość przez dwa dla } mov dl,2 {2-bajtowego kopiowania do bufora.} div dl cmp ah,0 je @Bez_reszty mov ah,1 @Bez_reszty: mov cx,Y2 {Wyliczenie wysokości obiektu.} sub cx,Y1 mov es:[di],cx {Zapis wysokości do 3-4 bajtu bufora.} inc di inc di inc cx @Next_line: push cx mov ch,0 mov cl,al rep movsw {Kopiowanie danych 2-bajtowo do bufora.} mov cl,ah rep movsb {Kopiow. bajtu dla szerok. nieparzystej.} sub si,bx {Ustawienie offsetu na początku} add si,320 {nastepnego wiersza.} pop cx loop @Next_line {Pobiera następną linię.} pop ds End; {Wyświetlanie wycinka obrazu} {X1, Y1 - lewy górny róg } {Seg_buf, Ofs_buf - segment i offset bufora} procedure PutBitMap (X1, Y1, Seg_buf, Ofs_buf:word); Assembler; Asm push ds mov bx,Y1 {Wyliczenie offsetu lewego górnego rogu.} mov cl,6 shl bx,cl mov ax,bx shl bx,1 shl bx,1 add ax,bx add ax,X1 mov di,ax {es:di = pamięć obrazu.} mov ax,0a000h mov es,ax mov ax,Seg_buf {ds:si = pamięć bufora.} mov ds,ax mov si,Ofs_buf mov bx,ds:[si] {Odczyt szerokości obiektu.} inc si inc si inc bx mov ax,bx {Dzieli szerokość przez dwa dla } mov dl,2 {2-bajtowego kopiowania na ekran.} div dl cmp ah,0 je @Bez_reszty mov ah,1 @Bez_reszty: mov cx,ds:[si] {Odczyt wysokości obiektu.} inc si inc si inc cx @next_line: push cx mov ch,0 mov cl,al rep movsw {Kopiowanie danych 2-bajtowo.} mov cl,ah rep movsb {Kopiow. bajtu dla szerok. nieparzystej.} sub di,bx {Ustawienie offsetu na początku} add di,320 {następnego wiersza.} pop cx loop @next_line {Wyświetla następną linię.} pop ds End;
Poniżej przedstawiam wyniki testu porównującego prędkości działania obydwu zestawów procedur. Test przeprowadziłem na komputerze 386 DX 40MHz Czas 1000 krotnego pobierania i wyświetlania kwadratu o wymiarach 100x100 funkcjami: GetImage, PutImage z dwoma driverami BGI: VGA256 2.00 Borland International: 18.5700 sek. SVGA256 2.31 Jordan Hargraphix: 65.2500 sek. GetBitMap, PutBitMap z dwoma driverami BGI: VGA256 2.00 Borland International: 9.1200 sek. SVGA256 2.31 Jordan Hargraphix: 9.1700 sek.
Jeśli ktoś chce sam się przekonać o wydajności procedur to może uruchomić ten program:
{Program testuje prędkość działania procedur. } {Należy posiadać driver BGI VGA256 firmy } {Borland, SVGA256 - Jordan Hargraphix lub inny. } {Działanie programu testującego nie jest poprawne } {na przełomie dni !!! } Program Test; Uses Dos,Graph; Var H1, M1, S1, Ss1 :word; H2, M2, S2, Ss2 :word; Time1, Time2, Licz :longint; Driver, Mode :integer; Bufor :pointer; {UWAGA: W tym miejscu należy umieścić listingi procedur} { GetBitMap i PutBitMap. } Begin {Zainstalowanie sterownika BGI dla trybu 13H} Driver:=InstallUserDriver ('VGA256', nil); {Borland..} { Driver:=InstallUserDriver ('SVGA256', nil); } {Jordan..} If Driver=GrError then Halt (1); Mode:=0; InitGraph (Driver, Mode, ''); GetMem (Bufor, 100*100); GetTime (H1, M1, S1, Ss1); {Pierwszy pobór czasu.} For Licz:=0 To 999 Do Begin GetBitMap (0,0,99,99,Seg (Bufor^),Ofs (Bufor^)); {Nowe.} PutBitMap (0,0,Seg (Bufor^),Ofs (Bufor^)); { GetImage (0,0,99,99,Bufor^); } {Procedury BGI.} { PutImage (0,0,Bufor^,NormalPut); } End; GetTime (H2, M2, S2, Ss2); {Drugi pobór czasu.} FreeMem (Bufor, 100*100); CloseGraph; {Przeliczenie czasów na setne sekundy.} Time1:=(Ss1)+(S1*100)+(M1*100*60)+(H1*100*60*60); Time2:=(Ss2)+(S2*100)+(M2*100*60)+(H2*100*60*60); Writeln ('Czas trwania: ', (Time2-Time1)/100:0:4, ' sek.'); End.
Istnieje możliwość aby przedstawione procedury GetBitMap i PutBitMap były jeszcze szybsze jeśli zastosuje się rozkaz MOVSD, który kopiuje 2 słowa (4 bajty). Rozwiązanie to pozostawiam jednak do indywidualnych eksperymentów.
Literatura: 1. "Turbo Assembler Biblia użytkownika" - Gary Syck, 2. "Programowanie kart graficznych" - Krzysztof Łabanowski, 3. "Procedury graficzne dla kart EGA, VGA i SVGA" - Jarosław Skolimowski.