Na FreeHostingu Endora běží desítky tisíc webů. Přidejte se ještě dnes!
Vytvořit web zdarmaNa FreeHostingu Endora běží desítky tisíc webů. Přidejte se ještě dnes!
Vytvořit web zdarmaTak, mme tu druh dl o video reimech. Trochu jsem se odmlel, protoe jsem byl ln, nemocn a pak jsem si dlal drze svoje vci. Tentokrt pejdeme u na zajmavj st a to jsou grafick reimy na potach PC. Opt k tomu budeme vyuvat Turbo Pascal 7 a Free Pascal spolu s jejich assemblery JSA.
Pedem upozoruji, e se budu vnovat hlavn kartm VGA a vym. Pokud nkdo chcete programovat Hercules, EGA i CGA, a budete mt dost tst, kdy seenete na smetiti nejen grafick karty, ale i monitory, mete se podvat do ATHelpu, pop. do manul na Internet, kde najdete potebn informace. Stejn tak se nebudu vnovat 16 barevnm reimm (nap. $12). Ty jsou podobn 256 barevnm, jen s tm, e paleta m jen 16 poloek, pixel je indexovn 4 bity, tedy v jednom bytu jsou 2 (lev je MSN), jen zmna palety probh tak, jak jsem ji popsal u textovch reim (alespo myslm: pokud ne, tak je stejn jako u 256 barev). Protoe se navc zatm budeme dret jen relnho reimu (i kdy nkter pklady uku i ve Free Pascalu), nebudeme zatm brt reim LFB (ten a pozdji), ale pouze Bank Switch. Nebudu se tak vnovat nastavovn tzv. dvojitch reim (nkter nov karty je nepodporuj, a nejsou ani celkem tak moc poteba).
Nejprve si dme trochu t teorie. Grafick karta mapuje svj prostor na 64 kB pamti od adresy $a000 (pokud chcete vyut 128 kB najednou, a Vae karta nepotebuje nutn dv okna v reimu VESA pro zpis a ten zvl, a nemte pdavn aktivn adaptr typu Hercules, mete si pomoc CGC registru ($3ce,$3cf) slo 6 zapnout nastavenm bit 2-3 na 00 tento reim; 64 kB m kombinace 01, viz. ATHelp. Programovat jsme se je uili v minul sti tohoto serilu; pozor, nkter nov karty nejsou kompatibiln s VGA a neposkytuj pstup k VGA registrm a nkdy ani k DAC (tj. nenastavte paletu pes OUT, ale jedin pes instrukce INT). To ale probereme a v sekci VESA). Pokud se cel VRAM vejde v danm reimu do tohoto prostoru, mete jej adresovat stylem (Y*MaxX+X)*BPP, kde X,Y jsou souadnice pixelu, MaxX je maximln rozlien (nebo dlka logickho dku, pokud je jin ne dlka zobrazovan sti) a BPP je poet byt na pixel. Pokud m karta ernobl monitor, Vs to nemus zajmat. Pevod probhne automaticky v nm, resp. v D/A pevodnku karty. Mete samozejm zapnout pepoet palety na ernoblou (resp. Gray Scale) ped tm, ne ji zapete, ale je to celkem zbyten, nebo si ji mete i sami vypotat (ideln nastavte v kad barv sloky R=G=B a takto mete mt a 64 odstn edi a zbytek teba barevn).
BPP me bt 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 zabr vce pamti, a nkter karty i lpe eeno spe aplikace berou nejvy byte jako alfa kanl, nebo-li masku prhlednosti, kdy 0=pixel nen vidt (je vidt to, co je pod nm, co ve VRAM dost dobe nejde), 255=pixel je vidt cel). U reim s BPP>1 se do pamti VRAM zapisuj u pmo RGB sloky 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 nkter karty nezvldaj 24 bitov True Colory, nebo jednu z varianty HC, jin nap. zvldaj oboj, ale standardn si inicializuj 24 bit jako prvn. Muste tedy Vae aplikace pst co nejvce pizpsobiv. Pro BPP=1 se do VRAM zapisuje index barvy v palet. Paleta se nastavuje v PEL registrech ($3c6-9), kdy standardn plat, e je vznamnch pouze prvnch (dolnch) 6 bit v kadm ze t byt RGB (erven zelen modr). Pokud mte paletu s rozsahem 0-255, muste nejprve kadou sloku vydlit 4 (nebo provst SHR 2) bez zaokrouhlovn nahoru! Naopak, pi ten ji zase muste vynsobit.
Pokud se v danm reimu cel VRAM do standardnch 64 kB nevejde (co je u pro 640x400x256), muste pout v RM tzv. pepnn bank (v PM mete vyut i LFB, to probereme pozdji). Na toto radji pouvejte systm VESA, kter normalizuje obsluhu vych rozlien na vech kartch. Je s nm ale problm ve Windows XP, jeliko ovladae nkterch karet na VESA kalou a inicializuj ho chybn, tedy rozsah frekvenc monitoru je mimo meze. Muste znt tzv. granularitu karty, kter se bn pohybuje na 4kB, 16kB nebo 64 kB. To oznauje, kolik byt pamti se mapuje do naeho okna. Ideln je 64K, protoe pak sta pepnat mn bank a je to rychlej. Zpis a ten pixelu pak probh tak, e nejprve zjistte linern adresu podle ve uvedenho vztahu a pot provedete BANK := ADR DIV GRAN, a OFS := ADR MOD GRAN. Pokud je slo banku jin ne prv nastaven (to si muste udrovat sami; pokud zapisujete celou VRAM postupn, toto zjiovat nemuste, nebo vlastn banky zvyujete postupn), muste ho nejprve zmnit. Pot u jen vyuijete OFS jako adresu od bze $a000 a zapete 1-4 byt na 1 pixel podle BPP.
Je nutn podotknout jednu vc. ten z VRAM je o dost pomalej ne jej zpis, navc - VRAM je o dost pomalej ne normln RAM (pokud ovem nemte AGP kartu s kompletn sdlenou pamt, sdlen Z-Buffer nesta). Proto je vhodn si zavst tzv. virtuln obrazovku. To je normln pole ARRAY, kter m stejn rozmry jako VRAM. Nap. pro reim VGA sta udlat
TYPE TVRAM = ARRAY[0..199,0..319] OF BYTE. VAR VVRAM : TVRAM; VRAM : TVRAM absolute $a000:0;
Je ovem problm v tom, e TP7 ns omezuje v pamti pro jednu promnnou na necelch 64K (lpe eeno ns omezuje DOS na +/- 65520). Vimnte si, e sloupce jsou a druh index, tedy budeme adresovat pes [Y,X], protoe pam je linern, tedy zmna Y se mus projevit v adrese markantnji ne zmna X. Je ale jasn, e nap. pro 640x400 musme u udlat tuto promnnou:
VAR VVRAM : ARRAY[0..3,0..99,0..639] OF BYTE;
kde je prvn slo index banku. Nebo, aby se to lpe adresovalo, meme vytvoit nco takovho:
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 meme lpe adresovat dky (nemusme je dlit 4x), ale zase musme pomoc CASE rozhodnout podle dku o tom, kterou pouijeme promnnou. Problm je v tom, e Pascal ns zrove omez na velikost vech promnnch v datovm segmentu na 64K, take na nco takovho jako je ve meme zapomenout. Meme nap. vyut dynamick promnn, kter vytvome takto:
TYPE TVVRAM = ARRAY[0..99,0..639] OF BYTE; PTVVRAM = ^TVVRAM; VAR VVRAM : ARRAY[0..3] OF PTVVRAM;
Pokud prohodte prvn dva dky v bloku TYPE, TP7 to vyjmen tak povol. Pak musme pomoc GETMEM nebo NEW vytvoit na hald 4 bloky pamti a ped skonenm programu je zase dealokovat. Adresovat je budeme pes 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;
Vbr banku meme tak nahradit jednodum dkem:
VVRAM[Y DIV 100,Y-(Y DIV 100)*100,X]^ := BARVA;
Ale ten bude dost pravdpodobn pomalej ne nae CASE. Navc se dostvme k dalmu omezen. Velikost haldy je 640K, ale pro ns je vtinou dostupnch tak 300-500 kB (vyuijte MaxAvail a MemAvail funkce) dle potu DOSovch aplikac a sprvn nastavenho OS (resp. sprvce HIMEM a ostatnch). 640x400 m 256.000 byt (250 kB), ale u nap. HiColor by zabral 2x tolik!
Ve chrnnm reimu tyto pote odpadaj, tam mme pamti kolik jen chceme, zde si ale musme poradit pes pam XMS nebo EMS. Pro n ovem mus bt v pamti sprvce (HIMEM, DosBox nebo Windows). Oba dva formty nm umon alokovat pam nad 1.MB pamti, ovem s tm, e k nmu nemme pm pstup, a nememe v nm ani spoutt aplikace (do EMS je ale mon kd penet, prakticky do XMS taky). Funguje to tak, e si vytvome njakou dynamickou promnnou (cca. 32 kB) pro XMS (EMS vtinou pouv njak standardn okno se 4 strnkami po 16K, novj verze u funguj trochu lpe), alokujeme urit prostor (pro XMS v Kbytech (vtinou omezeno na 2048kB na 1 blok), pro EMS v 16K strnkch) a nyn u jen penme (kopie) pam mezi konvenn (doln) pamt a nam blokem v XMS nebo EMS (XMS nm vrac handle, podle kterho si vybrme, kam to chceme penst). Je nutn podotknout, e pod DOSem maj nkte sprvci (HIMEM.SYS jmenovit) problmy penet lich dlky bloku, take muste penet jen ky nsobky 2 byt. A ped skonenm programu muste Vae alokovan bloky samozejm uvolnit. Pokud plnujete, e si program jet spustte, mete si handle uloit na disk a po znovu sputn mete pes toto handle st neodalokovan bloky. Pokud ovem handle ztratte, zstane tato pam a do resetu potae nepstupn.
Je vhodn dost optimalizovat, tedy dlte pomoc SHR, nsobte pomoc SHL. Pixely nezapisujte po 1, ale pokud mono pes MOVSx (MOVE) po skupinch (idln pes cel dek, pokud ovem nevyuvte prhlednost). Do VRAM poslejte data z VVRAM pomoc 32 bitovch pesun, kdy v ASM napete MOVSW a dte ped ni prefix $66. Pokud potebujete dlit, zjistte 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 potebuje provst MOD se stejnm slem, mete to udlat jako AND (CISLO-1). Pro vy rychlost se tak doporuuje vyuvat pouze 32 bitov reimy a pokud mte pepnn bank, je vhodn nastavit si logickou dlku dku (viz. dle) tak, aby banky konily pesn na koncch dk a ne nkde uprosted. Pouvejte rozsah v mocninch dvou. Nap. pokud bychom vytvoili u pkladu se 4 banky VVRAM rozsahy v banku ne 0-99, ale 0-63, vytvoili bychom tchto bank 7 (alokujeme o 30.720 byt vce, ne vyuijeme. Kdybychom dali do banku jen 32 dk, tak bychom bank mli 13, ale zbyten bychom mli navc jen 10.240 byt; ovem do konena to tak nejde, protoe jednak m DOS omezen poet handle (samozejm, m jich dost i na stovky tchto blok, ale kad toto handle vyaduje njak prostor v pamti! XMS je navc mnohem omezenj, take je vhodn nauit se etit) a jednak je ppadn uvolovn a optovn alokace na hald trochu pomal) a pak bychom je mohli adresovat tmto stylem:
VVRAM[Y SHR 6,Y-(Y SHR 6) SHL 6,X]^ := BARVA;
Co je mnohem rychlej ne DIV a nsoben. Snate se tak nkter opakujc se vpoty provst pedem do pracovn promnn, aby se nemusely potat dvakrt. 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 naeho serilu potebovat (navc dynamickou pam a XMS pouijeme nkdy pt i pro zvukov karty), take se u meme konen pustit na praxi. Zaneme zase od tch nejjednoduch vc, nebo to je sprvn systm vuky na vechny mon vdy...
Zaneme tedy s praktickm uitm grafiky. Jen pipomnm, e vtina dle uvedench vc bude fungovat i ve Free Pascalu. Mla by tam fungovat i XMS pam, ale jej pouvn je ve FP zbyten, nebo si mete pes VAR nebo GETMEM naalokovat co hrdlo r a bude to jednodu a rychlej.
Zaneme jakoby od konce. Ukeme si, jak opustit grafick reim. Kdo jste zvykl na Graph jednotku v TP7, tak vte, e prakticky neexistuje nic jako oputn grafickho reimu. Pouze pejdete do reimu jinho. Ve vtin DOSovch aplikac se program vrac do textovho reimu 80x25, tedy nape nco v tomto stylu:
asm
mov ax,3 int 10h
end;
Zde jsme pouili peruen 16, resp. $10, kter obsluhuje BIOS primrn grafick karty (v ppad, e jich tam mte vce; sekundrn se obsluhuje pmo pes jej VRAM vtinou na $b800 nebo $b000 v textovm reimu). Pokud chceme pracovat s grafikou, zkusme nejprve to nejjednodu rozlien:
asm
mov ax,13h int 10h
end;
Tmto aktivujeme dve nejpouvanj rozlien pro hry: 320x200 v 256 barvch. Dnes vtina autor pouv mnohem vt rozlien, ale jaksi zapomn, e nejde o kvantitu, ale o kvalitu, a zbyten tedy zvyuj nroky na PC, akoliv samotn hra svm vzhledem spad do roku 123. A nezapomnejte na to, e $13 mus bt hexa (13 je 320x200 pro 16 barev). Pepnat reimy meme i tak, e nm video karta nesmae obrazovku (pokud o to stojme) a to tak, e k registru AX piteme konstantu $80. Take, jsme v grafice, a te co s n. Vtinou budeme asi zapisovat pixely. Vzhledem k tomu, jak je dlena pam, meme zapsat pixel takto:
Mem[$a000:Y*320+X] := BARVA;
Aby to bylo rychlej, meme to pepsat 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;
Pest 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 odpovdaj barvm EGA, ostatn jsou odstny dalch zkladnch barev. Mete si je zjistit, pokud si nechte 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 zmnit. Mme k tomu sluby peruen:
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;
Mnit ale barvu po jedn nen zrovna moc rychl. Natst mme slubu, kter to obstar za ns. Nm jen sta, kdy celou paletu ulome do 768 byt dlouhho pole RGB (v tomto poad!), kde kad poloka barvy bude mt hodnotu max. 63 (stle pouvme jen 6 bit):
var paleta : array[0..767] of byte;
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 zavolme jako _PALETA(@PALETA);. st paletu je samozejm tak mon:
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 vak mnohem rychlej zpsob a to nastavovn pes porty. Ten nemus bt u novjch karet ppustn, ale to probereme a u VESA md. Existuj tzv. PEL registry, kter pracuj s paletou. Pokud do prvnho zapete index barvy, tak do druhho mete zapsat postupn jej RGB hodnoty. Pokud chcete paletu zapsat, pouijte registr xx8, jinak pro ten zvolte xx7. Data jsou pak vdy na xxx9. VGA karta automaticky po obdren 3 data byt zv index barvy, take mete pokraovat v zpisu dal barvy (pokud jich chcete zapsat vc).
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 zmnme o jistm triku. Pokud trochu nco vte o HW video karty, tak vte, jakm zpsobem promt obraz na monitor. Pokud provedeme zmnu do obrazu bhem vykreslovn, meme dostat nepkn efekty nebo zrnn. Pro tento el je vhodn testovat, zda neprobh zptn bh paprsku. Pokud ano, meme kreslit:
procedure ZpetnyBeh; assembler; asm
mov dx,03dah @0: in al,dx test al,8 jnz @0
end;
Jakmile ns funkce pust, meme kreslit. Vhodnj je testovat nikoliv bit 4 v registru $3da, ale bit 7 v registru $3c2, nebo ten doke rozliit snmkov bh od kovho (dkov trv o mnoho krat dobu, navc nevme, zda u je ve vykreslen nebo jsme nkde u prosted):
procedure ZpetnyBeh; assembler; asm
mov dx,03c2h @0: in al,dx test al,128 jnz @0
end;
Nevhodou ve uvedenho pkladu je, e sice zjistme, e probh snmkov bh, ale u nezjistme, zda-li prv zaal, nebo zda-li je u na konci a tedy bychom radji kreslit nemli. To se e tak, e nejprve pokme, a ppadn paprsek skon (pokud u b), a pak teprve pokme, a se objev dal. I v nejhorm ppad ns to zdr jen na 1/60 vteiny (u modernch 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, ukeme si pr trik, tak dlat s obrazem zajmav efekty. Ty jsou rychlej ne u BPP>1 reim, protoe tady neplat, e m vt X*Y, tm pomalej operace. Jist znte, kdy se paleta u nkterch her ztmavila a do erna. Tomu se k FadeOut. FadeIn je, kdy se obraz naopak objev. Pak jsou efekty jet jako FadeFromGray, FadeToGray nebo FadeToWhite i FadeFromWhite, kdy clov/zdrojov barva obrazovky nen ern, ale bl, nebo se naopak obraz jakoby pevede do odstn edi. Mnohem efektnj je pak FadeToPal, kdy se paleta postupn zmn na jinou paletu. Podotknu, e pi kad zmn palety je vhodn nejprve pokat na paprsek. A kad zmna me probhnout ve Vmi stanovenm potu krok a kad tento krok me trvat urit as. Funkce na zpodn vypad njak takto:
procedure Cekej(Taktu : longint); var Start : longint; begin Start := MemL[0:$46c]; while MemL[0:$46c]-Start >= Taktu do; end;
Funkce na zmnu palety pak vypadaj takto (pro Free Pascal muste nahradit Port funkcemi OutPortB a tak nen nutn pouvat petypovn, nebo FP nem chybu pro peteen jako TP7). Pro zjednoduen jsem tam nepidal zpodn. To umstte ped zptn bh 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;
Pevod mezi edou a barevnou paletou je troku sloitj (a pozor, vnj cykl jde obrcen):
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 prolnn 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 nemusm snad podotkat, e mus platit KROKU <> 0. Sta na prvn dek procedury doplnit kontrolu a ppadn dt KROKU := 1;. Ped tm, ne zahjte FadeIn, pokud jste ped tm neprovedli FadeOut, muste nastavit paletu na ernou, pak vykreslit celou obrazovku a pak teprve zahjit FadeIn. Pro ernou paletu mete pout:
procedure BlackPal; var index : word; begin ZpetnyBeh; Port[$3c8] := 0; for index := 0 to 768-1 do Port[$3c9] := 0; end;
Nebo Vm sta maskovat registr palety:
procedure Black; begin Port[$3c6] := 0; end;
Pak ale nezapomete po prvnm cyklu ve FadeIn na tento port poslat zase 255, jinak nic neuvidte. Pro blou paletu (pokud chcete provdt FadeFromWhite bez toho, e jste nejprve provedli FadeToWhite (mete to pout pro vmnu obrzk, ale pokud tam jet dn nemte, je zbyten dlat Fade, leda byste to provedli na minimum krok, kde to stejn nikdo nepostehne)) pouijte toto:
procedure WhitePal; var index : word; begin ZpetnyBeh; Port[$3c8] := 0; for index := 0 to 768-1 do Port[$3c9] := 63; end;
Pokud potebujete i edou paletu, je mon to provst takto (je to jednodu varianta, kdy vytvote jen 64 odstn edi, kdy pro kadou barvu poleme 3x stejnou sloku R=G=B, ale 256/64 = 4, tedy celkem 12x stejn slo; existuje samozejm i vzoreek pro vech 256 odstn, 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 zajmav efekty pat zmna jasu nebo obarven palety. Zde provdme vpoty na palet v pamti (pedchoz funkce tuto paletu nemnily), take pokud by Vm to pilo nevhod, je dobr si paletu zkoprovat do njakho jinho pole pomoc:
Mem(zdroj,cil,sizeof(cil));
a pak pracovat na tomto poli. Podobn efekty budeme vyuvat i pi BPP>1. Tam se tak naume prhlednost. Vpoet prhlednosti v 256 barvch probh tak, e si zjiste RGB od tch dvou barev, kter chcete mixovat. Tak provedete mix jako pi TC (viz. pozdji), a podle novho RGB najdete nejbli barvu v palet. Je to velmi zdlouhav innost, proto se doporuuje pouvat bu vypotenou paletu nebo si pedpotat hodnoty pro kombinace pixel (nap. tak, e jim nejprve RGB sloky 4x zmente, tm dostanete jen 64/4 = 16 monost, 16*16 = 256 kombinac. Kdy si urte nap. 32 stup prhlednosti, dostanete 256*32 = 8 kB velkou tabulku. Pak u nemuste nic potat, sta kdy oba pixely vezmete jako indexy do tabulky - pokud tam budete mt sloky RGB, co je velmi pravdpodobn, zabere to 3x tolik byt).
Co se te tch efekt, tak vlastn jen obarvujeme sloky pixel, kdy 128 je beze zmny, 64 je 1/2 jas, a 255 je 2x takov jas. Idelnj je samozejm nahradit SHR 8 na SHR 4, a pak bude platit e 16 = 100% a na ob strany pak mte cca. 16x zvten/zmenen poloky (ovem bez mezistup pro zmenovn, nap. 1.5x u te nepjde, ale zase pjde 3x zvtit). Zmna jasu je pro zmny R=G=B, zmna 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 zmnit jen nkter pixely (pixel), jist bude pro Vs jednoduch upravit si tuto (a ppadn 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 opustme efekty a vrhneme na urychlen naeho kreslen. Kreslit po jednom pixelu je velice neefektivn a st z VRAM je u vbec na nic. Pro tento el si zavedeme virtuln obrazovku. To bude vlastn obyejn RAM, kter nm bude zastupovat skutenou VRAM. Sta nm jen upravit nkter 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 zjednoduen se zde vyhbm kontrolm, zda X <= 319, atd., ale je na Vs, zda je tam pidte a zpomalte tm vykreslovn, nebo zda si jednodue dte pozor na to, kam kreslte (jinak mete bt celkem pekvapeni: pokud je dlka logickho dku (viz. dle) = 320, tak pixel [320,0] = pixel [0,1]). Do tto pamti meme vesele kreslit a st z n, ani bychom museli ekat na paprsek. Teprve, a budeme mt celou obrazovku hotovou, meme ji poslat do video karty:
Move(VVRAM,Mem[$a000:0],SizeOf(VRAM));
Vymazat ji meme stejn jednodue (pokud budeme pedpokldat, e barva 0 je ern):
FillChar(VVRAM,SizeOf(VRAM),0);
Protoe Pascal je 16 bitov, tak je zobrazovn takov VRAM pomalej, ne by dnes mohlo bt. Kdy pepeme funkci MOVE na 32 bitovou, uetme tm cca. 40% asu (protoe penme po Dwordech, sta nm 16.000 cykl namsto 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 meme urychlit i vmaz obrazovky (a rovnou si jej pepeme na volitelnou barvu; do registru snad ES nic neukldat nemusme, nebo ten by se ml naplnit na DS dky tomu, e je standardn piazenm k offsetovmu 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 vlome 0, bude to standardn ern. Pokud tam vlome -1 (co je v dvojkovm doplku $ffffffff), bude to bl. Pokud tam dame cokoliv jinho, nap. $ff008000, dostaneme zajmav vzorek). Nebo to meme pepsat 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 dcch je vhodn, pokud chceme nco opakovat (u zpisu do VRAM by to bylo rychlej, ale protoe mme VVRAM, je zbyten zapisovat jeden dek najednou nejprve nkam a pak do VVRAM). Pokud nap. chceme vytvoit okno, kter nen prhledn, meme si do pracovnho bufferu zapsat na 1. a posledn pixel barvu rmeku a zbytek vymazat na barvu pozad. Pot u tento dek zapeme nkolikrt do buferu, ani bychom ho museli neustle skldat. Jednodu je ale pracovat pmo s VVRAM, take pomoc PIXEL nakreslme rmeek a pot moc FILL vymaeme uritou oblast (a budeme pracovat s XMS, je samozejm rychlej si dek pipravit pedem a pak zapsat jen dky, protoe se tm uet poet penos):
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 mli zkladn grafick reim. Vm to ale urit stait nebude. Zkusme pejt tedy na dal reim, nap. 640x480 (nebo 640x400, kter je pesn 2x vt ne 320x200, resp. zabr 4x tolik plochy a pamti VRAM). Zde se budeme muset poprat se spoustou novch problm. Za prv, 640x480 se nevejde do pamti 64kB, kter jsou vyhrazeny 1 oknu. Za druh, v zkladnch reimech nen 6x4 nikde popsn. Za tet, reim zabr 300 kB pamti pro VVRAM a nen ho tedy mon umstit do datovho segmentu Pascalu pes VAR, a i pes GETMEM by nm peci jen mohlo tch 300K chybt (pokud programujete ve Free Pascalu, bod 3 Vs nemus zajmat, nebo si mete takov pole klidn nadefinovat pes VAR VVRAM[0..479,0..639] OF BYTE a budete to mt jednodu; body 1 a 2 se Vs ale tkat budou - do doby, ne pejdeme na LFB).
Nejprve vyeme bod 2.
Ale pedem nkolik zajmavch informac. Protoe se nyn zadvaj mdy VESA jako word, pibylo nkolik bit, kter by Vs mohli zajmat. Bit8 (tedy 256) je vdy nastaven na 1 (tak se pozn, e to je VESA). Bit15 je to sam, jako bit7; pes bit11 si mete nastavit vlastn obnovovac frekvenci a nastavenm bitu14 na 1 si vyberete tzv. Linear Frame Buffer (LFB), kter je vak vyuiteln jen v chrnnm mdu (PM), nap. ve Free Pascalu. Pokud Vm funkce vrt v AH = 3, tak to mte dobe, ale pro dan reim ta funkce nedv smysl. Nen to tedy tak pln chyba. Nkter novj karty neum reimy 400x300 i 320x200. Ale te u na ten bod 2:
Vrobci video karet se kdysi dohodli, e aby nebyl nepodek ve vych reimech, zavede se jednotn tabulka. Vs jako programtora u tedy nemus zajmat, kter karta m kter rozlien pod kterm reimem (takov tabulka existuje a je pkn dlouh). Byl zaveden standard VESA. Pro nkolik zkladnch rozlien, kter obsahuj 640x480 a 1600x1200, a barvy od 1 a po 4 BPP, byla vytvoena tabulka. Bohuel, pro nov rozlien u tabulka nen, a navc, nkte vrobci se rozhodli ignorovat i tuto tabulku. Co z toho pro ns vyplv? Bu to riskneme a pouijeme slo reimu takkajc na tvrdo (v naem ppad 101h) a ono to na 97% vdy vyjde, nebo si musme zjistit, pod kterm reimem je dan rozlien s danou BPP (vnuje se tomu Laaca).
Jak na to? Jsou tu dv funkce VESA, kter zjiuj informace o samotnm VESA a pak navc jet o samotnch mdech. Funkce VESA vrac v AL kd $4f a v AH 0, pokud byla operace spn. Krom funkc uvdm i zkladn tabulku VESA reim. Neuvdm mezi n textov reimy (viz. ATHelp); potebnou pam spotte jako X*Y*BPP (16=0.5, 256=1, 32/64K=2, 16.8M=3-4):
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 vyuvat. V nich se dozvte docela zajmav odlinosti od standardnch reim. VESA toti zavd docela zajmav "podrazy". Akoliv velk vtina karet se chov stejn, jako je tomu v reimu 320x200 bez VESA, u nkterch lze oekvat, e velikost okna nejen nemus bt 64 kB, ale teba jen 4 kB (tedy se jich tam vejde nkde a 16), ale tak nemus bt okno R/W, tedy souasn pro zpis i ten. Mohou existovat dv okna, kter mohou bt RW, ale tak me bt jedno jen R (ten) a druh jen W (zpis). Mohou se nastavovat zvl a nemus leet vdy na $a000 (ale mohou a tedy se jakoby pekrvaj).
Ne pjdeme dl, ukeme si jet dv uiten funkce. Ta prvn mn poet bit v palet na sloku (8/6) a ta druh nm umouje mnit paletu na kart v ppad, e nm 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 pouv jin formt palety ne bez VESA:
var paleta : array[0..255] of record B,G,R : byte; {pozor! je to obrcen} zarovnani : byte; {nepouv se} end;
Jen doplnm, e instrukce LES/LDS lze pout jen na ta data, kter jsou definovna jako globln pes VAR (ne ve funkci) nebo jako VAR ve funkci (pedv se adresa). Na data, kter se do funkce pedvaj bez VAR (hodnota) nelze LES pout (ta vlastn dl jen to, e 4 byty z dan adresy umst do 2 registr po 2 bytech, take by vyla chybn adresa). Mon jsem nkde (u textovch reim) toto pouil chybn (LES na data bez VAR), tak mi to prosm odpuste...
Nejprve si nadefinujeme tabulky, kter nm tyto dv funkce napln (tabulky menily v prbhu vvoje VESA velikost, take nap. tabulka pro VESA3 je vt ne pro VESA a to je docela prvih):
var _vesa : tvesa;
_mode : tmode;
Definici jsme udlali pomoc uivatelskch typ. Jinak Vm samozejm nebrnm udlat tyto tabulky dynamick pes GETMEM. Vtinou je potebujete stejn jen 1x a to na zatku programu, kde si z nich vezmete ve potebn (a vtinou je toho zlomek), kter si ulote do svch spornjch tabulek. Tabulky jsou docela obshl a dozvte se toho z nich mnoho (pro vce informac doporuuji 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 etzec} Moznosti : longint; {co karta um} Mody : pointer; {ukazatel na seznam md} Pamet : word; {*16K dv od verze 2 velikost VRAM} Revize : word; {slo verze SW} Vyrobce : pointer; {ukazatel na nzev vrobce/prodejce} Produkt : pointer; {ukazatel na jmno vrobku} PRevize : pointer; {ukazatel na info o revizi (etzec)} Rezerva : array[0..221] of byte; _OEM : array[0..255] of char; {OEM etzce} end; _tmode = record Atributy : word; {mdu} AtributyA : byte; {okno A} AtributyB : byte; Granularita : word; {rozchod bank} Velikost : word; {velikost okna} SegmentA : word; {umstn okna A} SegmentB : word; Funkce : pointer; {voln funkce pro okna} BPL : word; {byt na 1 scan dku} {od VESA 1.2 a ve:} X,Y : word; {rozlien mdu} zX,zY : byte {rozmry fontu pro text.reimy} Rovin : byte; {poet bitovch rovin} bPP : byte; {bit na pixel, div 8 = BPP} Banku : byte; {poet bank na cel reim} Model : byte; {pamov model} VelBanku : word; {velikost banku} Stranek : byte; {poet strnek 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 pm barv, resp. rezerv} Dinfo : byte; {vlastnosti tto rezervy} {od VESA 2.0 a ve} LFB : longint; {fyzick adresa LFB bufferu} Rezerva1 : longint; {=0} Rezerva2 : word; {=0} {od VESA 3.0 a ve} BLPSL : word; {byt na scan dku v linernm mdu} BI : byte; {poet obraz pro bankov reimy} LI : byte; {a pro L reimy} LRvel,LRpoz : byte; {tot jako bez L, ale pro L reimy} LGvel,LGpoz : byte; LGvel,LBpoz : byte; LDvel,LDpoz : byte; MaxHodiny : longint; {maximln pix.frekvence pro reim} end;
Velikost bloku je 256 byt, od verze 2.0 je velk 512 byt. Vechny etzce jsou zakonen nulou (tedy ANSI, nebo-li ASCIIZ). Co se tk monost karty, tak ns hlavn zajm, zda je bit0 = 1. Pokud ano, je mon pepnout DAC do 8 bitovho mdu, kdy mte max.hodnotu sloky 255 a ne 63 jako doposud. Umon to lep rozlien barev, a pi R=G=B tak 4x vce odstn edi. Pi inicializaci mdu je vdy 6 bitov. Pokud jej pepneme na 8 bitov, mli bychom to ped skonenm prce zase vrtit. A bit1, pokud je 1, udv, e nememe programovat VGA a DAC registry pmo, ale jedin pes INTy. Bit2 = 1 uruje, zda musme pi zpisu do VRAM nebo hlavn pi programovn RAMDACu ekat na zptn paprsek. Pokud musme, tak pi programovn palety pes funkci 9 musme zapnout bit s hodnotou 80h. Ukazatel na md ns zajm, protoe obsahuje tabulku 16 bitovch hodnot reim, na kter se karta me pepnout. Tabulka je zakonena hodnotou $ffff. Karta vrac celkovou velikost VRAM (v 16K blocch), ale ta nemus bt vdy dostupn.
Velikost bloku o mdu je vdy 256 byt. Atributy nm opt poskytuj njak ty informace. Pokud je b0 = 0, md nen podporovn a tedy by ani neml bt v tabulce. b4 = 1 grafick reim, b3 = 1 je barevn, d5 = 0 je VGA kompatibiln, d6 = 0 pouv stejn okno jako VGA reim (resp. podporuje tak bank-switch (BS, pepnn bank), d7=1 znamen, e meme pouvat jen/tak LFB. Pokud je b8=1, meme z tohoto reimu udlat nap. 320x200 nebo 640x200, atd. Mezi dal bity pat nap. i podpora virtulnch 3D brl. Poet byt na scan dku se nemus rovnat X*BPP. Scan dka se toti me zmnit a i kdy nebude vidt na obrazovce, mete do n pst (vhled se d ppadn posunout - toho se vyuv na Triple Buffer (nae virtuln obrazovka je vlastn Double Buffer), kdy zobrazujete jinou st VRAM, ne do kter prv zapisujete), ale hlavn se dky n posunou hranice bank, co nm umon trochu optimalizovat (viz. dle). Atributy oken: b1=1 znamen, e z okna lze st, b2=1 je, e jde (tak) zapisovat. Granularita uruje, na jak nsobek pamti me bt okno umstno v pamti (v kB!). Standardn se jedn o 64kB nebo o 4kB. Pouv se u BS reim. Velikost okna me bt jin. Mete nap. umstit 64K velk okno na 16K, pokud m granularitu = 4 kB. Segment oken je adresa $a000 nebo $b000. Je-li zde 0, BS nen podporovn a tedy muste bt v PM a pouvat LFB (v TP7 na to zapomete: pouijte BP nebo FP). Vstupn bod funkce pro okna nm umouje rychleji pepnat banky ne by to lo pes INT. Rozlien je vdy nastaveno nap. na 800x600, atd. Velikost znak je standardn 9x14 (zan od 1). Poet rovin pro 256 barev je 1, pro 16 to jsou 4. Natst se 16 barev moc nepouv. Bit na pixel je 4, 8, 15, 16, 24 i 32. Pamov model je vtinou 0 (text) nebo 5 (256C VGA), 6 (RGB, HC) i 7 (pro milovnky YUV). Od verze 1.1 nen mezi 15 a 16 bity rozdl (alespo ne tady). Poet bank bv pro VGA karty 1 (neplete si to prosm s BS banky). Poet obraz uruje, kolik celch 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 zjitn, ve kterm mdu jsme (v YUV reimech 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 reimy 8,8,8,8 a pozice 0,8,16,24, atd. Pm barva se me pouvat pro zen vyhledvacch tabulek RGB. Adresa LFB je poteba, pokud do nj chceme v PM pst. Me mt jinou logickou dlku dk ne BS, protoe nemusme pltvat pamt na zarovnn bank. Nsleduje informace, kolik bank je poteba pro celou VRAM. To si meme tak spotat jako LogX*Y*BPP div VelBanku (vtinou VelBanku=Granularita=64K).
Tm jsme zskali vechny informace. Nejjednodu je st postupn tabulku reim a pro kad z nich si zjistit zkladn informace (X,Y,BPP, pozice a velikosti RGB, vel.okna a granularita, info o LFB/BS) do njak sv tabulky. V n pot meme najt poadovan reim, kde jen porovnme, zda X,Y a BPP jsou shodn s tm, co chceme najt. Kdy znme reim, meme se do nj pepnout (toto me tak slouit jako test na ptomnost VESA):
procedure InitVESA(rezim : word); assembler; asm
mov ax,$4f02 mov bx,rezim int 10h
end;
A meme opt testovat AX=$4f. Pokud chceme zmnit frekvenci, musme nastavit bit 11 a do ES:DI nastavit tabulku. Ale s tm Vs nebudu v tomto lnku zatovat. Ns bude zajmat ovldn bankovch oken. To je dleit, protoe v TP7 nemme LFB a njak se do cel VRAM dostat musme. Pro zjednoduen budu pedpokldat, e okno A je RW (nebo alespo W) a tedy budu pouvat jen toto.
procedure PoziceOkna(Jednotky : word); assembler; asm
xor bx,bx mov ax,$4f05 mov dx,jednotky int 10h
end;
Dky tomu, e mme ale zjitn vstupn bod ovldac procedury pro okna, meme vyut mnohem rychlej voln funkce:
procedure PoziceOkna(Jednotky : word); assembler; asm
xor bx,bx mov dx,jednotky {$F+} call mode.funkce {$F-}
end;
Pozici okna vypoteme jako X*Y*BPP div Granularita (nebo nkdy i div VelOkna). Od dan pozice (Pozice*Granularita) meme vyut a VelOkna Kbyt, pak budeme muset okno zase posunout. Nap. v reimu 640x480x256 kon prvn bank pi velikosti 64K (a pro zjednoduen tak budeme brt i Granularitu=64K) na 102. dku, 256. pixelu (65536/640=102.4). Je to dost blb slo, pro jeho vpoet budeme potebovat DIV, take ns to trochu zpomal (a se naume nastavovat dlku dku, budeme moci pouvat SHR). My o vlku a vlk za dvemi. Funkce (v tomto ppad vlastn procedura) na nastaven dlky dku je nsledujc:
procedure NastavLogR(Delka : word); assembler; asm
mov ax,$4f06 xor bl,bl mov cx,delka int 10h
end;
Zde zadvme dlku v pixelech. Tedy stav po initu reimu je stejn, jako kdybychom provedli Set(X). Pokud bychom do BL dali 2 namsto 0, budeme nastavovat dlku v bytech (tedy X*BPP), ale pro si to komplikovat. Pokud nastavme pro 640x480 logickou dlku dku na 1024, vyjde nm, e na 1 bank pipad pesn 64 dk. Spotebujeme sice 480 kB namsto 300, ale za cenu, e vpoet sla banku bude 10-20x rychlej (co se te t jedn instrukce). Funkce pro prvn ppad 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 nemli dlen, pepeme toto pro instrukce AND a SHR (pklad je pro 64K; pro 16K dosate sla 14 a 16383; pro 4K to bude 12 a 4095). Asi se divte, k emu nm je tedy nastavovn logickho dku, kdy jsme stejn u instrukci DIV vyhodili. To uvidte.
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 musme na kad pixel toti provdt 3 operace nsoben. A pitom je to vlastn zbyten. Pokud nastavme dlku dku na 1024, tak ji vlastn meme vzt u jako urit daj o banku. Pokud je bank velk 64K, tak vlastn obsahuje 64 dk. Tm pdem vydlenm dku hodnotou 64 dostvme u i slo banku. Zjednodume si to tm, e budeme dlit ne potem 1024 pixel, ale 1024 byt. Pro BPP=1 je to samozejm tot, ale pro vy u se to li a museli bychom to BPP tam zapotvat (pepoet si meme udlat dopedu a to jen 1x). Zrove meme tedy vypotat 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 mli BPP=2, i 3 nebo 4, tak vpoet upravme nsledovn (dlka logickho 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 ns budou zajmat, kdy budeme zapisovat do na VVRAM, abychom vdli, kter bank si budeme chtt zpstupnit (jejich sprvu provedeme pozdji). A jak nai VRAM vykreslit?
Meme samozejm provdt to, e vdy vykreslme nco do naeho grafickho bufferu (VVRAM) a pot jej cel zapeme do VRAM (zde je situace snadn, nebo vdy peneseme VelOkna a pak zvme ukazatel na bank a ten pepneme). To je ale zbyten pltvn, zvlt, kdy bychom zapsali teba jen jedin pixel na posledn dek bufferu. Pro pak penet i ty ostatn? eenm je, e si budeme udrovat informaci o tom, kter banky jsme mnili. Vytvome si tabulku v tomto stylu (me bt samozejm bitov, ale pak jej obsluha trv o nco dle):
var Banky : array[0..Banku-1] of boolean;
Bez zmny scan dku by bank bylo 5 (posledn by nebyl vyuit cel), se zmnou jich bude 8. Po kadm vykreslen (a na zatku programu) vymaeme tabulku na 0 pomoc:
FillChar(Banky,SizeOf(Banky),0);
Vdy, kdy zapeme njak data do na VRAM (a u se jedn o pixel, dek, blok dat), zapeme jeho bank do na tabulky pomoc:
Banky[Bank] := True;
Pak ped kadm kreslenm provedeme zjitn, kter banky kreslit mme a kter ne. Na to potebujeme dal tabulku s polokami typu BYTE nebo radji WORD:
var Kreslit : array[0..Banku-1] of word; ZmBanku : word;
Funkce, kter nm zjist, kter banky se kreslit budou bude pak vypadat nsledovn:
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 probhne njak 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 pesuneme obsah Banku z VVRAM do VRAM end; end;
Meme optimalizovat jet dl a zjistit si pro kad bank i rozsah dk (Y1,Y2, pro 256C to bude 0-63), a penet do pamti jen je. Pak to samozejm chce dal tabulku, kde budou dv hodnoty WORD pro kad bank (na zatku je nutn tu prvn nastavit na $ffff a tu druhou na 0), a pi kadm zpisu dat se mus Y dat porovnat s obma hodnotami. Pokud je men ne ta prvn, zape se do prvn. Pokud je vt ne ta druh (je nutn vypotat i konec dat pokud zapisujeme vce ne 1 dek najednou), zape se do n. Funkce VykresliRAM bude vypadat pln stejn, jen ta st, kter pesunuje data, nepesune celch 64K, ale jen rozsah dk, mezi ktermi se nco zmnilo. I to urychl vykreslovn o docela dost (i kdy to zpomal zpis dat do VVRAM - alespo o dvod vc nezapisovat po jednom pixelu)!
Dobr, vyeili jsme problmy 1 a 2. Ale kam uloit nai VVRAM? Pod RM (reln md) vyuijeme sluby sprvce pamti vysoko nad 1. MB (HIMEM.SYS pop. EMM386; i ve Windows). Jsou dv monosti, jak zskat nkdy a 64 MB RAM i pro DOSov aplikace. Bu pouijeme EMS nebo XMS. Nebudu zastrat, e mm oblbencem je spe XMS ne EMS. Take EMS jen zmnm, ale dle u budu pouvat jen XMS.
EMS funguje podobn jako XMS. EMS m data rozdlena na tzv. strnky. Kad z nch je velk 16 kB. To je tak minimln velikost, jakou mete alokovat. Systm funguje na principu, e si zjistte adresu, pes kterou sprvce EMS pen data (ta je v doln/konvenn pamti). Pak si muste alokovat urit poet strnek pro Vae data, do kterch (nebo z kterch) budete "penet". Ped skonenm prce s programem muste tak strnky uvolnit. Mezi prostorem v konvenn pamti a Va pamt (v naem ppad to bude $a000 pro VRAM) pente data pes standardn funkci TP7:
Move(Prostor^,Mem[$a000:0],16384);
Pokud chcete data jen st bez toho, abyste je poslali do VRAM, nebo je naopak chcete zapisovat, pouijete toto:
Move(VasProstor^,Prostor^,16384); Move(Prostor^,VasProstor^,16384);
Ukazatele jsou nsledujc:
var Prostor : pointer; {buffer pro 4 strnky EMS od sprvce} VasProstor : pointer; {prostor na hald pes GetMem}
N prostor si vytvome nsledovn (a pi skonen programu jej tak musme uvolnit pes FREEMEM):
GetMem(VasProstor,16384);
To je tak velikost jedn strnky EMS. Tedy, pokud budeme chtt penet data z na VVRAM do VRAM po celch 64K, musme provst penos na tyikrt s tm, e offset ve VRAM vdy zvme o 16384. Samozejm je monost, e bychom nepouvali n prostor a pracovali jen s tm, kter nm nabz EMS, ale to pin problmy, o kterch se zmnm dle. Informace o tom, kde jsou strnky EMS nm sdl sprvce:
function ZjistiProstor : pointer; assembler; asm
mov ah,41h mov dx,bx xor ax,ax int 67h
end;
Zbv jen podotknout, e sprvce EMS obsazuje peruen $67. Strnky jsou v segmentu, kter nm prv vrtil vdy na offsetech $4000*Stranka od 0. Ped tm, ne ho pouijete, je samozejm vhodn si zjistit, zda je vbec ptomen:
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 mete provst pes test, zda jde soubor EMMXXXX0 otevt a pokud ano, zda jde zjistit jeho vstupn stav. Ale jak km, nemm to rd. Pam alokujete a nsledn zase uvolnme tmito dvmi funkcemi (prvn Vm vrt slo handle; zadvte j poet 16K strnek):
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 sprvn pracovat, muste vdt, kolik strnek si mete dovolit alokovat. Poet volnch Vm zjist tato funkce:
function Stranek : word; assembler; asm
mov ah,42h int 67h mov ax,bx
end;
K datm v EMS pamti se dostanete tak, e pslunou strnku z Vaeho bloku namapujete na jednu ze 4 fyzickch strnek v bufferu EMS. To se provd 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 Vae handle, slo fyzick strnky (0-3) a slo 16K bloku (od 0) ve Vaem handle (mus bt men nebo rovnu skutenmu potu alokovanch blok). Jene ty 4 fyzick strnky jsou spolen vem aplikacm. Jinmi slovy, pokud jste rezident, mte problm. Pokud pracujete pod TP7 a vyuvte EMS, mte problm. Muste vdy uloit stav mapovn, pak si pest data a pak to zase vrtit. Existuj pravddpodobn i elegantnj funkce, vechno je to popsan v ATHelpu, ale jak km, nemm EMS rd. Jednak ho moc neznm, jednak se mi zd pomalej a i kdy ne o moc vce ne u XMS, sprva dat uloench v EMS je trochu krkolomn.
Zkusme tedy u standard XMS. Je jednodu ne EMS a tak se s nm lpe pracuje. XMS nepracuje na systmu mapovn blok pamti, ale na tom, e data pen. Vhodou je, e nemte sdlen dn buffer s jinmi aplikacemi. Sta Vm jen vytvoit si V vlastn:
GetMem(VasProstor,32768);
Vytvoil jsem jej 32 kB, abychom mohli penet data do/z XMS efektivnji. Do VRAM je meme penet pmo. XMS pracuje stejn jako EMS s tzv. handle. Ale narozdl od EMS nemusme stle volat INT. Pi prvn incializaci XMS je nm sdlena adresa, kterou budeme u nsledn 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 ptomno? 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 vechny funkce probhaj volnm danho ukazatele. Abychom vdli, kolik kB pamti meme alokovat, mme zde tuto funkci (je univerzln, take bu vrac kolik kB XMS je celkov voln (na zatku obvykle 64 MB) pro True, nebo pro False vrac nejvt 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 mme dal dv funkce, kter zskaj nebo uvoln pam v XMS. Velikost se zadv v kB. Tu prvn si upravme tak, aby v ppad chyby vracela handle 0. Jinak je to vtinou njak slo typu WORD (vce o tomto viz. ATHelp):
function AlokujXMS(Velikost : word) : word; assembler; asm
mov ah,9 mov dx,velikost call dword ptr [XMSsluzby] {tot jako FAR voln} 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 mme, ale jak s n budeme pracovat? Budeme potebovat funkci, kter nm z ukazatele vytvo typ longint, protoe ten je vyuvn v nastaven XMS penos:
function Ptr2Long(Ukazatel : pointer) : longint; assembler; asm
mov ax,ukazatel.word[0] mov dx,ukazatel.word[2]
end;
Nsledn si musme vytvoit pro kad penos strukturu, kter bude udvat, co, kam a kolik se bude koprovat. Vyuijeme schopnosti XMS a vytvome si rovnou 3 popisovae, kter nm budou velmi prospn:
type XMSinfo = record Vel : longint; {kolik byt (!) se bude penet} ZdrHandle : word; {odkud se penej data} ZdjOffs : longint; {pozice v danch datech} CilHandle : word; {kam se budou data penet} CilOffs : longint; end; var doXMS, zXMS, doVRAM : XMSinfo;
Te nsleduje funkce, kter se postar o penesen dat ktermkoliv smrem:
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;
Tto funkci pedvme ve definovanou strukturu. O jej naplnn se star nkolik funkc, kter jsou u ist Pascalovsk. Zbv jen podotknout, e vechny daje jsou v bytech, a e handle Vm sdl funkce pro alokovn XMS. Zvltn slo 0 je vyhrazeno konvenn pamti nebo tomu, co zan 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 pouijeme, abychom nastavili vchoz daje pro nae penosy. Popisovae zXMS a doXMS budou sloit k vmn dat mezi nam bufferem a XMS, popisova doVRAM penese data z XMS pmo do video karty (tam jsme rovnou nastavili velikost celho 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 chtt nco penst, tak jen zmnme nutn daje. Meme si tedy napsat u i pslun, pesn na mru it funkce, kter nm provedou ve potebn (co se te konven pamti, tak budeme vdy penet data na doln hranici, tedy offset = 0, abychom je v nm 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 mete napsat na dek ve VykresliVVRAM, kde je koment. Zbv jen podotknout, e pokud pracujete pod DOSem a pouvte HIMEM.SYS, penejte vdy sudou dlku bloku. Penet nap. jen 1 byte sice jde, ale vyjde Vm nesmysl. Stejn tak 3,5,7,atd. Offset ve Va VVRAM zjistte tmto vpotem (ovem muste ji mt stejn velkou, jako je VRAM, tedy vetn logick dlky dku nastaven na 1024, jinak muste pout jin vpoet):
function OffsetXMS(X,Y : word) : longint; begin OfsetXMS := Y shl 10 + X; end;
Pedpkldm, e si danou funkci u optimalizujete podle toho, co budete chtt zobrazovat (tady jsem bral 640x480x8b). Pokud chcete zjistit offset njakho 64K velkho banku, pouijte toto:
function OffsetBanku(Cislo : word) : longint; begin OfsetXMS := Cislo shl 16; end;
Zde se u vyplc nae logick dlka dku, jinak bychom zde museli nsobit X*Y a BPP, a pak to dlit 64K. Nyn, pokud chcete poslat do XMS nap. jeden pixel s BPP=1, ulote jeho BYTE do VasProstor^ a zavolejte funkci UlozDoXMS s (1,OffsetXMS(X,Y,1)); Kdy budete st, najdete ho opt na Mem[Seg:(VasProstor^):Ofs(VasProstor^)].
procedure Pixel(X,Y : word; Barva : byte); begin {bude zlobit pod istm DOSem s HIMEM.SYS} byte(VasProstor^) := Barva; {Pro DOS nutno pepsat: pest 2 pixely, pepsat 1} UlozXMS(OffsetXMS(X,Y),1); {a pak zase odeslat 2 (sud poet)} Banky[Y shr 6] := True; end; function CtiPixel(X,Y : word) : byte; begin CtiXMS(OffsetXMS(X,Y),1); {Pro ist DOS pest 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 chtli pout vynechvac prhlednost (anglicky se tomu k "transparency"; vraz "translucency" se vtinou pouv, pokud dlme % prnik dvou pixel), sta jednodue dan pixel vynechat. Pokud zapisujeme cel dek, musme si ho nejprve nast, abychom ho mohli poslze zmnit (u dku vtinou pedpokldm, 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 mt v pamti i obrzky, sta, kdy si alokujete dal XMS pam a pipravte si 2 popisovae pro smr XMS->DOS a DOS->XMS. Obrzk mete mt vce, vdy budete mnit jen handle XMS, velikost a offset XMS. Obrzky mete ukldat v 256 barvch (pozdji samozejm i ve vce BPP, a nemus bt ani stejn jako BPP VVRAM; konec konc, ani ta nemus mt BPP shodn s VRAM, ale pak budete muset provdt pevody a to zdruje). Kdy budete chtt obrzek zobrazit, natete ho po dcch a tyto dky umstte do Va VVRAM. A budete mt vechno, mete VVRAM zobrazit (penet mete i XMS<>XMS, ani byste pouvali konvenn pam, to pro ppad, e byste nap. chtli vymazat VVRAM texturou pozad, pokud se Vm budou na obrazovce hbat jen sprity, mete vech 307.200 byt jednodue zkoprovat z handle njakho 640x480 (1024x480 pro logickou dlku dku = 1K; nebo to zapisovat po dcch, pak sta vdy jen 480x penst 640 pixel) obrzku pmo do VVRAM, tm ji vymaete a zrove nakreslte pozad). Protoe poet handle XMS je omezen, je vhodn alokovat teba cel 2 MB velk blok XMS a obrzky ukldat do nj za sebe (vytvote si tabulku, kde budete mt jmno (8 znak (bu array of char nebo string[7] pro zarovnn dat na dword; nebo slo 4 byty) a offset (4 byty) v danm handle; jmno='' bude oznaovat konec tabulky). A poznmka na zvr: do "prhlednch" funkc, kter provd zpis do VVRAM jsem nadval nic, co by aktualizovalo seznam zmnnch bank. To si prosm pidejte sami.
Jist jste si vimli, e bude nutn pro kad rozlien a kad BPP nutn napsat nov funkce. Ano, ale za cenu vy rychlosti, ne kdyby funkce byly univerzln. Abyste je pak nemuseli neustle pomoc CASE OF vybrat (zvlt, je-li hodn kombinac), mete pout procedurln typ a vechny funkce, kter potebujete pro zpis pelote s modelem FAR. Na zatku programu, resp. pi zmn rozlien piadte (pod FP muste pout ped nzvem funkce na prav stran znak @) procedurln promnn jen ty funkce, kter obsluhuj dan rozlien a bhem programu u je nemuste testovat (jejich voln bude sice pomalej, protoe budou FAR, ale funkce na grafiku vtinou stejn bvaj v jin jednotce ne hlavn program a tedy, pokud nepouvte $G, le v jinm segmentu a volaj se stejn vzdlen; kadopdn, pokud podporujete 5 rznch rozlien a 4 rzn BPP, nehled na VESA "kompatibilnosti", jen test pomoc CASE i IF by trval dle, 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 Vs to zatm nechm vstebat a pt budeme pokraovat jinmi BPP, s obrzky a animacemi, efekty a geometri. U te je toho myslm na jeden dl serilu docela dost...
*** POKRAOVN P͊T ***