int21h

GRAFICKÝ REŽIM

Tak, máme tu druhý díl o video režimech. Trochu jsem se odmlčel, protože jsem byl líný, nemocný a pak jsem si dělal drze svoje věci. Tentokrát přejdeme už na zajímavější část a to jsou grafické režimy na počítačích PC. Opět k tomu budeme využívat Turbo Pascal 7 a Free Pascal spolu s jejich assemblery JSA.

Předem upozorňuji, že se budu věnovat hlavně kartám VGA a vyšším. Pokud někdo chcete programovat Hercules, EGA či CGA, a budete mít dost štěstí, kdy seženete na smetišti nejen grafické karty, ale i monitory, můžete se podívat do ATHelpu, popř. do manuálů na Internet, kde najdete potřebné informace. Stejně tak se nebudu věnovat 16 barevným režimům (např. $12). Ty jsou podobné 256 barevným, jen s tím, že paleta má jen 16 položek, pixel je indexován 4 bity, tedy v jednom bytu jsou 2 (levý je MSN), jen změna palety probíhá tak, jak jsem ji popsal u textových režimů (alespoň myslím: pokud ne, tak je stejná jako u 256 barev). Protože se navíc zatím budeme držet jen reálného režimu (i když některé příklady ukážu i ve Free Pascalu), nebudeme zatím brát režim LFB (ten až později), ale pouze Bank Switch. Nebudu se také věnovat nastavování tzv. dvojitých režimů (některé nové karty je nepodporují, a nejsou ani celkem tak moc potřeba).

Nejprve si dáme trochu té teorie. Grafická karta mapuje svůj prostor na 64 kB paměti od adresy $a000 (pokud chcete využít 128 kB najednou, a Vaše karta nepotřebuje nutně dvě okna v režimu VESA pro zápis a čtení zvlášť, a nemáte přídavný aktivní adaptér typu Hercules, můžete si pomocí CGC registru ($3ce,$3cf) číslo 6 zapnout nastavením bitů 2-3 na 00 tento režim; 64 kB má kombinace 01, viz. ATHelp. Programovat jsme se je učili v minulé části tohoto seriálu; pozor, některé nové karty nejsou kompatibilní s VGA a neposkytují přístup k VGA registrům a někdy ani k DAC (tj. nenastavíte paletu přes OUT, ale jedině přes instrukce INT). To ale probereme až v sekci VESA). Pokud se celá VRAM vejde v daném režimu do tohoto prostoru, můžete jej adresovat stylem (Y*MaxX+X)*BPP, kde X,Y jsou souřadnice pixelu, MaxX je maximální rozlišení (nebo délka logického řádku, pokud je jiná než délka zobrazované části) a BPP je počet bytů na pixel. Pokud má karta černobílý monitor, Vás to nemusí zajímat. Převod proběhne automaticky v něm, resp. v D/A převodníku karty. Můžete samozřejmě zapnout přepočet palety na černobílou (resp. Gray Scale) před tím, než ji zapíšete, ale je to celkem zbytečné, neboť si ji můžete i sami vypočítat (ideálně nastavte v každé barvě složky R=G=B a takto můžete mít až 64 odstínů šedi a zbytek třeba barevný).

BPP může být 1 (pro 256 barev), 2 pro HiColor (15 či 16 bitů), 3 pro 24 bitový True Color, nebo 4 pro 32 bitový TC (stejný jako 24 bitový, jen je rychlejší, ale zabírá více paměti, a některé karty či lépe řečeno spíše aplikace berou nejvyšší byte jako alfa kanál, nebo-li masku průhlednosti, kdy 0=pixel není vidět (je vidět to, co je pod ním, což ve VRAM dost dobře nejde), 255=pixel je vidět celý). U režimů s BPP>1 se do paměti VRAM zapisují už přímo RGB složky pixelů, ne indexy do palety. True Color má tuto skladbu BGR, resp. 0BGR. HiColor má ve své 16 bitové variantě "rrrr rggg gggb bbbb", v 15 bitové takovouto "0rrr rggg ggbb bbb". Je nutné podotknout, že některé karty nezvládají 24 bitové True Colory, nebo jednu z varianty HC, jiné např. zvládají obojí, ale standardně si inicializují 24 bitů jako první. Musíte tedy Vaše aplikace psát co nejvíce přizpůsobivé. Pro BPP=1 se do VRAM zapisuje index barvy v paletě. Paleta se nastavuje v PEL registrech ($3c6-9), kdy standardně platí, že je významných pouze prvních (dolních) 6 bitů v každém ze tří bytů RGB (červená zelená modrá). Pokud máte paletu s rozsahem 0-255, musíte nejprve každou složku vydělit 4 (nebo provést SHR 2) bez zaokrouhlování nahoru! Naopak, při čtení ji zase musíte vynásobit.

Pokud se v daném režimu celá VRAM do standardních 64 kB nevejde (což je už pro 640x400x256), musíte použít v RM tzv. přepínání banků (v PM můžete využít i LFB, to probereme později). Na toto raději používejte systém VESA, který normalizuje obsluhu vyšších rozlišení na všech kartách. Je s ním ale problém ve Windows XP, jelikož ovladače některých karet na VESA kašlou a inicializují ho chybně, tedy rozsah frekvencí monitoru je mimo meze. Musíte znát tzv. granularitu karty, která se běžně pohybuje na 4kB, 16kB nebo 64 kB. To označuje, kolik bytů paměti se mapuje do našeho okna. Ideálně je 64K, protože pak stačí přepínat méně banků a je to rychlejší. Zápis a čtení pixelu pak probíhá tak, že nejprve zjistíte lineární adresu podle výše uvedeného vztahu a poté provedete BANK := ADR DIV GRAN, a OFS := ADR MOD GRAN. Pokud je číslo banku jiné než právě nastavený (to si musíte udržovat sami; pokud zapisujete celou VRAM postupně, toto zjišťovat nemusíte, neboť vlastně banky zvyšujete postupně), musíte ho nejprve změnit. Poté už jen využijete OFS jako adresu od báze $a000 a zapíšete 1-4 bytů na 1 pixel podle BPP.

Je nutné podotknout jednu věc. Čtení z VRAM je o dost pomalejší než její zápis, navíc - VRAM je o dost pomalejší než normální RAM (pokud ovšem nemáte AGP kartu s kompletně sdílenou pamětí, sdílený Z-Buffer nestačí). Proto je vhodné si zavést tzv. virtuální obrazovku. To je normální pole ARRAY, které má stejné rozměry jako VRAM. Např. pro režim VGA stačí udělat


TYPE	TVRAM = ARRAY[0..199,0..319] OF BYTE.
VAR	VVRAM : TVRAM;
	VRAM : TVRAM absolute $a000:0;  

Je ovšem problém v tom, že TP7 nás omezuje v paměti pro jednu proměnnou na necelých 64K (lépe řečeno nás omezuje DOS na +/- 65520). Všimněte si, že sloupce jsou až druhý index, tedy budeme adresovat přes [Y,X], protože paměť je lineární, tedy změna Y se musí projevit v adrese markantněji než změna X. Je ale jasné, že např. pro 640x400 musíme už udělat tuto proměnnou:


VAR	VVRAM : ARRAY[0..3,0..99,0..639] OF BYTE;  

kde je první číslo index banku. Nebo, aby se to lépe adresovalo, můžeme vytvořit něco takového:


VAR	VVRAM0 : ARRAY[0..99,0..639] OF BYTE;
	VVRAM1 : ARRAY[100..199,0..639] OF BYTE;
	VVRAM2 : ARRAY[200..299,0..639] OF BYTE;
	VVRAM3 : ARRAY[300..399,0..639] OF BYTE;  

Kdy můžeme lépe adresovat řádky (nemusíme je dělit 4x), ale zase musíme pomocí CASE rozhodnout podle řádku o tom, kterou použijeme proměnnou. Problém je v tom, že Pascal nás zároveň omezí na velikost všech proměnných v datovém segmentu na 64K, takže na něco takového jako je výše můžeme zapomenout. Můžeme např. využít dynamické proměnné, které vytvoříme takto:


TYPE	TVVRAM = ARRAY[0..99,0..639] OF BYTE;
	PTVVRAM = ^TVVRAM;
VAR	VVRAM : ARRAY[0..3] OF PTVVRAM;  

Pokud prohodíte první dva řádky v bloku TYPE, TP7 to vyjímečně také povolí. Pak musíme pomocí GETMEM nebo NEW vytvořit na haldě 4 bloky paměti a před skončením programu je zase dealokovat. Adresovat je budeme přes VVRAM[b,y-z,x]^ := P:


VAR	I : BYTE;
	X,Y : WORD;
	BARVA : BYTE;
BEGIN
 FOR I := 0 TO HIGH(VVRAM) DO
  GETMEM(VVRAM[I],SizeOf(TVVRAM));
 X := 128;
 Y := 128;
 BARVA := 15;
 CASE Y OF
  0..99 : VVRAM[0][Y,X]^ := BARVA;
  100..199 : VVRAM[1][Y-100,X]^ := BARVA;
  200..299 : VVRAM[2][Y-200,X]^ := BARVA;
  300..399 : VVRAM[3][Y-300,X]^ := BARVA;
 END;
 FOR I := 0 TO HIGH(VVRAM) DO
  FREEMEM(VVRAM[I],SizeOf(TVVRAM));
END;  

Výběr banku můžeme také nahradit jednodušším řádkem:


VVRAM[Y DIV 100,Y-(Y DIV 100)*100,X]^ := BARVA;  

Ale ten bude dost pravděpodobně pomalejší než naše CASE. Navíc se dostáváme k dalšímu omezení. Velikost haldy je 640K, ale pro nás je většinou dostupných tak 300-500 kB (využijte MaxAvail a MemAvail funkce) dle počtu DOSových aplikací a správně nastaveného OS (resp. správce HIMEM a ostatních). 640x400 má 256.000 bytů (250 kB), ale už např. HiColor by zabral 2x tolik!

