programowanie asembler algorytmy

obsługa grafiki

Grafika pod VESA:

NOTKA: Opisuję tutaj dostęp do trybów graficznych SVGA. Zakładam, że potrafisz obsługiwać tryb 13h (320x200x256 kolorów).

Na początek może podam potrzebne struktury:

include '%fasminc%\macro\struct.inc'

; Struktura danych dla funkcji 4F00h  przerwania 10h
;( Pobranie informacji o VBE)

struct VbeInfoBlock
  .VbeSignature       db 'VESA'; Napis VESA
  .VbeVersion         dw 0200h ; Wersja
  .OemStringPtr       dd ?     ; Nazwa producenta
  .Capabilities       rb 4     ; Zarezerwowane
  .VideoModePtr       dd ?     ; Lista trybów
  .TotalMemory        dw ?     ; Liczba bloków po
                               ; 64 Kb każdy
                               ; dodane dla VBE 2.0
  .OemSoftwareRev     dw ?
  .OemVendorNamePtr   dd ?
  .OemProductNamePtr  dd ?
  .OemProductRevPtr   dd ?
  ._Reserved_         rb 222
  .OemData            rb 256
  ends

;Funkcja 4F01h
;( Pobranie informacji o trybie )

struct ModeInfoBlock
  ; Informacje dotyczące każdej wersji VBE
  .ModeAttributes      dw ? ; Atrybuty trybu;
                            ; pierwszy bit określa,
                            ; czy tryb dostępny.
   .WinAAttributes      db ?
   .WinBAttributes      db ?
   .WinGranularity      dw ?
   .WinSize             dw ?
   .WinASegment         dw ?
   .WinBSegment         dw ?
   .WinFuncPtr          dd ? ;Wskaźnik do funkcji
                             ;przełączającej okna.
   .BytesPerScanLine    dw ? ;Liczba bajtów w linii.
   ; Informacje dla wersji 1.2 lub nowszej
   .XResolution         dw ? ; Rozdzielczość pozioma
   .YResolution         dw ? ; Rozdzielczość pionowa
   .XCharSize           db ?
   .YCharSize           db ?
   .NumberOfPlanes      db ? ; Liczba płatów pamięci
   .BitsPerPixel        db ?
   .NumberOfBanks       db ?
   .MemoryModel         db ?
   .BankSize            db ?
   .NumberOfImagePages  db ?
   ._Reserved           db ?
   .RedMaskSize         db ? ;Liczba bitów przypadających
                             ;na składową czerwoną.
   .RedFieldPosition    db ? ;Pozycja składowej czerwonej
   .GreenMaskSize       db ?
   .GreenFieldPosition  db ?
   .BlueMaskSize        db ?
   .BlueFieldPosition   db ?
   .RsvdMaskSize        db ? ;Składowa dodatkowa.
   .RsvdFieldPosition   db ?
   .DirectColorModeInfo db ?
   ;Informacje dla wersji 2.0 lub nowszej
   .PhysBasePtr         dd ? ;Adres fizyczny pamięci VRAM
   .OffScreenMemOffset  dd ? ;Początek pamięci poza
                             ;obszarem widocznym na
                             ;ekranie
   .OffScreenMemSize    dw ? ;Rozmiar tego obszaru
   .__Reserved          rb 206
   ends

NOTKA: Wszystkie opisane funkcje dostępne są przez przerwanie 10h. Na początku pobieramy informacje o karcie graficznej:

Wejście:

Wyjście:

W pozycji VbeVersion jest zapisana wersja Vbe - starszy bajt to wersja główna, a młodszy bajt to wersja poboczna np: 0102h oznacza wersję 1.2.
Teraz musimy pobrać informacje o żądanym trybie graficznym:

Wejście:

Najmłodszy bit pozycji ModeAttributes określa czy tryb jest dostępny, czy nie. Jeżeli jest to możemy włączyć tryb graficzny:

Weście:

Jesteśmy już w trybie graficznym. Jeżeli korzystamy z trybu paletowego to należy najpierw ustawić paletę:

Wejście:

Format palety: RGBx (x to alpha lub wyrównanie). NOTKA: Funkcja ustawiania palety dostępna jest w VESA 2.0+.

Dostęp do pamięci ekranu jest możliwy na dwa sposoby: poprzez banki i poprzez adresowanie liniowe.

