e-mail    Debatní kniha    Mapa stránek    Hlavní  
 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, aledodnes 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 x25 (fyzické (technické) rozlišení odpovídá 720x400). V každém políčkuje právě jeden znak - obvykle písmeno. Možných znaků je 255 (tedy nejenpí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ůžeto být i znak "mezera" - prázdné místo), je definována i barva tohotoznaku.
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 paletamožných barev pro pozadí rozšíří na 0-15. Za chvíli se podíváme, jak seto 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[$b800:adresa]:=byte(pismeno);
Mem[$b800: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 za1. řá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ánekvideopaměti (tzn. 0-7), ale já si myslím, že pokud se nastaví režimpomocí volání VESA (mov ax,4f02h;mov bx,3;int 10h), tak že je stránekpří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($10,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:=$1003;
r.bl:=byte(not b);
Intr($10,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í takmaximálně přepínat mezi 80x25 a 40x25, což je trochu málo. Použijemeradě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 nanultou. Pokud k režimům menších než 100h přičtete 80h nebo k režimůmnad 100h 8000h, tak se nevymaže obsah obrazovky.
S rozlišením obrazovky se dají dělat psí kusy a existuje spoustaX-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íčkarozdě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 jeVGA zpětně kompatibilní) taktéž uměly 80x25, ale tam šlo o projekcirozliš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 sedo devátého bodu kopíruje bod osmý. (nebo řečeno jinak, do osmého bituse kopíruje sedmý bit)
Nad tímto bodem programátor tedy nemá plnou kontrolu. Někdy má protosmysl 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[$3c4]:=$100;
portw[$3c4]:=$1+r.BL shl 8;
portw[$3c4]:=$300;
inline ();
r.AX:=$1000;
r.BL:=3;
Intr($10,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[$3C4] := $402;
    PortW[$3C4] := $704;
    PortW[$3CE] := $204;
    PortW[$3CE] := $005;
    PortW[$3CE] := $006;
    Move(Ptr($A000, 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[$3C4] := $402;
    PortW[$3C4] := $704;
    PortW[$3CE] := $204;
    PortW[$3CE] := $005;
    PortW[$3CE] := $006;
    Move(c[b, 1], Ptr(SA000, w)^, 16);
    PortW[$3C4] := $302;
    PortW[$3C4] := $304;
    PortW[$3CE] := $004;
    PortW[$3CE] := 005;
    PortW[$3CE] := $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é VGAfonty 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[$3C4] := $402;
    PortW[$3C4] := $704;
    PortW[$3CE] := $204;
    PortW[$3CE] := $005;
    PortW[$3CE] := $006;
    Move(Ptr($a000, w)^, c[b, 1], 16);
    PortW[$3C4] := $302;
    PortW[$3C4] := $304;
    PortW[$3CE] := $004;
    PortW[$3CE] := 005;
    PortW[$3CE] := $E06;
    Inline();
  end;
end;

Procedure ApplyCharsetToGraphics(c:charset);
begin
memw[0:$43 * 4 + 2] := seg(c);
memw[0:$43 * 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 maticeobrazovky (oněch 80x25) s prvky typu word. Reprezentuje virtuálníobrazovku a můžete do něho přistupovat jako do kteréhokoliv jinéhopole. Funkcí UpdateScreen ho překopírujete na obrazovku. Je u tohojedna věc, která se mi líbí. V jednotce Video je totiž jakýsi "zámek",který můžete funkcí LockScreenUpdate zamykat a UnLockScreenUpdateodemykat.
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.


Pascal - hlavní
Překladače
Vlastní články
Převzaté články
Věci na stáhnutí
Odkazy k tématu
BP7 buglist
Chyba Run-time 200

BASIC