Ve chráněném režimu tyto potíže odpadají, tam máme paměti kolik jen chceme, zde si ale musíme poradit přes paměť XMS nebo EMS. Pro ně ovšem musí být v paměti správce (HIMEM, DosBox nebo Windows). Oba dva formáty nám umožní alokovat paměť nad 1.MB paměti, ovšem s tím, že k němu nemáme přímý přístup, a nemůžeme v něm ani spouštět aplikace (do EMS je ale možné kód přenášet, prakticky do XMS taky). Funguje to tak, že si vytvoříme nějakou dynamickou proměnnou (cca. 32 kB) pro XMS (EMS většinou používá nějaké standardní okno se 4 stránkami po 16K, novější verze už fungují trochu lépe), alokujeme určitý prostor (pro XMS v Kbytech (většinou omezeno na 2048kB na 1 blok), pro EMS v 16K stránkách) a nyní už jen přenášíme (kopie) paměť mezi konvenční (dolní) pamětí a naším blokem v XMS nebo EMS (XMS nám vrací handle, podle kterého si vybíráme, kam to chceme přenést). Je nutné podotknout, že pod DOSem mají někteří správci (HIMEM.SYS jmenovitě) problémy přenášet liché délky bloku, takže musíte přenášet jen šířky násobky 2 bytů. A před skončením programu musíte Vaše alokované bloky samozřejmě uvolnit. Pokud plánujete, že si program ještě spustíte, můžete si handle uložit na disk a po znovu spuštění můžete přes toto handle číst neodalokované bloky. Pokud ovšem handle ztratíte, zůstane tato paměť až do resetu počítače nepřístupná.

Je vhodné dost optimalizovat, tedy dělte pomocí SHR, násobte pomocí SHL. Pixely nezapisujte po 1, ale pokud možno přes MOVSx (MOVE) po skupinách (idálně přes celý řádek, pokud ovšem nevyužíváte průhlednost). Do VRAM posílejte data z VVRAM pomocí 32 bitových přesunů, kdy v ASM napíšete MOVSW a dáte před ni prefix $66. Pokud potřebujete dělit, zjistěte si, zda dané číslo není mocninou 2. DIV 65536 se dá napsat jako SHR 16, DIV 4K se dá napsat jako SHR 12, DIV 16K jako SHR 14. Pokud potřebuje provést MOD se stejným číslem, můžete to udělat jako AND (CISLO-1). Pro vyšší rychlost se také doporučuje využívat pouze 32 bitové režimy a pokud máte přepínání banků, je vhodné nastavit si logickou délku řádku (viz. dále) tak, aby banky končily přesně na koncích řádků a ne někde uprostřed. Používejte rozsah v mocninách dvou. Např. pokud bychom vytvořili u příkladu se 4 banky VVRAM rozsahy v banku ne 0-99, ale 0-63, vytvořili bychom těchto banků 7 (alokujeme o 30.720 bytů více, než využijeme. Kdybychom dali do banku jen 32 řádků, tak bychom banků měli 13, ale zbytečně bychom měli navíc jen 10.240 bytů; ovšem do konečna to také nejde, protože jednak má DOS omezený počet handle (samozřejmě, má jich dost i na stovky těchto bloků, ale každé toto handle vyžaduje nějaký prostor v paměti! XMS je navíc mnohem omezenější, takže je vhodné naučit se šetřit) a jednak je případné uvolňování a opětovná alokace na haldě trochu pomalá) a pak bychom je mohli adresovat tímto stylem:


VVRAM[Y SHR 6,Y-(Y SHR 6) SHL 6,X]^ := BARVA;  

Což je mnohem rychlejší než DIV a násobení. Snažte se také některé opakující se výpočty provést předem do pracovní proměnné, aby se nemusely počítat dvakrát. Zde bychom např. provedli toto:


A := Y SHR 6;
VVRAM[A,Y-A SHL 6,X]^ := BARVA;  


Tak, to je teorie, kterou budeme ve zbytku našeho seriálu potřebovat (navíc dynamickou paměť a XMS použijeme někdy příště i pro zvukové karty), takže se už můžeme konečně pustit na praxi. Začneme zase od těch nejjednodušších věcí, neboť to je správný systém výuky na všechny možné vědy...

Začneme tedy s praktickým užitím grafiky. Jen připomínám, že většina dále uvedených věcí bude fungovat i ve Free Pascalu. Měla by tam fungovat i XMS paměť, ale její používání je ve FP zbytečné, neboť si můžete přes VAR nebo GETMEM naalokovat co hrdlo ráčí a bude to jednodušší a rychlejší.


Začneme jakoby od konce. Ukážeme si, jak opustit grafický režim. Kdo jste zvyklí na Graph jednotku v TP7, tak věřte, že prakticky neexistuje nic jako opuštění grafického režimu. Pouze přejdete do režimu jiného. Ve většině DOSových aplikací se program vrací do textového režimu 80x25, tedy napíše něco v tomto stylu:


asm  
	mov	ax,3
	int	10h  
end;  

Zde jsme použili přerušení 16, resp. $10, které obsluhuje BIOS primární grafické karty (v případě, že jich tam máte více; sekundární se obsluhuje přímo přes její VRAM většinou na $b800 nebo $b000 v textovém režimu). Pokud chceme pracovat s grafikou, zkusíme nejprve to nejjednodušší rozlišení:


asm  
	mov	ax,13h
	int	10h  
end;  

Tímto aktivujeme dříve nejpoužívanější rozlišení pro hry: 320x200 v 256 barvách. Dnes většina autorů používá mnohem větší rozlišení, ale jaksi zapomíná, že nejde o kvantitu, ale o kvalitu, a zbytečně tedy zvyšují nároky na PC, ačkoliv samotná hra svým vzhledem spadá do roku 123. A nezapomínejte na to, že $13 musí být hexa (13 je 320x200 pro 16 barev). Přepínat režimy můžeme i tak, že nám video karta nesmaže obrazovku (pokud o to stojíme) a to tak, že k registru AX přičteme konstantu $80. Takže, jsme v grafice, a teď co s ní. Většinou budeme asi zapisovat pixely. Vzhledem k tomu, jak je dělena paměť, můžeme zapsat pixel takto:


Mem[$a000:Y*320+X] := BARVA;  

Aby to bylo rychlejší, můžeme to přepsat do assembleru, nebo to napsat i v TP7, ale hlavně pomocí posunů (256+64 = 320):


Mem[$a000:Y shl 6+Y shl 8+X] := BARVA;  

Přečíst pixel z obrazovky je velmi snadné, ale pomalé (viz. teorie):


BARVA := Mem[$a000:Y shl 6+Y shl 8+X];  

Pixely v barvě 0-15 odpovídají barvám EGA, ostatní jsou odstíny dalších základních barev. Můžete si je zjistit, pokud si necháte vypsat celou paletu:


var	x,y : byte;
begin
 for y := 0 to 199 do
  for x := 0 to 255 do
   Mem[$a000:y shl 6+y shl 8+x] := x;
end;  

Paleta se dá ale změnit. Máme k tomu služby přerušení:


function Color(i,r,g,b : byte); assembler;
asm  
	xor	bx,bx
	mov	ax,1010h
	mov	bl,i
	mov	ch,g
	mov	cl,b
	mov	dh,r
	int	10h  
end;  

Měnit ale barvu po jedné není zrovna moc rychlé. Naštěstí máme službu, která to obstará za nás. Nám jen stačí, když celou paletu uložíme do 768 bytů dlouhého pole RGB (v tomto pořadí!), kde každá položka barvy bude mít hodnotu max. 63 (stále používáme jen 6 bitů):


var	paleta : array[0..767] of byte;  

nebo

var	paleta : array[0..255] of record
				   r,g,b : byte;
				  end;


function _Paleta(pole : pointer); assembler;
asm  
	mov	ax,pole.word[2]
	mov	es,ax
	mov	dx,pole.word[0]
	mov	ax,1012h
	xor	bx,bx
	mov	cx,256
	int	10h  
end;  

Funkci potom zavoláme jako _PALETA(@PALETA);. Číst paletu je samozřejmě také možné:


function CtiPaletu(pole : pointer); assembler;
asm  
	mov	ax,pole.word[2]
	mov	es,ax
	mov	dx,pole.word[0]
	mov	ax,1017h
	xor	bx,bx
	mov	cx,256
	int	10h  
end;  

Existuje však mnohem rychlejší způsob a to nastavování přes porty. Ten nemusí být u novějších karet přípustný, ale to probereme až u VESA módů. Existují tzv. PEL registry, které pracují s paletou. Pokud do prvního zapíšete index barvy, tak do druhého můžete zapsat postupně její RGB hodnoty. Pokud chcete paletu zapsat, použijte registr xx8, jinak pro čtení zvolte xx7. Data jsou pak vždy na xxx9. VGA karta automaticky po obdržení 3 data bytů zvýší index barvy, takže můžete pokračovat v zápisu další barvy (pokud jich chcete zapsat víc).


function ZmenBarvu(index, r,g,b : byte);
begin
 Port[$3c8] := index;
 Port[$3c9] := r;
 Port[$3c9] := g;
 Port[$3c9] := b;
end;


function CtiBarvu(index, r,g,b : byte);
begin
 Port[$3c7] := index;
 r := Port[$3c9];
 g := Port[$3c9];
 b := Port[$3c9];
end;


function ZmenPaletu(var pole : array of byte);
var i : byte;
begin
 Port[$3c8] := 0;
 for i := 0 to High(pole) do
  Port[$3c9] := pole[i];
end;


function CtiPaletu(var pole : array of byte);
var i : byte;
begin
 Port[$3c7] := 0;
 for i := 0 to High(pole) do
  pole[i] := Port[$3c9];
end;  