Dostęp przez banki: W tym trybie bufor ekranu podzielony jest na bloki po 64 kilobajty każdy (ze względu na ograniczenie rozmiaru segmentu). Chcąc narysować coś na ekranie trzeba przełączyć się na odpowiedni bank:

Wejście:

Bank znajduje się pod adresem A000h:0000h. To co wpiszemy do banku pojawi się w odpowiednim miejscu na ekranie (w zależności od wybranego banku).

Dostęp przez adresowanie liniowe: Ten sposób jest możliwy tylko w VESA 2.0+. Należy więc najpierw sprawdzić przy pobieraniu informacji o karcie wersję Vbe. Aby włączyć tryb graficzny z dostępem bezpośrednim (przez adresowanie liniowe) należy do numeru funkcji dodać 4000h (1 shl 15). Adres fizyczny bufora podany został w pozycji PhysBasePtr przy pobieraniu informacji o trybie graficznym. Aby przekształcić go na adres liniowy (tylko taki jest prawidłowy do bezpośredniego dostępu) należy (zakładam, że twój program jest klientem serwera DPMI) użyć przerwania 31h (przerwanie serwera DPMI):

Wejście:

jście:

Po zakończonej pracy z VESA należy zwolnić mapowanie adresu fizycznego przerwaniem 31h:

Wejście:

Jest jeszcze jedna sprawa - ekran może migać. Jest to spowodowane tym, że nasz program rysuje coś w buforze, gdy ekran jest odświeżany. Aby temu zapobiec stosuje się bufor pomocniczy ekranu (to na nim rysujemy) i kod:

        mov     dx,03dah
@@:
        in      al,dx
        test    al,8
        jz      @b
@@:
        in      al,dx
        test    al,8
        jnz     @b

Czeka on, aż nastąpi powrót pionowy. Zaraz po tym powinno nastąpić odrysowanie bufora pomocniczego na ekranie.

To chyba tyle. Teraz spróbujcie coś narysować.


Grafika pod DirectDraw:

Przygodę z DirectDraw musimy rozpocząć od znalezienia odpowiednich plików nagłówkowych. Mamy je od razu w FASM'ie, dla MASM/TASM są na stronie Iczeliona w Source Codes przy TASM D3DRDemo (czy coś takiego). Ja będę opisywał obsługę DDraw na podstawie przykładu z FASM'a (swoją drogą - jeśli ktoś ma opis API do DirectDraw to niech da mi znać).

API w DirectDraw opiera się na interfejsach. Nadrzędnym jest interfejs o typie DirectDraw - dajmy mu nazwę DDraw. To za pomocą tego interfejsu będziemy tworzyć pozostałe obiekty. Na początku musimy zainicjować strukturę DDraw. Możemy to zrobić na kilka sposobów. Jednym z nich jest korzystanie z bibliotek OLE, ale znacznie prościej jest skorzystać z funkcji biblioteki DDRAW.DLL o nazwie DirectDrawCreate. Wygląda to tak:

invoke        DirectDrawCreate,NULL,DDraw,NULL

'DDraw' to tutaj adres naszego interfejsu. Jak (prawie) zawsze, jeśli nastąpił błąd to funkcja zwróci zero i trzeba zakończyć program. To samo tyczy się pozostałych funkcji. Potem jeszcze jedna funkcja, można ją prztłumaczyć jako UstawPoziomWspółpracy:

comcall        DDraw,SetCooperativeLevel,\
        [hwnd],DDSCL_EXCLUSIVE+DDSCL_FULLSCREEN

[hwnd] to uczwyt okna, które poprzednio powinniśmy zrobić - bez okna aplikacja nie ruszy. Najlepiej, żeby okno było puste. Teraz trzeba ustawić tryb graficzny:

comcall        DDraw,SetDisplayMode,x,y,bpp

Oznaczenia: x,y - wymiary ekranu, bpp - liczba bitów na piksel. Później tworzymy tzw. powierzchnię:

mov        [ddsd.dwSize],sizeof.DDSURFACEDESC
mov        [ddsd.dwFlags],DDSD_CAPS+DDSD_BACKBUFFERCOUNT
mov        [ddsd.ddsCaps.dwCaps],DDSCAPS_PRIMARYSURFACE\
        +DDSCAPS_COMPLEX+DDSCAPS_FLIP
mov        [ddsd.dwBackBufferCount],1
comcall        DDraw,CreateSurface,ddsd,DDSPrimary,NULL

