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.