Nyní se zmíníme o jistém triku. Pokud trochu něco víte o HW video karty, tak víte, jakým způsobem promítá obraz na monitor. Pokud provedeme změnu do obrazu během vykreslování, můžeme dostat nepěkné efekty nebo zrnění. Pro tento účel je vhodné testovat, zda neprobíhá zpětný běh paprsku. Pokud ano, můžeme kreslit:


procedure ZpetnyBeh; assembler;
asm  
	mov	dx,03dah
@0:	in	al,dx
	test	al,8
	jnz	@0  
end;  

Jakmile nás funkce pustí, můžeme kreslit. Vhodnější je testovat nikoliv bit 4 v registru $3da, ale bit 7 v registru $3c2, neboť ten dokáže rozlišit snímkový běh od řákového (řádkový trvá o mnoho kratší dobu, navíc nevíme, zda už je vše vykreslené nebo jsme někde u prostřed):


procedure ZpetnyBeh; assembler;
asm  
	mov	dx,03c2h
@0:	in	al,dx
	test	al,128
	jnz	@0  
end;  

Nevýhodou výše uvedeného příkladu je, že sice zjistíme, že probíhá snímkový běh, ale už nezjistíme, zda-li právě začal, nebo zda-li je už na konci a tedy bychom raději kreslit neměli. To se řeší tak, že nejprve počkáme, až případný paprsek skončí (pokud už běží), a pak teprve počkáme, až se objeví další. I v nejhorším případě nás to zdrží jen na 1/60 vteřiny (u moderních monitorů s frekvencí 144 Hz to bude jen 1/144):


procedure ZpetnyBeh; assembler;
asm  
	mov	dx,3c2h
@1:	in	al,dx
	test	al,128
	jz	@1
@2:	in	al,dx
	test	al,128
	jnz	@2  
end;  

Když už jsme u té palety, ukážeme si pár triků, tak dělat s obrazem zajímavé efekty. Ty jsou rychlejší než u BPP>1 režimů, protože tady neplatí, že čím větší X*Y, tím pomalejší operace. Jistě znáte, když se paleta u některých her ztmavila až do černa. Tomu se říká FadeOut. FadeIn je, když se obraz naopak objeví. Pak jsou efekty ještě jako FadeFromGray, FadeToGray nebo FadeToWhite či FadeFromWhite, kdy cílová/zdrojová barva obrazovky není černá, ale bílá, nebo se naopak obraz jakoby převede do odstínů šedi. Mnohem efektnější je pak FadeToPal, kdy se paleta postupně změní na jinou paletu. Podotknu, že při každé změně palety je vhodné nejprve počkat na paprsek. A každá změna může proběhnout ve Vámi stanoveném počtu kroků a každý tento krok může trvat určitý čas. Funkce na zpoždění vypadá nějak takto:


procedure Cekej(Taktu : longint);
var Start : longint;
begin
 Start := MemL[0:$46c];
 while MemL[0:$46c]-Start >= Taktu do;
end;  

Funkce na změnu palety pak vypadají takto (pro Free Pascal musíte nahradit Port funkcemi OutPortB a také není nutné používat přetypování, neboť FP nemá chybu pro přetečení jako TP7). Pro zjednodušení jsem tam nepřidal zpoždění. To umístěte před zpětný běh nebo za celý cykl "FOR I".


procedure FadeIn(Kroku : word);
var i,j : word;
begin
 Port[$3c8] := 0;
 for j := 0 to Kroku-1 do
 begin
  ZpetnyBeh;
  for i := 0 to 768-1 do
   Port[$3c9] := word(paleta[i])*j div (Kroku-1);
 end;
end;


procedure FadeOut(Kroku : word);
var i,j : word;
begin
 Port[$3c8] := 0;
 for j := Kroku-1 downto 0 do
 begin
  ZpetnyBeh;
  for i := 0 to 768-1 do
   Port[$3c9] := word(paleta[i])*j div (Kroku-1);
 end;
end;


procedure FadeToWhite(Kroku : word);
var i,j : word;
begin
 Port[$3c8] := 0;
 for j := 0 to Kroku-1 do
 begin
  ZpetnyBeh;
  for i := 0 to 768-1 do
   Port[$3c9] := word(paleta[i])+(63-word(paleta[i]))*j div (Kroku-1)
 end;
end;


procedure FadeFromWhite(Kroku : word);
var i,j : word;
begin
 Port[$3c8] := 0;
 for j := Kroku-1 downto 0 do
 begin
  ZpetnyBeh;
  for i := 0 to 768-1 do
   Port[$3c9] := word(paleta[i])+(63-word(paleta[i]))*j div (Kroku-1)
 end;
end;  

Převod mezi šedou a barevnou paletou je trošku složitější (a pozor, vnější cykl jde obráceně):


procedure FadeToGray(Kroku : word);
var i,j : word;
    gray : byte;