Notka: struktura ddsd jest typu DDSurfaceDesc - czyli opis powierzchni. Interesującą flagą jest DDSCAPS_PRIMARYSURFACE - nakazuje ona, by stworzyć powierzchnię ekranu - to przez nią będziemy rysować na monitorze. Funkcja ta inicjuje interfejs DDSPrimary typu DirectDrawSurface. Standardowym sposobem jest utworzenie buforu pomocniczego ekranu, przenoszenie obrazków na ekran, a potem przesunięcie buforu na ekran, ja jednak zastosuję metodę bez buforu pomocniczego. Stworzę tylko powierzchnię obrazka, a potem będę go odrysowywał na ekranie - jest to o wiele prostsza i krótsza metoda (jak dla mnie). A więc:

mov        [ddsd.dwSize],sizeof.DDSURFACEDESC
mov        [ddsd.dwFlags],DDSD_CAPS+DDSD_WIDTH+DDSD_HEIGHT
mov        [ddsd.ddsCaps.dwCaps],DDSCAPS_SYSTEMMEMORY
mov        [ddsd.dwWidth],800
mov        [ddsd.dwHeight],600
comcall        DDraw,CreateSurface,ddsd,DDSPicture,0

Mamy teraz nową powierzchnię, tak samo typu DirectDrawSurface o nazwie DDSPicture. Zamiast flagi DDSCAPS_SYSTEMMEMORY można użyć flagi DDSCAPS_VIDEOMEMORY. Spowoduje to, że obrazek będzie przechowywany w pamięci karty graficznej zamiast w paięci systemowej. Pamięci nowych kart graficznych są szybsze od pamięci sytemowych, ale na starszych komputerach (np. na moim), przetwarznie obrazka znajdującego się w pamięci kart graficznej jest duuużo wolniejsze. Jeżeli masz (dość) nowy komputer to możesz spróbować z DDSCAPS_VIDEOMEMORY. Teraz przydałoby się schować kursor funkcją ShowCursor (jako flagę widoczności kursora wstawić FALSE).

Teraz będzie rzecz bardzo ważna: obsługa wyświetlania grafiki z jednoczesną obsługą okna. W przykładzie z FASM'a, obsługa okna i grafiki była razem w pętli okna (odbieranie komunikatów). Polegało to na sprawdzeniu, czy jest komunikat, przetworzenia go jeśli był, jeśli nie było to narysowaia kolejnej klatki animacji, a potem znowu sprawdzanie komunikatów. Ja rozwiązałem problem jednoczesnej obsługi okna i grafiki poprzez wątki. Otóż po stworzeniu okna i zainicjowaniu trybu graficznego, a przed wejściem w pętlę okna stworzyłem wątek, w którym na okrągło odmalowywana jest animacja. Wątek zakańczał się sam, gdy użytkownik nacisnął odpowiedni klawisz - gdyby wątek nie zakończył się mogłoby dojść do problemów.

Tworzenie animacji oparłem na bezpośrednim dotępie do pamięci obrazka. Realizuje się go poprzez wywołanie funkcji o postaci:

comcall        DDSPicture,LockSurface,0,ddsd,0,0
Wskaźnik do obrazka jest w [ddsd.lpSurface]. Po przetworzeniu obrazka należy wywołać funkcję:
comcall        DDSPicture,UnlockSurface,0

Odrysowywanie zawartości obrazka na ekran w naszym przykładzie odbywa się za pomocą funkcji:

comcall        DDSPrimary,BltFast,0,0,[DDSPicture],rect,0
W strukturze rect znajdują się wymiary części powierzchni DDSPicture (obrazka) do odmalowania - w naszym przykładzie cały ekran, czyli 0,0,800,600.

Ustawianie własnej palety (w trybach 256-kolorowych) odbywa się poprzez funkcje:

comcall        DDraw,CreatePalette,DDPCAPS_8BIT\
        +DDPCAPS_ALLOW256,paleta,DDPalette,0
comcall        DDSPrimary,SetPalette,[DDPalette]
Tutaj paleta to adres palety. Format jest nasepujący: BGRx - x musi być 0.

Na koniec musimy przywrócić stan sprzed uruchomienia naszego programu. Służą do tego funkcje:

comcall        DDraw,RestoreDisplayMode
comcall        DDraw,Release

Pierwsza przywraca poprzednią rozdzielczość, a druga zwalnia pamięć przydieloną interfejsowi DDraw. To by było na tyle :) Powinieneś umieć teraz zrobić jakąś prostą aplikację graficzną.