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.
w3cw3c
automatyka przemysłowa