begin
 for j := (Kroku-1) downto 0 do
 begin
  ZpetnyBeh;
  Port[$3c8] := 0;
  for i := 0 to 768-1 do
  begin
   if i mod 3 = 0 then
    gray := (word(paleta[i])+word(paleta[i+1])+word(paleta[i+2]) div 3;
   if gray > pal[i] then
    Port[$3c9] := gray-(gray-word(paleta[i]))*j div (Kroku-1)
     else
      Port[$3c9] := gray+(word(paleta[i])-gray)*j div (Kroku-1);
  end;
 end;
end;


procedure FadeFromGray(Kroku : word);
var i,j : word;
    gray : byte;
begin
 for j := 0 to (Kroku-1) do
 begin
  ZpetnyBeh;
  Port[$3c8] := 0;
  for i := 0 to 768-1 do
  begin
   if i mod 3 = 0 then
    gray := (word(paleta[i])+word(paleta[i+1])+word(paleta[i+2]) div 3;
   if gray > pal[i] then
    Port[$3c9] := gray-(gray-word(paleta[i]))*j div (Kroku-1)
     else
      Port[$3c9] := gray+(word(paleta[i])-gray)*j div (Kroku-1);
  end;
 end;
end;  

Efekt prolínání palety je velmi podobný:


procedure FadeToPal(Kroku : word; Zdroj,Cil : array of byte);
var i,j : word;
begin
 Port[$3c8] := 0;
 for j := 0 to Kroku-1 do
 begin
  ZpetnyBeh;
  for i := 0 to 767 do
   if cil[i] > zdroj[i] then
    Port[$3c9] := zdroj[i]+word(cil[i]-zdroj[i])*j div (Kroku-1)
     else
      Port[$3c9] := zdroj[i]-word(zdroj[i]-cil[i])*j div (Kroku-1);
 end;
end;  

A nemusím snad podotýkat, že musí platit KROKU <> 0. Stačí na první řádek procedury doplnit kontrolu a případně dát KROKU := 1;. Před tím, než zahájíte FadeIn, pokud jste před tím neprovedli FadeOut, musíte nastavit paletu na černou, pak vykreslit celou obrazovku a pak teprve zahájit FadeIn. Pro černou paletu můžete použít:


procedure BlackPal;
var index : word;
begin
 ZpetnyBeh;
 Port[$3c8] := 0;
 for index := 0 to 768-1 do
  Port[$3c9] := 0;
end;  

Nebo Vám stačí maskovat registr palety:


procedure Black;
begin
 Port[$3c6] := 0;
end;  

Pak ale nezapomeňte po prvním cyklu ve FadeIn na tento port poslat zase 255, jinak nic neuvidíte. Pro bílou paletu (pokud chcete provádět FadeFromWhite bez toho, že jste nejprve provedli FadeToWhite (můžete to použít pro výměnu obrázků, ale pokud tam ještě žádný nemáte, je zbytečné dělat Fade, leda byste to provedli na minimum kroků, kde to stejně nikdo nepostřehne)) použijte toto:


procedure WhitePal;
var index : word;
begin
 ZpetnyBeh;
 Port[$3c8] := 0;
 for index := 0 to 768-1 do
  Port[$3c9] := 63;
end;  

Pokud potřebujete i šedou paletu, je možné to provést takto (je to jednodušší varianta, kdy vytvoříte jen 64 odstínů šedi, kdy pro každou barvu pošleme 3x stejnou složku R=G=B, ale 256/64 = 4, tedy celkem 12x stejné číslo; existuje samozřejmě i vzoreček pro všech 256 odstínů, ale proč si komplikovat život):


procedure GrayPal;
var index : byte;
    i : byte;
begin
 ZpetnyBeh;
 Port[$3c8] := 0;
 for index := 0 to 63 do
  for i := 0 to 11 do
   Port[$3c9] := index;
end;  

Mezi další zajímavé efekty patří změna jasu nebo obarvení palety. Zde provádíme výpočty na paletě v paměti (předchozí funkce tuto paletu neměnily), takže pokud by Vám to přišlo nevhod, je dobré si paletu zkopírovat do nějakého jiného pole pomocí:


Mem(zdroj,cil,sizeof(cil));  

a pak pracovat na tomto poli. Podobné efekty budeme využívat i při BPP>1. Tam se také naučíme průhlednost. Výpočet průhlednosti v 256 barvách probíhá tak, že si zjisíte RGB od těch dvou barev, které chcete mixovat. Tak provedete mix jako při TC (viz. později), a podle nového RGB najdete nejbližší barvu v paletě. Je to velmi zdlouhavá činnost, proto se doporučuje používat buď vypočtenou paletu nebo si předpočítat hodnoty pro kombinace pixelů (např. tak, že jim nejprve RGB složky 4x zmenšíte, tím dostanete jen 64/4 = 16 možností, 16*16 = 256 kombinací. Když si určíte např. 32 stupňů průhlednosti, dostanete 256*32 = 8 kB velkou tabulku. Pak už nemusíte nic počítat, stačí když oba pixely vezmete jako indexy do tabulky - pokud tam budete mít složky RGB, což je velmi pravděpodobné, zabere to 3x tolik bytů).

Co se týče těch efektů, tak vlastně jen obarvujeme složky pixelů, kdy 128 je beze změny, 64 je 1/2 jas, a 255 je 2x takový jas. Ideálnější je samozřejmě nahradit SHR 8 na SHR 4, a pak bude platit že 16 = 100% a na obě strany pak máte cca. 16x zvětšení/zmenšení položky (ovšem bez mezistupňů pro zmenšování, např. 1.5x už teď nepůjde, ale zase půjde 3x zvětšit). Změna jasu je pro změny R=G=B, změna obarvení je pro R<>B nebo B<>G, či R<>G. Procedura bude vypadat takto:


procedure ZmenSlozky(dR,dB,dG : byte);
var i : word;
    p : word;
begin
 for i := 0 to 255 do
 begin
  p := i shl 2;
  paleta[p] := word(paleta[p])*dR shr 8;
  paleta[p+1] := word(paleta[p])*dG shr 8;
  paleta[p+2] := word(paleta[p])*dB shr 8;
 end;
end;  

Pokud chcete změnit jen některé pixely (pixel), jistě bude pro Vás jednoduché upravit si tuto (a případně i jiné funkce) takto:


procedure ZmenSlozky(_od,_do,dR,dB,dG : byte);
var i : word;
    p : word;
begin
 for i := _Od to _Do do
 begin
  p := i shl 2;
  paleta[p] := word(paleta[p])*dR shr 8;
  paleta[p+1] := word(paleta[p])*dG shr 8;
  paleta[p+2] := word(paleta[p])*dB shr 8;
 end;
end;  

Dobrá, teď opustíme efekty a vrhneme na urychlení našeho kreslení. Kreslit po jednom pixelu je velice neefektivní a číst z VRAM je už vůbec na nic. Pro tento účel si zavedeme virtuální obrazovku. To bude vlastně obyčejná RAM, která nám bude zastupovat skutečnou VRAM. Stačí nám jen upravit některé funkce:


var	VVRAM : array[0..199,0..319] of byte;


procedure Pixel(X,Y : word; Barva : byte);
begin
 VVRAM[Y,X] := Barva;
end;


function CtiPixel(X,Y : word) : byte;
begin
 CtiPixel := VVRAM[Y,X];
end;  

Pro zjednodušení se zde vyhýbám kontrolám, zda X <= 319, atd., ale je na Vás, zda je tam přidáte a zpomalíte tím vykreslování, nebo zda si jednoduše dáte pozor na to, kam kreslíte (jinak můžete být celkem překvapeni: pokud je délka logického řádku (viz. dále) = 320, tak pixel [320,0] = pixel [0,1]). Do této paměti můžeme vesele kreslit a číst z ní, aniž bychom museli čekat na paprsek. Teprve, až budeme mít celou obrazovku hotovou, můžeme ji poslat do video karty:


Move(VVRAM,Mem[$a000:0],SizeOf(VRAM));  

Vymazat ji můžeme stejně jednoduše (pokud budeme předpokládat, že barva 0 je černá):


FillChar(VVRAM,SizeOf(VRAM),0);  

Protože Pascal je 16 bitový, tak je zobrazování takové VRAM pomalejší, než by dnes mohlo být. Když přepíšeme funkci MOVE na 32 bitovou, ušetříme tím cca. 40% času (protože přenášíme po Dwordech, stačí nám 16.000 cyklů namísto 32.000 pro Wordy):


procedure ZobrazVVRAM; assembler;
asm  
	mov	ax,$a000
	mov	es,ax
	xor	di,di
	mov	si,offset vvram
	{lds	si,vram}
	mov	cx,16000
	rep
	db	66h
	movsw  
end;  

Stejně tak můžeme urychlit i výmaz obrazovky (a rovnou si jej přepíšeme na volitelnou barvu; do registru snad ES nic neukládat nemusíme, neboť ten by se měl naplnit na DS díky tomu, že je standardně přiřazeným k offsetovému registru DI):


procedure ClearVVRAM(Barva : longint); assembler;
asm  
	{mov	ax,ds
	mov	es,ax}
	mov	di,offset vvram
	mov	cx,16000
	db	66h
	mov	ax,barva
	rep
	db	66h
	stosw  
end;  

Pokud do Barva vložíme 0, bude to standardně černá. Pokud tam vložíme -1 (což je v dvojkovém doplňku $ffffffff), bude to bílá. Pokud tam dame cokoliv jiného, např. $ff008000, dostaneme zajímavý vzorek). Nebo to můžeme přepsat čistě na černou:


procedure ClearVVRAM; assembler;
asm  
	les	di,vvram
	mov	cx,16000
	db	66h
	xor	ax,ax
	rep
	db	66h
	stosw  
end;  

Zapisovat po řádcích je vhodné, pokud chceme něco opakovat (u zápisu do VRAM by to bylo rychlejší, ale protože máme VVRAM, je zbytečné zapisovat jeden řádek najednou nejprve někam a pak do VVRAM). Pokud např. chceme vytvořit okno, které není průhledné, můžeme si do pracovního bufferu zapsat na 1. a poslední pixel barvu rámečku a zbytek vymazat na barvu pozadí. Poté už tento řádek zapíšeme několikrát do buferu, aniž bychom ho museli neustále skládat. Jednodušší je ale pracovat přímo s VVRAM, takže pomocí PIXEL nakreslíme rámeček a poté mocí FILL vymažeme určitou oblast (až budeme pracovat s XMS, je samozřejmě rychlejší si řádek připravit předem a pak zapsat jen řádky, protože se tím ušetří počet přenosů):


procedure FillVVRAM(X1,Y1,X2,Y2);
var y : word;
begin
 for y := y1 to y2 do
  FillChar(VRAM[y,x1],x2-x1,0);
end;  

Dobrá, to bychom měli základní grafický režim. Vám to ale určitě stačit nebude. Zkusíme přejít tedy na další režim, např. 640x480 (nebo 640x400, který je přesně 2x větší než 320x200, resp. zabírá 4x tolik plochy a paměti VRAM). Zde se budeme muset poprat se spoustou nových problémů. Za prvé, 640x480 se nevejde do paměti 64kB, které jsou vyhrazeny 1 oknu. Za druhé, v základních režimech není 6x4 nikde popsán. Za třetí, režim zabírá 300 kB paměti pro VVRAM a není ho tedy možné umístit do datového segmentu Pascalu přes VAR, a i přes GETMEM by nám přeci jen mohlo těch 300K chybět (pokud programujete ve Free Pascalu, bod 3 Vás nemusí zajímat, neboť si můžete takové pole klidně nadefinovat přes VAR VVRAM[0..479,0..639] OF BYTE a budete to mít jednodušší; body 1 a 2 se Vás ale týkat budou - do doby, než přejdeme na LFB).

Nejprve vyřešíme bod 2.

Ale předem několik zajímavých informací. Protože se nyní zadávají módy VESA jako word, přibylo několik bitů, které by Vás mohli zajímat. Bit8 (tedy 256) je vždy nastaven na 1 (tak se pozná, že to je VESA). Bit15 je to samé, jako bit7; přes bit11 si můžete nastavit vlastní obnovovací frekvenci a nastavením bitu14 na 1 si vyberete tzv. Linear Frame Buffer (LFB), který je však využitelný jen v chráněném módu (PM), např. ve Free Pascalu. Pokud Vám funkce vrátí v AH = 3, tak to máte dobře, ale pro daný režim ta funkce nedává smysl. Není to tedy tak úplně chyba. Některé novější karty neumí režimy 400x300 či 320x200. Ale teď už na ten bod 2:

Výrobci video karet se kdysi dohodli, že aby nebyl nepořádek ve vyšších režimech, zavede se jednotná tabulka. Vás jako programátora už tedy nemusí zajímat, která karta má které rozlišení pod kterým režimem (taková tabulka existuje a je pěkně dlouhá). Byl zaveden standard VESA. Pro několik základních rozlišení, které obsahují 640x480 až 1600x1200, a barvy od 1 až po 4 BPP, byla vytvořena tabulka. Bohužel, pro nová rozlišení už tabulka není, a navíc, někteří výrobci se rozhodli ignorovat i tuto tabulku. Co z toho pro nás vyplývá? Buď to riskneme a použijeme číslo režimu takříkající na tvrdo (v našem případě 101h) a ono to na 97% vždy vyjde, nebo si musíme zjistit, pod kterým režimem je dané rozlišení s danou BPP (věnuje se tomu Laaca).

Jak na to? Jsou tu dvě funkce VESA, které zjišťují informace o samotném VESA a pak navíc ještě o samotných módech. Funkce VESA vrací v AL kód $4f a v AH 0, pokud byla operace úspěšná. Kromě funkcí uvádím i základní tabulku VESA režimů. Neuvádím mezi ně textové režimy (viz. ATHelp); potřebnou paměť spočítáte jako X*Y*BPP (16=0.5, 256=1, 32/64K=2, 16.8M=3-4):


100h - 640x400 256
101h - 640x480 256
102h - 800x600 16
103h - 800x600 256
104h - 1024x768 16
105h - 1024x768 256
106h - 1280x1024 16
107h - 1280x1024 256
10Dh - 320x200 32K (1:5:5:5)
10Eh - 320x200 64K (5:6:5)
10Fh - 320x200 16.8M (8:8:8)
110h - 640x480 32K (1:5:5:5)
111h - 640x480 64K (5:6:5)
112h - 640x480 16.8M (8:8:8)
113h - 800x600 32K (1:5:5:5)
114h - 800x600 64K (5:6:5)
115h - 800x600 16.8M (8:8:8)
116h - 1024x768 32K (1:5:5:5)
117h - 1024x768 64K (5:6:5)
118h - 1024x768 16.8M (8:8:8)
119h - 1280x1024 32K (1:5:5:5)
11Ah - 1280x1024 64K (5:6:5)
11Bh - 1280x1024 16.8M (8:8:8)
120h - 1600x1200 256

function VESAinfo(var vesa : tvesa) : boolean; assembler;
asm  
	mov	dx,1
	les	di,vesa
	mov	ax,$4f00
	int	10h
	cmp	ax,$4f
	je	@ok
	xor	dx,dx
@ok	mov	al,dl  
end;


function MODinfo(rezim : word; var mode : tmode) : boolean; assembler;
asm  
	mov	dx,1
	les	di,mode
	mov	cx,rezim
	mov	ax,$4f01
	int	10h
	cmp	ax,$4f
	je	@ok
	xor	dx,dx
@ok	mov	al,dl  
end;  

Tyto dvě funkce budeme využívat. V nich se dozvíte docela zajímavé odlišnosti od standardních režimů. VESA totiž zavádí docela zajímavé "podrazy". Ačkoliv velká většina karet se chová stejně, jako je tomu v režimu 320x200 bez VESA, u některých lze očekávat, že velikost okna nejen nemusí být 64 kB, ale třeba jen 4 kB (tedy se jich tam vejde někde až 16), ale také nemusí být okno R/W, tedy současně pro zápis i čtení. Mohou existovat dvě okna, která mohou být RW, ale také může být jedno jen R (čtení) a druhé jen W (zápis). Mohou se nastavovat zvlášť a nemusí ležet vždy na $a000 (ale mohou a tedy se jakoby překrývají).

Než půjdeme dál, ukážeme si ještě dvě užitečné funkce. Ta první mění počet bitů v paletě na složku (8/6) a ta druhá nám umožňuje měnit paletu na kartě v případě, že nám karta neemluje PEL registry:


procedure SetDAC(bitu : byte); assembler;
asm  
	mov	ax,$4f08
	xor	bx,bx
	mov	bh,bitu		{zadejte 6 nebo 8}
	int	10h  
end;


procedure SetPallete(var data : array of byte); assembler;
asm  
	les	di,data
	mov	ax,$4f09
	xor	bx,bx
	xor	dx,dx
	mov	cx,256
	int	10h  
end;  

Pro druhou funkci se ale používá jiný formát palety než bez VESA:


var	paleta : array[0..255] of record
						B,G,R : byte;		{pozor! je to obráceně}
						zarovnani : byte;		{nepoužívá se}
					  end;  

Jen doplním, že instrukce LES/LDS lze použít jen na ta data, která jsou definována jako globální přes VAR (ne ve funkci) nebo jako VAR ve funkci (předává se adresa). Na data, která se do funkce předávají bez VAR (hodnota) nelze LES použít (ta vlastně dělá jen to, že 4 byty z dané adresy umístí do 2 registrů po 2 bytech, takže by vyšla chybná adresa). Možná jsem někde (u textových režimů) toto použil chybně (LES na data bez VAR), tak mi to prosím odpusťte...

Nejprve si nadefinujeme tabulky, které nám tyto dvě funkce naplní (tabulky menily v průběhu vývoje VESA velikost, takže např. tabulka pro VESA3 je větší než pro VESA a to je docela průšvih):


var	_vesa : tvesa;
	_mode : tmode;  

Definici jsme udělali pomocí uživatelských typů. Jinak Vám samozřejmě nebráním udělat tyto tabulky dynamické přes GETMEM. Většinou je potřebujete stejně jen 1x a to na začátku programu, kde si z nich vezmete vše potřebné (a většinou je toho zlomek), které si uložíte do svých úspornějších tabulek. Tabulky jsou docela obsáhlé a dozvíte se toho z nich mnoho (pro více informací doporučuji dokumentaci VESA3):


type	_tvesa = record
			Znacka : array[0..3] of char;	{buď 'VESA' nebo 'VBE2'}
			Verze : word;			{např. 0300h; BCD. 102 = 1.2}
			OEM : pointer;			{ukazatel na OEM řetězec}
			Moznosti : longint;		{co karta umí}
			Mody : pointer;			{ukazatel na seznam módů}
			Pamet : word;			{*16K dává od verze 2 velikost VRAM}
			Revize : word;			{číslo verze SW}
			Vyrobce : pointer;		{ukazatel na název výrobce/prodejce}
			Produkt : pointer;		{ukazatel na jméno výrobku}
			PRevize : pointer;		{ukazatel na info o revizi (řetězec)}
			Rezerva : array[0..221] of byte;
			_OEM : array[0..255] of char;	{OEM řetězce}
		 end;


	_tmode = record
			Atributy : word;			{módu}
			AtributyA : byte;			{okno A}
			AtributyB : byte;
			Granularita : word;		{rozchod banků}
			Velikost : word;			{velikost okna}
			SegmentA : word;			{umístění okna A}
			SegmentB : word;
			Funkce : pointer;			{volání funkce pro okna}
			BPL : word;				{bytů na 1 scan řádku}
			{od VESA 1.2 a výše:}
			X,Y : word;				{rozlišení módu}
			zX,zY : byte			{rozměry fontu pro text.režimy}
			Rovin : byte;			{počet bitových rovin}
			bPP : byte;				{bitů na pixel, div 8 = BPP}
			Banku : byte;			{počet banků na celý režim}
			Model : byte;			{paměťový model}
			VelBanku : word;			{velikost banku}
			Stranek : byte;			{počet stránek obrazu}
			Rezerva : byte;			{=1}
			{informace o bitech v HC/TC/256C a YUV}
			Rvel,Rpoz : byte;			{velikost masky pro R/G/B}
			Gvel,Gpoz : byte;			{a její pozice v 16/24/32 bit.údaji}
			Gvel,Bpoz : byte;
			Dvel,Dpoz : byte;			{totéž u přímé barvě, resp. rezervě}
			Dinfo : byte;			{vlastnosti této rezervy}
			{od VESA 2.0 a výše}
			LFB : longint;			{fyzická adresa LFB bufferu}
			Rezerva1 : longint;		{=0}
			Rezerva2 : word;			{=0}
			{od VESA 3.0 a výše}
			BLPSL : word;			{bytů na scan řádku v lineárním módu}
			BI : byte;				{počet obrazů pro bankové režimy}
			LI : byte;				{a pro L režimy}
			LRvel,LRpoz : byte;		{totéž jako bez L, ale pro L režimy}
			LGvel,LGpoz : byte;
			LGvel,LBpoz : byte;
			LDvel,LDpoz : byte;
			MaxHodiny : longint;		{maximální pix.frekvence pro režim}
		 end;  

Velikost bloku je 256 bytů, od verze 2.0 je velký 512 bytů. Všechny řetězce jsou zakončené nulou (tedy ANSI, nebo-li ASCIIZ). Co se týká možností karty, tak nás hlavně zajímá, zda je bit0 = 1. Pokud ano, je možné přepnout DAC do 8 bitového módu, kdy máte max.hodnotu složky 255 a ne 63 jako doposud. Umožní to lepší rozlišení barev, a při R=G=B také 4x více odstínů šedi. Při inicializaci módu je vždy 6 bitový. Pokud jej přepneme na 8 bitový, měli bychom to před skončením práce zase vrátit. A bit1, pokud je 1, udává, že nemůžeme programovat VGA a DAC registry přímo, ale jedině přes INTy. Bit2 = 1 určuje, zda musíme při zápisu do VRAM nebo hlavně při programování RAMDACu čekat na zpětný paprsek. Pokud musíme, tak při programování palety přes funkci 9 musíme zapnout bit s hodnotou 80h. Ukazatel na mód nás zajímá, protože obsahuje tabulku 16 bitových hodnot režimů, na které se karta může přepnout. Tabulka je zakončena hodnotou $ffff. Karta vrací celkovou velikost VRAM (v 16K blocích), ale ta nemusí být vždy dostupná.

Velikost bloku o módu je vždy 256 bytů. Atributy nám opět poskytují nějaké ty informace. Pokud je b0 = 0, mód není podporován a tedy by ani neměl být v tabulce. b4 = 1 grafický režim, b3 = 1 je barevný, d5 = 0 je VGA kompatibilní, d6 = 0 používá stejné okno jako VGA režim (resp. podporuje také bank-switch (BS, přepínání banků), d7=1 znamená, že můžeme používat jen/také LFB. Pokud je b8=1, můžeme z tohoto režimu udělat např. 320x200 nebo 640x200, atd. Mezi další bity patří např. i podpora virtuálních 3D brýlí. Počet bytů na scan řádku se nemusí rovnat X*BPP. Scan řádka se totiž může změnit a i když nebude vidět na obrazovce, můžete do ní psát (výhled se dá případně posunout - toho se využívá na Triple Buffer (naše virtuální obrazovka je vlastně Double Buffer), kdy zobrazujete jinou část VRAM, než do které právě zapisujete), ale hlavně se díky ní posunou hranice banků, což nám umožní trochu optimalizovat (viz. dále). Atributy oken: b1=1 znamená, že z okna lze číst, b2=1 je, že jde (také) zapisovat. Granularita určuje, na jaký násobek paměti může být okno umístěno v paměti (v kB!). Standardně se jedná o 64kB nebo o 4kB. Používá se u BS režimů. Velikost okna může být jiná. Můžete např. umístit 64K velké okno na 16K, pokud má granularitu = 4 kB. Segment oken je adresa $a000 nebo $b000. Je-li zde 0, BS není podporován a tedy musíte být v PM a používat LFB (v TP7 na to zapomeňte: použijte BP nebo FP). Vstupní bod funkce pro okna nám umožňuje rychleji přepínat banky než by to šlo přes INT. Rozlišení je vždy nastaveno např. na 800x600, atd. Velikost znaků je standardně 9x14 (začíná od 1). Počet rovin pro 256 barev je 1, pro 16 to jsou 4. Naštěstí se 16 barev moc nepoužívá. Bitů na pixel je 4, 8, 15, 16, 24 či 32. Paměťový model je většinou 0 (text) nebo 5 (256C VGA), 6 (RGB, HC) či 7 (pro milovníky YUV). Od verze 1.1 není mezi 15 a 16 bity rozdíl (alespoň ne tady). Počet banků bývá pro VGA karty 1 (nepleťte si to prosím s BS banky). Počet obrazů určuje, kolik celých obrazovek-1 se vejde do VRAM (zde se jedná o banky pro BS, ale i pro LFB). Pozice RGB a jejich velikosti se hodí pro zjištění, ve kterém módu jsme (v YUV režimech platí, že Y=G, U=B a V=R). Např. pro 15 bitů platí, že velikosti jsou 5,5,5,1 a pozice 0,5,10,15. Pro 16 bitů platí že to bude 5,6,5,0 a pozice 0,5,11,0. Pro 32 bitové režimy 8,8,8,8 a pozice 0,8,16,24, atd. Přímá barva se může používat pro řízení vyhledávacích tabulek RGB. Adresa LFB je potřeba, pokud do něj chceme v PM psát. Může mít jinou logickou délku řádků než BS, protože nemusíme plýtvat pamětí na zarovnání banků. Následuje informace, kolik banků je potřeba pro celou VRAM. To si můžeme také spočítat jako LogX*Y*BPP div VelBanku (většinou VelBanku=Granularita=64K).

Tím jsme získali všechny informace. Nejjednodušší je číst postupně tabulku režimů a pro každý z nich si zjistit základní informace (X,Y,BPP, pozice a velikosti RGB, vel.okna a granularita, info o LFB/BS) do nějaké své tabulky. V ní poté můžeme najít požadovaný režim, kde jen porovnáme, zda X,Y a BPP jsou shodné s tím, co chceme najít. Když známe režim, můžeme se do něj přepnout (toto může také sloužit jako test na přítomnost VESA):


procedure InitVESA(rezim : word); assembler;
asm  
	mov	ax,$4f02
	mov	bx,rezim
	int	10h  
end;  

A můžeme opět testovat AX=$4f. Pokud chceme změnit frekvenci, musíme nastavit bit 11 a do ES:DI nastavit tabulku. Ale s tím Vás nebudu v tomto článku zatěžovat. Nás bude zajímat ovládání bankových oken. To je důležité, protože v TP7 nemáme LFB a nějak se do celé VRAM dostat musíme. Pro zjednodušení budu předpokládat, že okno A je RW (nebo alespoň W) a tedy budu používat jen toto.


procedure PoziceOkna(Jednotky : word); assembler;
asm  
	xor	bx,bx
	mov	ax,$4f05
	mov	dx,jednotky
	int	10h  
end;  

Díky tomu, že máme ale zjištěný vstupní bod ovládací procedury pro okna, můžeme využít mnohem rychlejší volání funkce:


procedure PoziceOkna(Jednotky : word); assembler;
asm  
	xor	bx,bx
	mov	dx,jednotky
	{$F+}
	call	mode.funkce
	{$F-}  
end;  

Pozici okna vypočteme jako X*Y*BPP div Granularita (nebo někdy i div VelOkna). Od dané pozice (Pozice*Granularita) můžeme využít až VelOkna Kbytů, pak budeme muset okno zase posunout. Např. v režimu 640x480x256 končí první bank při velikosti 64K (a pro zjednodušení také budeme brát i Granularitu=64K) na 102. řádku, 256. pixelu (65536/640=102.4). Je to dost blbé číslo, pro jehož výpočet budeme potřebovat DIV, takže nás to trochu zpomalí (až se naučíme nastavovat délku řádku, budeme moci používat SHR). My o vlku a vlk za dveřmi. Funkce (v tomto případě vlastně procedura) na nastavení délky řádku je následující:


procedure NastavLogR(Delka : word); assembler;
asm  
	mov	ax,$4f06
	xor	bl,bl
	mov	cx,delka
	int	10h  
end;  

Zde zadáváme délku v pixelech. Tedy stav po initu režimu je stejný, jako kdybychom provedli Set(X). Pokud bychom do BL dali 2 namísto 0, budeme nastavovat délku v bytech (tedy X*BPP), ale proč si to komplikovat. Pokud nastavíme pro 640x480 logickou délku řádku na 1024, vyjde nám, že na 1 bank připadá přesně 64 řádků. Spotřebujeme sice 480 kB namísto 300, ale za cenu, že výpočet čísla banku bude 10-20x rychlejší (co se týče té jedné instrukce). Funkce pro první případ jsou tyto:


function CisloBanku(X,Y : word; BPP : byte) : word;
begin
 CisloBanku := longint(X)*longint(Y)*longint(BPP) div Granularita;
end;


function Offset(X,Y : word; BPP : byte) : word;
begin
 Offset := longint(X)*longint(Y)*longint(BPP) mod Granularita;
end;  

Abychom zde neměli dělení, přepíšeme toto pro instrukce AND a SHR (příklad je pro 64K; pro 16K dosaďte čísla 14 a 16383; pro 4K to bude 12 a 4095). Asi se divíte, k čemu nám je tedy nastavování logického řádku, když jsme stejně už instrukci DIV vyhodili. To uvidíte.


function CisloBanku(X,Y : word; BPP : byte) : word;
begin
 CisloBanku := longint(X)*longint(Y)*longint(BPP) shr 16;
end;


function Offset(X,Y : word; BPP : byte) : word;
begin
 Offset := longint(X)*longint(Y)*longint(BPP) and 65535;
end;  

Nyní musíme na každý pixel totiž provádět 3 operace násobení. A přitom je to vlastně zbytečné. Pokud nastavíme délku řádku na 1024, tak ji vlastně můžeme vzít už jako určitý údaj o banku. Pokud je bank velký 64K, tak vlastně obsahuje 64 řádků. Tím pádem vydělením řádku hodnotou 64 dostáváme už i číslo banku. Zjednodušíme si to tím, že budeme dělit ne počtem 1024 pixelů, ale 1024 bytů. Pro BPP=1 je to samozřejmě totéž, ale pro vyšší už se to liší a museli bychom to BPP tam započítávat (přepočet si můžeme udělat dopředu a to jen 1x). Zároveň můžeme tedy vypočítat i offset:


procedure InfoPixel(X,Y : word; var Bank,Ofset : word);
begin
 Bank := Y shr 6;
 Ofset := ((Y - Bank shl 6) shl 10 + X);
end;  

Kdybychom čistě teoreticky měli BPP=2, či 3 nebo 4, tak výpočet upravíme následovně (délka logického řádku v bytech (!) bude 2048 pro 2 a 3, a 4096 pro 4):


procedure InfoPixel2(X,Y : word; var Bank,Ofset : word);
begin
 Bank := Y shr 5;
 Ofset := ((Y - Bank shl 5) shl 11 + X shl 1);
end;


procedure InfoPixel3(X,Y : word; var Bank,Ofset : word);
begin
 Bank := Y shr 5;
 Ofset := ((Y - Bank shl 5) shl 11 + X shl 1 + X);
end;


procedure InfoPixel4(X,Y : word; var Bank,Ofset : word);
begin
 Bank := Y shr 4;
 Ofset := ((Y - Bank shl 4) shl 11 + X shl 2);
end;  

Tyto hodnoty nás budou zajímat, když budeme zapisovat do naší VVRAM, abychom věděli, který bank si budeme chtít zpřístupnit (jejich správu provedeme později). A jak naši VRAM vykreslit?

Můžeme samozřejmě provádět to, že vždy vykreslíme něco do našeho grafického bufferu (VVRAM) a poté jej celý zapíšeme do VRAM (zde je situace snadná, neboť vždy přeneseme VelOkna a pak zvýšíme ukazatel na bank a ten přepneme). To je ale zbytečné plýtvání, zvláště, když bychom zapsali třeba jen jediný pixel na poslední řádek bufferu. Proč pak přenášet i ty ostatní? Řešením je, že si budeme udržovat informaci o tom, které banky jsme měnili. Vytvoříme si tabulku v tomto stylu (může být samozřejmě bitová, ale pak její obsluha trvá o něco déle):


var	Banky : array[0..Banku-1] of boolean;  

Bez změny scan řádku by banků bylo 5 (poslední by nebyl využit celý), se změnou jich bude 8. Po každém vykreslení (a na začátku programu) vymažeme tabulku na 0 pomocí:


FillChar(Banky,SizeOf(Banky),0);  

Vždy, když zapíšeme nějaká data do naší VRAM (ať už se jedná o pixel, řádek, blok dat), zapíšeme jeho bank do naší tabulky pomocí:


Banky[Bank] := True;  

Pak před každým kreslením provedeme zjištění, které banky kreslit máme a které ne. Na to potřebujeme další tabulku s položkami typu BYTE nebo raději WORD:


var	Kreslit : array[0..Banku-1] of word;
	ZmBanku : word;  

Funkce, která nám zjistí, které banky se kreslit budou bude pak vypadat následovně:


procedure ZjistiBanky;
var i : word;
begin
 ZmBanku := 0;
 for i := 0 to High(Banky) do
  if Banky[i] then
  begin
   Kreslit[ZmBanku] := i;
   Inc(ZmBanku);
  end;
 FillChar(Banky,SizeOf(Banky),False);
end;  

Vykreslení naší VVRAM pak proběhne nějak takto:


procedure VykresliVVRAM;
var i : word;
begin
 ZjistiBanky;
 if ZmBanku = 0 then Exit;
 for i := 0 to ZmBanku-1 do
 begin
  PoziceOkna(Kreslit[i]);
  ZpetnyBeh;
  ;zde přesuneme obsah Banku z VVRAM do VRAM
 end;
end;  

Můžeme optimalizovat ještě dál a zjistit si pro každý bank i rozsah řádků (Y1,Y2, pro 256C to bude 0-63), a přenášet do paměti jen je. Pak to samozřejmě chce další tabulku, kde budou dvě hodnoty WORD pro každý bank (na začátku je nutné tu první nastavit na $ffff a tu druhou na 0), a při každém zápisu dat se musí Y dat porovnat s oběma hodnotami. Pokud je menší než ta první, zapíše se do první. Pokud je větší než ta druhá (je nutné vypočítat i konec dat pokud zapisujeme více než 1 řádek najednou), zapíše se do ní. Funkce VykresliRAM bude vypadat úplně stejně, jen ta část, která přesunuje data, nepřesune celých 64K, ale jen rozsah řádků, mezi kterými se něco změnilo. I to urychlí vykreslování o docela dost (i když to zpomalí zápis dat do VVRAM - alespoň o důvod víc nezapisovat po jednom pixelu)!

Dobrá, vyřešili jsme problémy 1 a 2. Ale kam uložit naši VVRAM? Pod RM (reálný mód) využijeme služby správce paměti vysoko nad 1. MB (HIMEM.SYS popř. EMM386; i ve Windows). Jsou dvě možnosti, jak získat někdy až 64 MB RAM i pro DOSové aplikace. Buď použijeme EMS nebo XMS. Nebudu zastírat, že mým oblíbencem je spíše XMS než EMS. Takže EMS jen zmíním, ale dále už budu používat jen XMS.

EMS funguje podobně jako XMS. EMS má data rozdělena na tzv. stránky. Každá z ních je velká 16 kB. To je také minimální velikost, jakou můžete alokovat. Systém funguje na principu, že si zjistíte adresu, přes kterou správce EMS přenáší data (ta je v dolní/konvenční paměti). Pak si musíte alokovat určitý počet stránek pro Vaše data, do kterých (nebo z kterých) budete "přenášet". Před skončením práce s programem musíte také stránky uvolnit. Mezi prostorem v konvenční paměti a Vaší pamětí (v našem případě to bude $a000 pro VRAM) přenášíte data přes standardní funkci TP7:


Move(Prostor^,Mem[$a000:0],16384);  

Pokud chcete data jen číst bez toho, abyste je posílali do VRAM, nebo je naopak chcete zapisovat, použijete toto:


Move(VasProstor^,Prostor^,16384);
Move(Prostor^,VasProstor^,16384);  

Ukazatele jsou následující:


var	Prostor : pointer;		{buffer pro 4 stránky EMS od správce}
	VasProstor : pointer;		{prostor na haldě přes GetMem}  

Náš prostor si vytvoříme následovně (a při skončení programu jej také musíme uvolnit přes FREEMEM):


GetMem(VasProstor,16384);  

To je také velikost jedné stránky EMS. Tedy, pokud budeme chtít přenášet data z naší VVRAM do VRAM po celých 64K, musíme provést přenos na čtyřikrát s tím, že offset ve VRAM vždy zvýšíme o 16384. Samozřejmě je možnost, že bychom nepoužívali náš prostor a pracovali jen s tím, který nám nabízí EMS, ale to přináší problémy, o kterých se zmíním dále. Informace o tom, kde jsou stránky EMS nám sdělí správce:


function ZjistiProstor : pointer; assembler;
asm  
	mov	ah,41h
	mov	dx,bx
	xor	ax,ax
	int	67h  
end;  

Zbývá jen podotknout, že správce EMS obsazuje přerušení $67. Stránky jsou v segmentu, který nám právě vrátil vždy na offsetech $4000*Stranka od 0. Před tím, než ho použijete, je samozřejmě vhodné si zjistit, zda je vůbec přítomen:


function JeEMS : boolean; assembler;
asm  
	xor	bx,bx
	xor	ax,ax
	mov	es,ax
	les	di,es:[019ch]
	mov	di,0ah
	push	cs
	pop	ds
	lea	si,@ems
	mov	cx,8
	repz	cmpsb
	jnz	@konec
	mov	bx,1
@konec:
	mov	al,bl
	ret
@ems:
db	'EMMXXXX0',0  
end;  

Nebo to také můžete provést přes test, zda jde soubor EMMXXXX0 otevřít a pokud ano, zda jde zjistit jeho výstupní stav. Ale jak říkám, nemám to rád. Paměť alokujete a následně zase uvolníme těmito dvěmi funkcemi (první Vám vrátí číslo handle; zadáváte jí počet 16K stránek):


function AlokujEMS(stranek : word) : word; assembler;
asm  
	mov	bx,stranek
	mov	ah,43h
	int	67h
	mov	ax,dx  
end;


procedure ZrusEMS(handle : word); assembler;
asm  
	mov	ah,45h
	mov	dx,handle
	int	67h  
end;  

Aby mohla první funkce správně pracovat, musíte vědět, kolik stránek si můžete dovolit alokovat. Počet volných Vám zjistí tato funkce:


function Stranek : word; assembler;
asm  
	mov	ah,42h
	int	67h
	mov	ax,bx  
end;  

K datům v EMS paměti se dostanete tak, že příslušnou stránku z Vašeho bloku namapujete na jednu ze 4 fyzických stránek v bufferu EMS. To se provádí touto funkcí:


procedure Mapuj(fyz_str : byte; handle,blok : word); assembler;
asm  
	mov	ah,44h
	mov	al,fyz_str
	mov	dx,handle
	mov	bx,blok
	int	67h  
end;  

Stačí jen dosadit Vaše handle, číslo fyzické stránky (0-3) a číslo 16K bloku (od 0) ve Vašem handle (musí být menší nebo rovnu skutečnému počtu alokovaných bloků). Jenže ty 4 fyzické stránky jsou společné všem aplikacím. Jinými slovy, pokud jste rezident, máte problém. Pokud pracujete pod TP7 a využíváte EMS, máte problém. Musíte vždy uložit stav mapování, pak si přečíst data a pak to zase vrátit. Existují pravdědpodobně i elegantnější funkce, všechno je to popsané v ATHelpu, ale jak říkám, nemám EMS rád. Jednak ho moc neznám, jednak se mi zdá pomalejší a i když ne o moc více než u XMS, správa dat uložených v EMS je trochu krkolomná.

Zkusíme tedy už standard XMS. Je jednodušší než EMS a také se s ním lépe pracuje. XMS nepracuje na systému mapování bloků paměti, ale na tom, že data přenáší. Výhodou je, že nemáte sdílený žádný buffer s jinými aplikacemi. Stačí Vám jen vytvořit si Váš vlastní:


GetMem(VasProstor,32768);  

Vytvořil jsem jej 32 kB, abychom mohli přenášet data do/z XMS efektivněji. Do VRAM je můžeme přenášet přímo. XMS pracuje stejně jako EMS s tzv. handle. Ale narozdíl od EMS nemusíme stále volat INT. Při první incializaci XMS je nám sdělena adresa, kterou budeme už následně volat:


var XMSsluzby : pointer;
    _handle_ : word;


function XMS : boolean; assembler;
asm  
	xor	dx,dx			{je to rychlejší než 8 bit operace}
	mov	ax,4300h		{je XMS přítomno? funkce multiplexu}
	int	2fh
	cmp	al,80h
	jnz	@nenixms
	mov	ax,4310h		{jaký je vstupní bod?}
	int	2fh
	mov	XMSsluzby.word,bx
	mov	XMSsluzby+2.word,es
	jmp	@konec
	mov	dx,1
@konec:
	mov	ax,dx  
end;  

Od teď už všechny funkce probíhají voláním daného ukazatele. Abychom věděli, kolik kB paměti můžeme alokovat, máme zde tuto funkci (je univerzální, takže buď vrací kolik kB XMS je celkově volné (na začátku obvykle 64 MB) pro True, nebo pro False vrací největší blok, který lze alokovat najednou pro 1 handle (obvykle 2 MB)):


function XMSvolno(Celkem : boolean) : word; assembler;
asm  
	mov	ah,8
	call dword ptr [XMSsluzby]
	cmp	celkem,true
	jne	@konec
	mov	ax,dx
@konec:  
end;  

Zde máme další dvě funkce, které získají nebo uvolní paměť v XMS. Velikost se zadává v kB. Tu první si upravíme tak, aby v případě chyby vracela handle 0. Jinak je to většinou nějaké číslo typu WORD (více o tomto viz. ATHelp):


function AlokujXMS(Velikost : word) : word; assembler;
asm  
	mov	ah,9
	mov	dx,velikost
	call dword ptr [XMSsluzby]		{totéž jako FAR volání}
	cmp	ax,1
	je	@konec
	xor	dx,dx
@konec:
	mov	ax,dx  
end;


function UvolniXMS(Handle : word) : boolean; assembler;
asm  
	mov	ah,$a
	mov	dx,handle
	call dword ptr [XMSsluzby]
	cmp	ax,1
	je	@konec
	xor	ax,ax
 @konec:  
end;  

Fajn, paměť máme, ale jak s ní budeme pracovat? Budeme potřebovat funkci, která nám z ukazatele vytvoří typ longint, protože ten je využíván v nastavení XMS přenosů:


function Ptr2Long(Ukazatel : pointer) : longint; assembler;
asm  
	mov	ax,ukazatel.word[0]
	mov	dx,ukazatel.word[2]  
end;  

Následně si musíme vytvořit pro každý přenos strukturu, která bude udávat, co, kam a kolik se bude kopírovat. Využijeme schopnosti XMS a vytvoříme si rovnou 3 popisovače, které nám budou velmi prospěšné:


type	XMSinfo = record
			Vel : longint;		{kolik bytů (!) se bude přenášet}
			ZdrHandle : word;		{odkud se přenášejí data}
			ZdjOffs : longint;	{pozice v daných datech}
			CilHandle : word;		{kam se budou data přenášet}
			CilOffs : longint;
		    end;


var	doXMS, zXMS, doVRAM : XMSinfo;  

Teď následuje funkce, která se postará o přenesení dat kterýmkoliv směrem:


function PresunXMS(var XMSstruktura : XMSinfo) : boolean; assembler;
asm  
	push	ds
	mov	ah,$b
	lds	si,XMSstruktura
	call dword ptr [XMSsluzby]
	cmp	ax,1
	je	@konec
	xor	ax,ax
@konec:
	pop	ds  
end;  

Této funkci předáváme výše definovanou strukturu. O její naplnění se stará několik funkcí, které jsou už čistě Pascalovské. Zbývá jen podotknout, že všechny údaje jsou v bytech, a že handle Vám sdělí funkce pro alokování XMS. Zvláštní číslo 0 je vyhrazeno konvenční paměti nebo tomu, co začíná od adresy $a000:


procedure NastavKPresun(var _XMS : XMSinfo; Velik : longint;
				Zdroj : word; ZdrojOfs : longint;
				Cil : word; CilOfs : longint);
begin
 with _XMS do
 begin
  Vel := Velik;
  ZdrojHandle := Zdroj;
  ZdrojOffs := ZdrojOfs;
  CilHandle := Cil;
  CilOffs := CilOfs;
 end;
end;  

Rovnou ji použijeme, abychom nastavili výchozí údaje pro naše přenosy. Popisovače zXMS a doXMS budou složit k výměně dat mezi naším bufferem a XMS, popisovač doVRAM přenese data z XMS přímo do video karty (tam jsme rovnou nastavili velikost celého banku):


NastavKpresun(doXMS,0,Ptr2Long(VasProstor),0,_handle_,0);
NastavKpresun(zXMS,0,_handle_,0,Ptr2Long(VasProstor),0);
NastavKpresun(doVRAM,65536,_handle_,0,Ptr2Long(Ptr($a000,0)),0);  

Nyní, když budeme chtít něco přenést, tak jen změníme nutné údaje. Můžeme si tedy napsat už i příslušné, přesně na míru šité funkce, které nám provedou vše potřebné (co se týče konvenčí paměti, tak budeme vždy přenášet data na dolní hranici, tedy offset = 0, abychom je v něm nemuseli hledat):


function UlozDoXMS(Pocet,Ofset : longint) : boolean;
begin
 doXMS.Vel := Pocet;
 doXMS.CilOffs := Ofset;
 UlozDoXMS := PresunXMS(doXMS);
end;


function CtiXMS(Pocet,Ofset : longint) : boolean;
begin
 zXMS.Vel := Pocet;
 zXMS.ZdrojOffs := Ofset;
 CtiXMS := PresunXMS(zXMS);
end;


function ZobrazXMS(Ofset : longint) : boolean;
begin
 doVRAM.ZdrojOffs := Ofset;
 ZobrazXMS := PresunXMS(doVRAM);
end;  

Poslední funkci můžete napsat na řádek ve VykresliVVRAM, kde je komentář. Zbývá jen podotknout, že pokud pracujete pod DOSem a používáte HIMEM.SYS, přenášejte vždy sudou délku bloku. Přenášet např. jen 1 byte sice jde, ale vyjde Vám nesmysl. Stejně tak 3,5,7,atd. Offset ve Vaší VVRAM zjistíte tímto výpočtem (ovšem musíte ji mít stejně velkou, jako je VRAM, tedy včetně logické délky řádku nastavené na 1024, jinak musíte použít jiný výpočet):


function OffsetXMS(X,Y : word) : longint;
begin
 OfsetXMS := Y shl 10 + X;
end;  

Předpkládám, že si danou funkci už optimalizujete podle toho, co budete chtít zobrazovat (tady jsem bral 640x480x8b). Pokud chcete zjistit offset nějakého 64K velkého banku, použijte toto:


function OffsetBanku(Cislo : word) : longint;
begin
 OfsetXMS := Cislo shl 16;
end;  

Zde se už vyplácí naše logická délka řádku, jinak bychom zde museli násobit X*Y a BPP, a pak to dělit 64K. Nyní, pokud chcete poslat do XMS např. jeden pixel s BPP=1, uložte jeho BYTE do VasProstor^ a zavolejte funkci UlozDoXMS s (1,OffsetXMS(X,Y,1)); Když budete číst, najdete ho opět na Mem[Seg:(VasProstor^):Ofs(VasProstor^)].


procedure Pixel(X,Y : word; Barva : byte);
begin							{bude zlobit pod čistým DOSem s HIMEM.SYS}
 byte(VasProstor^) := Barva;			{Pro DOS nutno přepsat: přečíst 2 pixely, přepsat 1}
 UlozXMS(OffsetXMS(X,Y),1);			{a pak zase odeslat 2 (sudý počet)}
 Banky[Y shr 6] := True;
end;


function CtiPixel(X,Y : word) : byte;
begin
 CtiXMS(OffsetXMS(X,Y),1);			{Pro čistý DOS přečíst 2 pixely z XMS}
 CtiPixel := byte(VasProstor^);
end;


procedure ZapisRadek(X,Y : word; Radek : array of byte);
begin
 Move(Radek,VasProstor^,High(Radek)-Low(Radek));
 if High(Radek)-Low(Radek)+X <= 640 then
  UlozXMS(OffsetXMS(X,Y),High(Radek)-Low(Radek))
   else UlozXMS(OffsetXMS(X,Y),640-X);
 Banky[Y shr 6] := True;
end;  

A pokud bychom chtěli použít vynechávací průhlednost (anglicky se tomu říká "transparency"; výraz "translucency" se většinou používá, pokud děláme % průnik dvou pixelů), stačí jednoduše daný pixel vynechat. Pokud zapisujeme celý řádek, musíme si ho nejprve načíst, abychom ho mohli posléze změnit (u řádku většinou předpokládám, že jeho první prvek má index roven 0).


procedure PruhPixel(X,Y : word; Barva : byte; Pruhl : byte);
begin
 if Barva <> Pruhl then
 begin
  byte(VasProstor^) := Barva;
  UlozXMS(OffsetXMS(X,Y),1);
 end;
end;


procedure ZapisPruhRadek(X,Y : word; Radek : array of byte; PruhBarva : byte);
var Pocet : integer;
    i : word;
begin
 if High(Radek)+X <= 640 then
  Pocet := High(Radek) else
   Pocet := 640-X;
 if Pocet > 0 then
 begin
  CtiXMS(OffsetXMS(X,Y),Pocet);
  for i := 0 to Pocet-1 do
   if Radek[i] <> PruhBarva then
    Mem[Seg(VasProstor^):Ofs(VasProstor^)+i] := Radek[i];
  UlozXMS(OffsetXMS(X,Y),Pocet);
 end;
end;  

Pokud chcete mít v paměti i obrázky, stačí, když si alokujete další XMS paměť a připravíte si 2 popisovače pro směr XMS->DOS a DOS->XMS. Obrázků můžete mít více, vždy budete měnit jen handle XMS, velikost a offset XMS. Obrázky můžete ukládat v 256 barvách (později samozřejmě i ve více BPP, a nemusí být ani stejné jako BPP VVRAM; konec konců, ani ta nemusí mít BPP shodné s VRAM, ale pak budete muset provádět převody a to zdržuje). Když budete chtít obrázek zobrazit, načtete ho po řádcích a tyto řádky umístíte do Vaší VVRAM. Až budete mít všechno, můžete VVRAM zobrazit (přenášet můžete i XMS<>XMS, aniž byste používali konvenční paměť, to pro případ, že byste např. chtěli vymazat VVRAM texturou pozadí, pokud se Vám budou na obrazovce hýbat jen sprity, můžete všech 307.200 bytů jednoduše zkopírovat z handle nějakého 640x480 (1024x480 pro logickou délku řádku = 1K; nebo to zapisovat po řádcích, pak stačí vždy jen 480x přenést 640 pixelů) obrázku přímo do VVRAM, tím ji vymažete a zároveň nakreslíte pozadí). Protože počet handle XMS je omezený, je vhodné alokovat třeba celý 2 MB velký blok XMS a obrázky ukládat do něj za sebe (vytvořte si tabulku, kde budete mít jméno (8 znaků (buď array of char nebo string[7] pro zarovnání dat na dword; nebo číslo 4 byty) a offset (4 byty) v daném handle; jméno='' bude označovat konec tabulky). A poznámka na závěr: do "průhledných" funkcí, které provádí zápis do VVRAM jsem nadával nic, co by aktualizovalo seznam změněných banků. To si prosím přidejte sami.

Jistě jste si všimli, že bude nutné pro každé rozlišení a každé BPP nutné napsat nové funkce. Ano, ale za cenu vyšší rychlosti, než kdyby funkce byly univerzální. Abyste je pak nemuseli neustále pomocí CASE OF vybírat (zvláště, je-li hodně kombinací), můžete použít procedurální typ a všechny funkce, které potřebujete pro zápis přeložíte s modelem FAR. Na začátku programu, resp. při změně rozlišení přiřadíte (pod FP musíte použít před názvem funkce na pravé straně znak @) procedurální proměnné jen ty funkce, které obsluhují dané rozlišení a během programu už je nemusíte testovat (jejich volání bude sice pomalejší, protože budou FAR, ale funkce na grafiku většinou stejně bývají v jiné jednotce než hlavní program a tedy, pokud nepoužíváte $G, leží v jiném segmentu a volají se stejně vzdáleně; každopádně, pokud podporujete 5 různých rozlišení a 4 různé BPP, nehledě na VESA "kompatibilnosti", jen test pomocí CASE či IF by trval déle, než provedení samotné procedury PutPixel):


var ZapisPixel : procedure(X,Y : word);


begin
 case Rozliseni of
  6 : case BPP of
       1 : ZapisPixel := ZapisPixel6x4x1;
       2 : ZapisPixel := ZapisPixel6x4x2;
       3 : ZapisPixel := ZapisPixel6x4x3;
      end;
  8 : case BPP of
       1 : ZapisPixel := ZapisPixel8x6x1;
       2 : ZapisPixel := ZapisPixel8x6x2;
       3 : ZapisPixel := ZapisPixel8x6x3;
      end;
 end;
end;  

No, já Vás to zatím nechám vstřebat a příště budeme pokračovat jinými BPP, s obrázky a animacemi, efekty a geometrií. Už teď je toho myslím na jeden díl seriálu docela dost...


*** POKRAČOVÁNÍ PŘÍŠTĚ ***

2006-11-30 | Martin Lux
Reklamy: