Magazyn komputerowy PTiKuś
www.ptik.ivg.pl
#3 - Grudzień

Grafika w trybie 13h

1. Wprowadzenie
Artykuł ten opisuje podstawy tworzenia grafiki w systemie DOS przy użyciu trybu 13h. Bo co z tego, że "początkujący programista" ma dostępne spacjalne biblioteki i komponenty (jak OpenGL czy DirectX), skoro nie potrafi narysować nawet piksela i wogóle nie orientuje się na czym polega wyświetlanie grafiki.
Ponadto nie wszyscy potrafią programować w systemie Windows.
Fragmenty kodu zawarte w artykule i dołączone programy napisane są w C.

2. Podstawowe informacje na temat trybu 13h
W trybie 13h wymiary wynoszą 320 (szerokość ekranu) na 200 (wysokość). Należy pamiętać, że podajemy je w zakresie od 0 do 319 dla osi x i od 0 do 199 dla osi y (podobnie jak w tablicach). Początek układu zapisujemy jako (0;0) i znajduje się w lewym-górnym rogu.

Struktura ekranu w trybie 13h

Można wyświetlić maksymalnie 256 kolorów, przy czym każdy kolor przedstawiony jest jako 8 bitów (28=256) lub 1 bajt.
Zatem pamięć ekranu zajmuje 320 (szerokość ekranu) * 200 (wysokość ekranu) * 1 (kolor) = 64000 bajtów w pamięci.

3. Ustawienie trybu graficznego
Do ustawienia trybu graficznego należy skorzystać z przerwania BIOSu 10h (funkcje graficzne) dla wartości 0 w rejestrze AH i żądanego numeru trybu w rejestrze AL (w naszym przypadku jest to wartość 13h lub też 0x13).
Przykładowa funkcja ustawiająca tryb 13h wygląda zatem następująco:

void ustaw_tryb_13h (void)
{
     union REGS regs;

     regs.h.ah=0x00; // funkcja 00h - ustaw tryb
     regs.h.al=0x13; // tryb 13h - 320x200x256
     int86(0x10,&regs,&regs); // wykonaj polecenie
}


Natomiast wyjście z trybu 13h wykonujemy analogicznie, tzn. ustawiamy tą samą funkcję i przerwanie, tylko że zamiast trybu 13h (320x200x256) ustawiamy 03h (tryb tekstowy).

void wylacz_tryb_13h (void)
{
     union REGS regs;

     regs.h.ah=0x00; // funkcja 00h - ustaw tryb
     regs.h.al=0x03; // tryb tekstowy
     int86(0x10,&regs,&regs); // wykonaj polecenie
}

4. Wirtualny ekran
Jeśli chodzi o tryb 13h, to znane mi są dwie techniki rysowania.
Pierwsza charakteryzuje się tym, że rysowany jest piksel po pikselu bezpośrednio na ekranie.
Druga natomiast polega na umieszczaniu pikseli w tzw. buforze (wirtualnym ekranie) a następnie przerzuczeniu całego bufora do pamięci ekranu.
Osobiście wolę tą drugą wersję, ponieważ najpierw mogę stworzyć cały rysunek, a dopiero wyświelić go na ekranie. Ponadto jest to chyba najczęściej stosowany sposób przez programistów tworzących gry.
Przede wszystkim należy zadeklarować kilka zmiennych (globalnych), które pozwolą nam operować pamięcią bufora ("wirtualnego ekranu") oraz pamięcią ekranu.

unsigned char far *ekran; // wskaźnik do pamięci karty grafiki (VGA)
unsigned char far *bufor_ekranu; // wskaźnik do bufora ekranu
unsigned int rozmiar_ekranu; // 320*200=64000 int szerokosc_ekranu, wysokosc_ekranu; // parametry ekranu


Użyłem wskaźników do pamięci bufora ekranu, ponieważ jest to najszybszy sposób dostępu, a w grach najbardziej potrzebne jest jak najszybsze wykonywanie instrukcji.
Teraz wystarczy zarezerwować pamięć na zmienną bufor_ekranu, ustawić wskaźnik ekran na adres pamięci karty grafiki oraz nadać wartości pozostałym zmiennym. Wszystkie instrukcje zawarłem w funkcji ustaw_grafikę ( ) ponieważ dodatkowo inicjalizuje ona grafikę.

