int21h

Kouzla s textovými módy VGA

základy
adresace
rozšíření barev
nastavování textmódů
změna fontů
textmódy ve Freepascalu


Základy


Většina uživatelů i programátorů se zajímá spíše o grafické režimy, ale dodnes má smysl vědět něco o režimech textových. Předpokládám, že čtenáři vědí, co to textový režim je. (Typickým příkladem je vývojové prostředí Turbo pascalu.)
Přesto si to shrneme:
Obrazovka je rozdělená do relativně malého počtu políček (typicky 80 x 25 (fyzické (technické) rozlišení odpovídá 720x400). V každém políčku je právě jeden znak - obvykle písmeno. Možných znaků je 255 (tedy nejen písmena) tato množina se nazývá ASCII tabulka.
Kromě toho, že pro každé políčko je definován nějaký ASCII znak (může to být i znak "mezera" - prázdné místo), je definována i barva tohoto znaku.
Barva je hodnota 0-255 (tedy typ BYTE, (8 bitů)) a je takto zakódovaná
X BBB FFFF
7 654 3210

F znamená barva popředí. Jsou použity 4 bity, takže to může být hodnota 0-15.
Tabulka barev:
0 - černá
1 - tmavomodrá
2 - zelená
3 - modrozelená
4 - červená
5 - fialová
6 - hnědá
7 - šedivá
8 - tmavošedá (zesvětlená 0)
9 - ocelově modrá (zesvětlená 1)
10 - světle zelená (zesvětlená 2)
11 - světlounce modrá (zesvětlená 3)
12 - světle červená (zesvětlená 4)
13 - světle fialová (zesvětlená 5)
14 - žlutá (zesvětlená 6)
15 - jasně bílá (zesvětlená 7)

B znamená barva pozadí. Jsou použity 3 bity, tedy 0-7.
X znamená blikání. Pokud je tento bit nastaven na 1, tak znak bliká. Ovšem malým trikem lze toto chování změnit a potom tento bit neznamená blikání, ale přidává se k bitům určujícím barvu pozadí, takže se paleta možných barev pro pozadí rozšíří na 0-15. Za chvíli se podíváme, jak se to dělá.
Adresace
Videopaměť je přístupná od této adresy: B800h : 0
Pokud tedy chceme zapsat do levého horního rohu velké písmeno A, napíšeme toto:
Mem[$b800:0]:=byte('A');
nebo prostě
Mem[$b800:0]:=65;
A takhle tomu Áčku lze zadat barvu.
Mem[$b800:0+1]:=2 * 16 + 4;
Technický dotaz: Co je to vlastně za barvu?
???
Je to červený znak na zeleném pozadí.

Takhle bude vypadat procedura na vypsání znaku kdekoliv na obrazovce.
Procedure NapisZnak(pismeno:char;x,y:byte;popredi,pozadi:byte;blikat:boolean);
const SIRKA_OBRAZOVKY = 80;
       VYSKA_OBRAZOVKY = 25;
var barva,adresa:byte;
begin
barva:=pozadi*16+popredi;
if BLIKAT then barva:=barva+128;
y:=y-1; { Je sice zvykem, zadavat radky od 1, ale pocitac je cisluje od 0 }
x:=x-1; { To same se sloupci }
adresa:=(y*SIRKA_OBRAZOVKY+x)*2; { Nasobime 2, protoze 1 bunka=2bajty (znak,barva) }
Mem[$b00:adresa]:=byte(pismeno);
Mem[$b00:adresa+1]:=barva;
end;  

Co se ovšem stane, jestli budete zapisovat na 26. řádek? Jde to?
Jde to a je to základ tzv. stránkování. 26. řádek se totiž považuje za 1. řádek 1. stránky videopaměti. Stránky videopaměti se ovšem číslují od nuly, takže:
řádky 1-25 = ř. 1-25 0. stránky videopaměti
řádky 26-49 = ř. 1-25 1. stránky videopaměti
řádky 50-75 = ř. 1-25 2. stránky videopaměti

A tak dále. Specifikace tvrdí, že BIOS podporuje osm stránek videopaměti (tzn. 0-7), ale já si myslím, že pokud se nastaví režim pomocí volání VESA (mov ax,4f02h;mov bx,3;int 10h), tak že je stránek přístupných více.
Ještě jsme si nepověděli, jak se jednotlivé stránky přepínají:
Procedure PrepniStranku(b:byte);
{ potrebuje unit Dos (anebo prepis do assembleru) }
var r:registers;
begin
r.ah:=5;
r.al:=b;
Intr(0,r);
end;  

POZOR! Je důležité pohlídat, aby byla při ukončení programu zobrazována 0. stránka.

Rozšíření barev


A teď slíbený trik s rozšířením palety barev pozadí.
Zavolejte ve vašem programu jednu z těchto procedur:
Procedure ViceBarevPozadi1(b:boolean);
{ potrebuje unit Dos (anebo prepis do assembleru) }
var r:registers;
begin
r.ax:=003;
r.bl:=byte(not b);
Intr(0,r);
end;  

nebo
Procedure ViceBarevPozadi2(b:boolean);
var a:byte;
begin
a:=port[da];
port[c0]:=0;
a:=port[c1];
a:=a and ;
if B=false then a:=a or 8;
Port[c0]:=a;
end;  

Nastavení textových režimů


Pascal poskytuje v jednotce Crt funkci SetTextMode, jenže ta umí tak maximálně přepínat mezi 80x25 a 40x25, což je trochu málo. Použijeme raději BIOS nebo VESA BIOS. Tedy:
BIOS: mov ax,rezim;int 10h; nebo VESA BIOS: mov ax,4f02h;mov bx,rezim;int 10h;
Pokud nemáte extra důvod proč ne, tak raději používejte VESA BIOS. Tabulka režimů:
1 40x25 16 barev
3 80x25 16 barev
108h 80x25 16 barev jen VESA
109h 132x25 16 barev jen VESA
10Ah 132x43 16 barev jen VESA
10Bh 132x50 16 barev jen VESA
10Ch 132x60 16 barev jen VESA
Vyvolání těchto funkcí automaticky přepne zobrazovanou stránku na nultou. Pokud k režimům menších než 100h přičtete 80h nebo k režimům nad 100h 8000h, tak se nevymaže obsah obrazovky.
S rozlišením obrazovky se dají dělat psí kusy a existuje spousta X-módů. My se podíváme na jednu standardní záležitost - na mód 80x50. Používá ho třeba IDE Turbo pascalu. Jde o to, že se obrazová políčka rozdělí na dvě - ačkoli fyzické rozlišení obrazovky zůstává stejné (720x400). Přitom se použije jiná tabulka znaků. Ne znaky 8x16, ale 8x8.
1) Přepneme se do režimu 80x25
2) Zavoláme toto: mov ah,$11;mov al,12h;int 10h;
Do registru AL se dá dosadit jedna z těchto hodnot:
14h 25 řádků znaky 8x16
11h 28 řádků znaky 8x14
12h 50 řádků znaky 8x8


