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,®s,®s); // 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,®s,®s); // 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
|