int ustaw_grafike (void)
{
     bufor_ekranu=farmalloc (64000u); // zarezerwowanie pamięci na bufor ekranu
         if (bufor_ekranu) // jeśli wszystko OK
             {
             ekran=MK_FP (0xa000, 0); // ekran = adres pamięci karty
             szerokosc_ekranu=320;
             wysokosc_ekranu=200;
             rozmiar_ekranu=64000u; // 320x200=64000
             ustaw_tryb_13h ( ); // uruchom tryb 13h
             _fmemset(bufor_ekranu, 0, rozmiar_ekranu); // inicjalizacja bufora_ekranu
             return 0;
             }
         else // jeśli brak pamięci
             {
             wylacz_tryb_13h ( ); // wyłącz tryb graficzny i przejdź do tekstowego
             printf("Błąd: brak pamięci!\n");
             return -1;
             }
}

5. Rysowanie
Rysowanie piksela w pamięci bufora polega po prostu na podaniu koloru danego punktu w odpowiednim miejscu w pamięci określonym wzorem:

adres_pierwszego_elementu_bufora_ekranu + y (współrzędna y punktu) * szerokość_ekranu (320) + x (wsp. x punktu)

Zatem definicja funkcji rysującej punkt o współrzędnych x i y w kolorze kolor jest następująca:

void rysuj_piksel (int x, int y, int kolor)
{
    *(bufor_ekranu + y * szerokosc_ekranu + x)=kolor;
}


Wywołanie funkcji rysuj_piksel (10,10,10) nie wpłynie jeszcze na zmianę ekranu. Dzieje się tak dlatego, że wszystko rysujemy pamięci bufora ekranu (nie mylić z pamięcią ekranu!). Żeby wyświetlić nasz "wirtualny ekran" należy dodatkowo wywołać funkcję, która przerzuca zawartość bufora ekranu do pamięci ekranu.

void rysuj_bufor (void)
{
     // Czekaj na odświerzanie ekranu
     while (inportb(INPUT_STATUS_0) & 8 );
     while (!(inportb(INPUT_STATUS_0) & 8) );
     // kopiowanie bufora do pamięci karty grafiki
     _fmemcpy (ekran, bufor_ekranu, rozmiar_ekranu);
}


Nie będę opisywał tej funkcji. Dodam tylko, że korzysta ona z definicji #define INPUT_STATUS_0 0x3da, która pozwala narysować zawartość ekranu w czasie odświeżania. Metodę stosuje się po to, by uniknąć efektu "migotania" obrazu.

6. Przykład
Poniżej przedstawiony jest przykładowy program wykorzystujący w/w funkcje.
W miejscach oznaczonych komentarzem /* ciało funkcji */ należy wstawić instrukcje zapisane w powyższych funkcjach.

/****************************************/
/* Przykładowy program dołączony do artykułu */
/*                   "Grafika w trybie 13h"                  */
/****************************************/

#include <conio.h>
#include <dos.h>
#include <malloc.h>
#include <stdio.h>
#include <string.h>

#define INPUT_STATUS_0 0x3da

unsigned char far *ekran; // wskaźnik do pamięci karty grafiki (VGA)
unsigned char far *bufor_ekranu; // wskaźnik do bufora ekranu
unsigned int rozmiar_ekranu; // 320*200=64000 int szerokosc_ekranu, wysokosc_ekranu; // parametry ekranu

/* deklaracja funkcji */
int ustaw_grafike (void);
void ustaw_tryb_13h (void);
void wylacz_tryb_13h (void);
void rysuj_piksel (int x, int y, int kolor);
void rysuj_bufor (void);

/* definicja funkcji */
int ustaw_grafike (void)
{ /* ciało funkcji */ }

void ustaw_tryb_13h (void)
{ /* ciało funkcji */ }

void wylacz_tryb_13h (void)
{ /* ciało funkcji */ }

void rysuj_piksel (int x, int y, int kolor)
{ /* ciało funkcji */ }

void rysuj_bufor (void)
{ /* ciało funkcji */ }

void main (void)
{
     char kl;
     int i,j;

     ustaw_grafike ( ); // ustaw tryb 13h i inicjalizuj zmienne globalne
         for (i=0; i<255; i++)
             for (j=0; j<200; j++)
                 {
                 rysuj_piksel (i, j, i); // rysuj (w bufor_ekranu) punkt o współrzędnych x = i oraz y = j w kolorze kolor = i
                 }
     rysuj_bufor ( ); // rysuj wszystko na ekranie
     kl=getch ( ); // czekaj na klawisz
     wylacz_tryb_13h ( ); // wyjdź z trybu 13h i włącz tryb tekstowy
}


Oto efekt końcowy (dostępna paleta kolorów):



GREG
warsztat@poczta.fm
http://www.warsztat.px.pl

Copyright (C) 2000 by PTiK
Wszelkie prawa zastrzeżone.
Kopiowanie tekstów i grafiki bez zgody autora zabronione !