Osmibitové znaky


Jak jsem psal výše, na VGA kartě je matice 80x25 znaků projekcí fyzického rozlišení 720x400 bodů. Starší adaptéry EGA (se kterými je VGA zpětně kompatibilní) taktéž uměly 80x25, ale tam šlo o projekci rozlišení 640x350.
Jak je to možné?
Výpočtem zjistíme, že u VGA jsou obrazové buňky široké 9 bodů (9x80=720), kdežto u EGA 8 bodů (8x80=640). Jenže znaky v tabulce znaků mají u obou grafických karet šířku 8 bodů. Jak je tedy definován 9. bod?
Bod je prostě ponechán prázdný. Výjimkou jsou znaky C0h-DFh. U nich se do devátého bodu kopíruje bod osmý. (nebo řečeno jinak, do osmého bitu se kopíruje sedmý bit)
Nad tímto bodem programátor tedy nemá plnou kontrolu. Někdy má proto smysl přepnout se do režimu s 8 bodů širokými buňkami při zachování matice 80x25. Fyzické rozlišení bude 640x400.
Procedure SetCharWidth8(t:boolean);
{ Potrebuje unit DOS }
var r:registers;
    x:byte;
begin
if t then r.BX:=1 else r.BX:=$800;
x:=port[cc] and not (4+8);
if not t then x:=x or 4;
port[c2]:=x;inline ();
portw[c4]:=$100;
portw[c4]:=$1+r.BL shl 8;
portw[c4]:=$300;
inline ();
r.AX:=000;
r.BL:=3;
Intr(0,R);
end;  

Toto nastavení "přežije" i znovuzavedení textového módu. Pokud tedy přepneš do osmibitových buněk, musíš to sám vrátit na devítibitové.
Definice uživatelských znaků
Už tu byla řeč o ASCII tabulce znaků. Tento soubor je uložen v paměti ROM videokarty a při startu počítače je zaváděn do RAM videokarty. Tudíž se dá předefinovat. Přesně tohle dělají ovladače na české znaky a českou klávesnici.
BIOS tady totálně selhává, neboť pro to neposkytuje dostatek funkcí. (Umí nahrát do videoRAM font, ale neumí ho zpětně přečíst) Je to ale schůdné i přes porty. Důležité je, provádět čachry s fonty v textovém módu. V grafice se mapuje videoRAM do normální paměti jinak a ke znakové sadě se nedá dostat.
type charset = Array[0..255,1..16] of Byte;


Procedure LoadCharsetFromVGA(var c:charset);
Var
  b : Byte;
  w : Word;
begin
  For b := 0 to 255 do
  begin
    w := b * 32;
    Inline();
    PortW[C4] := $402;
    PortW[C4] := $704;
    PortW[CE] := $204;
    PortW[CE] := $005;
    PortW[CE] := $006;
    Move(Ptr(, w)^, c[b, 1], 16); {Ackoliv jsme v textaku, tak se k fontum pristupuje pres tento segment}
    PortW[C4] := $302;
    PortW[C4] := $304;
    PortW[CE] := $004;
    PortW[CE] := 005;
    PortW[CE] := $E06;
    Inline();
  end;
end;


Procedure PlaceCharsetToVGA(c:charset);
Var
  b : Byte;
  w : Word;
begin
  For b := 0 to 255 do
  begin
    w := b * 32;
    Inline();
    PortW[C4] := $402;
    PortW[C4] := $704;
    PortW[CE] := $204;
    PortW[CE] := $005;
    PortW[CE] := $006;
    Move(c[b, 1], Ptr(, w)^, 16);
    PortW[C4] := $302;
    PortW[C4] := $304;
    PortW[CE] := $004;
    PortW[CE] := 005;
    PortW[CE] := $E06;
    Inline();
  end;
end;  

Změna celé znakové sady nebo třeba jen jednoho znaku je mocná zbraň. Dají se měnit dynamicky a velice rychle a lze takto vytvořit programy i hry, do kterých byste neřekli, že běží v texťáku. Na tomto principu někdy funguje "grafický ukazatel myši v textovém režimu". Podívejte se například na klasický DOSový Defrag, tam je to použito.

Tímto jsme vyřešili znaky v textovém módu. Jak to udělat pro grafický mód? Jde to? Jde to. Ale když u borlandů psali proceduru OutText, která píše v grafickém režimu, tak určitě nebyli ve formě. Na uživatelské VGA fonty zapomněli a OutText zcela debilně tahá fonty z ROM.
Nicméně dělá se to takhle:
Procedure ApplyCharsetToGraphics(c:charset);
begin
memw[$b800:3 * 4 + 2] := seg(c);
memw[$b800:3 * 4] := ofs(c);
end;  

Ovšem využitelné to je, například takhle:
uses Crt;
type charset = Array[0..255,1..16] of Byte;


Procedure Videorezim(w:word);assembler;
asm
mov ax,4f02h
mov bx,w
int 10h
end;


Procedure LoadCharsetFromVGA(var c:charset);
Var
  b : Byte;
  w : Word;
begin
  For b := 0 to 255 do
  begin
    w := b * 32;
    Inline();
    PortW[C4] := $402;
    PortW[C4] := $704;
    PortW[CE] := $204;
    PortW[CE] := $005;
    PortW[CE] := $006;
    Move(Ptr(, w)^, c[b, 1], 16);
    PortW[C4] := $302;
    PortW[C4] := $304;
    PortW[CE] := $004;
    PortW[CE] := 005;
    PortW[CE] := $E06;
    Inline();
  end;
end;


Procedure ApplyCharsetToGraphics(c:charset);
begin
memw[$b00:3 * 4 + 2] := seg(c);
memw[$b00:3 * 4] := ofs(c);
end;




var gd,gm:integer;
    s:string;
    c:charset;


begin
LoadCharsetFromVGA(c); {Nactu aktualni font z videoRAM}
DirectVideo:=false;    {Promenna jednotky CRT. Chceme psat pres BIOS, ne primo. }
Videorezim(2);       {640x480 16 barev}
ApplyCharsetToGraphics(c);  {Nastavim ukazatel na graficky font na C}
writeln('Napis neco s ceskymi znaky');
readln(s);
writeln('OK, napsal jsi:');
textcolor(12);         {I v grafickem rezimu lze psat pestrymi barvami...}
writeln(s);


repeat until keypressed;
while keypressed do readkey;


VideoRezim(3);
end.  

Textmódy ve Freepascalu


Všechny uvedené fígle lze v zásadě použít i ve freepascalu. Nicméně uvedené zdrojové kódy by bylo potřeba trošku upravit.
- pokud chcete přistupovat k polím port a portW, tak musíte dát do programu uses Ports
- nahradit proceduru Move procedurami DosMemGet a DosMemPut
- upravit proceduru ApplyCharsetToGraphics a počítat s tím, že font, který předává jako argument, musí být umístěn v konvenční paměti.

Nicméně Freepascal má navíc jednu jednotku, kterou Turbo pascal nemá. Jde o jednotku Video.
Moc toho neumí - prostě definuje dvojrozměrné pole velké jako matice obrazovky (oněch 80x25) s prvky typu word. Reprezentuje virtuální obrazovku a můžete do něho přistupovat jako do kteréhokoliv jiného pole. Funkcí UpdateScreen ho překopírujete na obrazovku. Je u toho jedna věc, která se mi líbí. V jednotce Video je totiž jakýsi "zámek", který můžete funkcí LockScreenUpdate zamykat a UnLockScreenUpdate odemykat.
Kouzlo je v tom, že LockScreenUpdate vlastně dělá zámek:=zámek+1 a UnLockScreenUpdate zámek:=zámek-1
UpdateScreen se ovšem provede jen pokud zámek=0. Takto se dá elegantně zamezit zbytečným mnohonásobným kopírováním na obrazovku.

A smysl této jednotky Video?
Její smysl je v totální multiplatformnosti. Program, který ji využívá, můžete bez jakýchkoliv změn přeložit pro všechny platformy, jaké Freepascal podporuje.
2006-11-30 | Laaca
Reklamy:
„Láska vdaných žen je nejcennější na světě, manželé o tom ovšem nevědí.“ Oscar Wilde