int21h


* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Zacatecnikuv pruvodce po 256barevne SVGA grafice od Initgraph po Putimage *
* ========================================================================= *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *


Obsah:
1) Uvod
   jak, co a proc, adresovani, omezeni realneho modu atd.
2) Spusteni grafiky
   asm: registry, mov, int, les, cmp, navesti, jmp, je, jne, jz, jnz
3) Ukonceni grafiky
   (velmi kratka kapitola :-) )
4) Nakresleni pixelu
   asm: mul, add, adc, xor, pravidla presouvani hodnot mezi registry,
        psani adres
5) Nakresleni vodorovne cary
   asm: rep, stosb, stosw, stosd, prefix db $66 pro 32bitovou instrukci,
        and, or, jc, jnc, inc, dec, shl, shr, sub
6) Nakresleni svisle cary
   asm: loop
7) Obdelniky a obrazky
   asm: movsb, movsw, movsd, lodsb
8) Vyuziti bitovych masek
   asm: rol, ror
9) Virtualni obrazovka aneb jak na neblikavou grafiku
   asm: in, out, test

******************************************************************************
1) Uvod
******************************************************************************
 Oblibili jste si rozliseni 640 x 480, ale prestalo vam stacit 16 barev?
Spokojite se pro zacatek s 256 barvami? Chteli byste grafiku, ktera je rychla,
neblika a nepotrebuje externi BGI soubory? Nemate ani paru o Assembleru a
podobnych sprostych slovech, cili jste vicemene naprosti zacatecnici?
 Pak ctete dale, text je urcen prave vam.


 Nejdriv nejake ty technicke informace na uvod.


 Kreslit znamena zapisovat data do videopameti. 256barevna grafika se
vyznacuje obecne tim, ze kazdy pixel je vyjadren jednim bytem. Skutecne RGB
slozky jednotlivych barev jsou pak ulozeny zvlast v palete, o ktere si neco
povime pozdeji.
 Videopamet zacina na adrese $A000:0. Presneji: neni tam opravdu fyzicky
umistena, ale kdykoli neco zapisete do segmentu $A000, pocitac vi, ze to ma
hodit do VRAM.
 Ehm... jsta fakt zacatecnici? Tak to bude lepsi zacit vykladem o tom, co to
ten segment vlastne je. Takze: v realnem modu, coz je rezim, ve kterem se
pocitac obvykle pri praci s Dosem a Pascalem nachazi, se adresa kazdeho mista
v pameti sklada ze dvou cisel typu word: segmentu a ofsetu. Skutecna adresa,
ktera funguje uvnitr obvodu pameti, je dvacetibitove cislo, ktere se spocita
takto: segment*16+ofset. Tj.: ke zmene adresy o 16 B muzeme bud zvetsit jeji
ofset o 16 nebo segment o 1. Ale pozor, to prave pro segment $A000 neplati!
Ten se zde nenachazi fyzicky, ale jen virtualne, takze do videopameti musime
pristupovat vzdy jen pres segment $A000, ne treba $A001!
 Kazda promenna v pameti ma vzdy nejakou adresu. Globalni staticke promenne
maji segmentovou cast rovnou DS, coz je jeden z registru procesoru (povime si
o nich dale). Dynamicke promenne maji segment nejaky jiny, ale ofset vzdy 0.
V pameti (na hromade - prostoru pro dynamicke promenne) se totiz zarovnavaji
po 16 B (z toho vyplyva, ze neni zrovna moudre delat dynamicke struktury se
spoustou dejme tomu petibytovych prvku, protoze kazdy z nich realne zabere
minimalne 16, to jen tak na okraj). Protoze ofset je 16bitovy (word), znamena
to, ze kazda promenna muze byt jen tak velka, aby ji ofset dokazal celou
obsahnout. Max. hodnota wordu je 65535 (cili $FFFF sestnactkove, coz se lip
pamatuje) a max. velikost promenne je tedy 65536 B cili 64 kB.


 Urcite jste se uz nekdy setkali s grafickym rezimem $13, cili 256 barev pri
rozliseni 320 na 200. Ze ne? Neverim :-). Je dost pravdepodobne, ze v nem bylo
do dneska napsano daleko vic her nez v jakemkoli jinem rezimu. Kdyz se najde
dobry grafik, daji se i s takhle velikymi pixely delat divy.
Hlavni vyhoda tohoto rezimu je v tomhle: 320*200=64000, coz je cislo mensi nez
magicka hranice 65536 cili 64 kB. Takze celou obrazovku si muzeme nejen
predstavit, ale primo definovat jako pole bytu:


var obrazovka: array[0..199,0..319] of byte absolute $A000:0;
                       ^y     ^x
Ve VRAM jsou pixely usporadany po radcich, zleva doprava, od leveho horniho
rohu dolu, proto mame v tomto poli na prvnim miste cislo radku a na druhem
sloupcu. Slovo absolute znamena pevne umisteni promenne v pameti - pro
promennou se nevyhrazuje zadna specialni pamet, ale pocitac proste vi, ze kdyz
po nem chceme nejakou operaci s takto deklarovanou promennou, ma ji provest s
daty na dane adrese, pretypovanymi na prislusny typ. Pokud tedy chceme na
takto deklarovanou obrazovku nakreslit pixel, neni nic jednodussiho:


obrazovka[y,x]:=barva;


kde barva je cislo typu byte (rozsah 0..255).


Jina moznost (obecnejsi) vypada takhle:


ofset:=sirka*y+x;
mem[$A000:ofset]:=barva;


kde ofset je cislo typu word a Sirka je sirka obrazovky (zde 320).


 No, a ted pomalu prejdeme k vyssim rozlisenim.
Pamet je usporadana uplne stejne (po radcich). Pixel nakreslime opet uplne
stejne: mem[$A000:sirka*y+x]:=barva. Problem je ale v tom, ze tady uz je
obrazovka vetsi nez 64 kB (napr. 640*480=307200=300 kB) a tedy se nevejde do
jednoho segmentu. Jenze jak to pak udelat, kdyz na kresleni je vyhrazen
jenom jeden segment $A000? Bud muzeme prejit do 32bitoveho chraneneho rezimu
(protected mode), ve kterem muzeme provozovat LFB (linear frame buffer), tj.
zapisovat na obrazovku vyse uvedenym zpusobem. Ale to je zalezitost, kterou
zvladaji jenom 32bitove prekladace, napr. Freepascal, ale 16bitovy Turbo
Pascal ne (nebo mozna jo, ale netusim jak :-] ). Takze zbyva starsi, o neco
pomalejsi, ale spolehliva a o dost kompatibilnejsi metoda zvana bankswitching.


 Jak bankswitching (cili "prepinani bank") funguje? Zapisujeme stale do
segmentu $A000. Ten ale celou obrazovku neobsahne (64 kB, ze ano). Proto se
VRAM rozdeli na useky - banky - o velikosti 64 kB. Toto rozdeleni je sice
vicemene virtualni, protoze pamet ve skutecnosti nijak rozdelena neni, ale pro
predstavu to staci. Prepnuti banky tedy znamena nastaveni segmentu $A000 tak,
aby nas pak nasmeroval do te spravne banky (v podstate se nad videopameti
jakoby presouva nejaky ukazatel, ktery urcuje misto, kam se bude zapisovat).
Na prepinani bank se pouziva bud jista sluzba preruseni $10, nebo specialni
procedura, jejiz adresa se zjisti pomoci VESA funkci primo z graficke karty,
a ktera je obvykle o neco rychlejsi.


 To by na uvod stacilo, ted se podivame na to, jak se do grafickeho rezimu
dostat.

******************************************************************************
2) Spusteni grafiky
******************************************************************************
 Pocitejte s tim, ze bez Assembleru se prakticky neobejdete. Ale zadny strach,
kazda instrukce a postup budou podrobne vysvetleny.


 Zacneme pro ilustraci s nejjednodussim rezimem 320x200. Na jeho spusteni
staci dve instrukce:


asm
mov AX,$13
int $10
end;


 Jo, mozna radsi nejdriv kratky uvod do assembleru... takze: je to jazyk, ve
kterem nepisete zadne strukturovane prikazy, jak je znate z Pascalu, ale
primo jednotlive instrukce procesoru, napr. "dosad", "secti", "odecti",
"nasob", "skoc", "skoc, pokud..." atd.. Jeho vyhodou je predevsim rychlost.
Co napisete, to bude naprosto doslova prelozeno, takze veskera optimalizace je
plne ve vasich rukou a nemusite se obavat, co s tim prekladac provede. Dalsi
vyhodou je to, ze zvlada ruzne finty, ktere by v beznem Pascalu sly bud
obtizne nebo vubec - bitova rotace cisla, skoky podle preteceni atd. - ktere
budeme hojne vyuzivat. No, a nevyhoda je to, ze vypada na prvni, druhy a nekdy
i dalsi pohledy pomerne strasidelne a necitelne - ze sloupce instrukci pro
ruzne matematicke operace a presuny dat se dost obtizne taha nejaky smysl.
(Bohuzel procesor je uplne blby a nic jineho nez dosazovat, pocitat, presouvat
data a provadet skoky neumi.) Ale nebojte, za nejakou dobu si Asik taky
zamilujete :-).
 Co nam procesor na teto nejnizsi urovni nabizi? Zacneme tzv. registry.
To jsou v podstate jakoby promenne, ale ne nekde v pameti, jak jsme u
promennych Pascalu zvykli, ale hardwarove "prihradky" primo v procesoru. Jsou
proto velmi rychle. Ne kazdy registr ale lze pouzit na cokoli - nektere veci
se daji provadet jen s jednim registrem, jine zase s jinym, zalezi na situaci.
Registry jsou:


 AX, BX, CX, DX - 16bitove (word) registry pro vseobecne pouziti.
 Kazdy z nich se da rozdelit na horni (H) a dolni (L) byte:


 AH, AL, BH, BL, CH, CL, DH, DL - ty jsou tedy osmibitove (1 byte). Nejsou to
       specialni registry, ale jen jakoby odkaz na predchozi ctyri - pokud
       neco vlozite treba do AH, ovlivni to cely AX.


 EAX, EBX, ECX, EDX - 32bitove registry, ktere se v procesoru nachazeji.
                      Registry AX, BX, CX a DX jsou jejich nizsi pulky, stejne
                      jako treba AL je dolni pulkou AX.
                      K celym E_X registrum se da dostat jen pomoci
                      specialniho prefixu, protoze prekladac TP je 16bitovy a
                      32bitove registry jeste nezna. Ale o tom az pozdeji.
                      32b jsou i vsechny ostatni registry, ale nema cenu o tom
                      ted psat.


 DS (Data Segment) - 16bitovy registr, ve kterem je normalne ulozena adresa
                     segmentu s globalnimi promennymi. Ten nesmite prepsat,
                     a pokud ano, musite si nejdri jeho hodnotu nekam ulozit
                     a pak ji zase vratit, jinak si k tem promennym odriznete
                     pristup.


 ES (Extra Segment) - dalsi 16b registr urceny pro uchovavani segmentu, do nej
                      si obvykle muzete ulozit, co chcete.


 DI, SI - 16b registry urcene pro uchovavani ofsetovych casti adres.


 F (Flags) - 16b registr priznaku. V nem se v jednotlivych bitech uchovavaji
             ruzne informace. Jednitlive bity (priznaky) jsou:
     (tohle zatim muzete preskocit, vysvetlime si to, az to budeme potrebovat)
      ZF (Zero Flag) - rovna 1, pokud byl vysledek prave probehle instrukce
                       nulovy, jinak 0.
      OF (Overflow Flag) - rovna 1, pokud doslo k preteceni, jinak 0.
      CF (Carry Flag) - 1, pokud doslo k prenosu z nizsiho wordu operandu do
                        vyssiho. To znamena: pokud treba k AX neco prictete,
                        vyjde hodnota vetsi nez $FFFF, ktera se do AX
                        maximalne vejde, a to, co "pretece", by slo do horni
                        pulky registru EAX (cely skutecny 32bitovy registr) a
                        CF bude nastavena na 1. Pokud by se vysledek do AX
                        vesel, bude CF = 0. Pozor, neplette si CF s OF - OF by
                        bylo 1, az kdyby pretekl cely EAX.
      AF (Auxiliary carry Flag) - 1, pokud doslo k prenosu z nizsiho bytu do
                                  vyssiho, treba z AL do AH.
      SF (Sign Flag) - 1, pokud vysel zaporny vysledek.
     To vsechno byly priznaky, ktere se samy automaticky nastavuji podle
     vysledku predchozi instrukce. Pak tu jsou ale jeste dalsi:
      IF (Interrupt Flag) - pokud je 1, coz obvykle je, jsou povolena
                            asynchronni preruseni (treba od mysi, klavesnice
                            atd.). Pokud 0, jsou zakazana.
      DF (Direction Flag) - pokud je 0, coz vetsinou je, jsou retezcove
                            instrukce zpracovavany ve vzestupnem smeru. Pri 1
                            v sestupnem. K retezcovym instrukcim se dostaneme
                            pozdeji.
     Ostatni priznaky (PF, TF) nas ted nezajimaji, tak o nich psat nebudu.


 Dalsi registry (SP, IP, BP atd.) uz vicemene na nic nepotrebujeme.


 Vratme se k Asm. Obecna syntaxe jednoho radku je:
[navesti:] oznaceni instrukce cili opkod [operandy]
 Navesti je podobne tem, ktere se v Pascalu definuji v sekci Label,
podrobnosti pozdeji, az je budeme potrebovat. Neni povinne.
 Opkod (opcode) je symbolicka zkratka prislusne instrukce, kterou od procesoru
chceme (napr. mov, int, add, sub, inc atd.).
 Operandy jsou nejaka data, adresy apod., ktere chceme prislusnou instrukci
ovlivnit. Obecne kazda instrukce potrebuje neco jineho, takze si o nich
rekneme az pri popisu jednotlivych konkretnich instrukci.
 Jednotlive instrukce se daji psat bud pod sebe (bez stredniku na konci radku)
nebo vedle sebe na jeden radek oddelene stredniky. Obvykle budeme pouzivat
prvni zpusob, protoze je prehlednejsi.


 Zpet k nasemu prikladu se spustenim grafiky. Tam jsme vyuzili dvou instrukci:
MOV (MOVe) - ekvivalent k pascalskemu :=. "Mov kam,co" znamena "kam:=co", nic
             vic. Takze "mov AX,$13" znamena "AX:=$13" cili "do registru AX
             vloz hodnotu $13". Pozor, ze muzeme presouvat jen data o spravne
             velikosti: wordy do 16b registru a promennych, byty do 8bitovych.
             Pokud to nejde, musime pretypovavat (rekneme si pozdeji).
INT (call INTerrupt) - zavola preruseni s danym cislem ("int $10" tedy zavola
                       preruseni cislo $10). Preruseni jsou takove obsluzne
                       procedury ulozene nekde v Biosu nebo v pameti, ktere
                       umi hromadu veci. Co od nich chceme urcime nastavenim
                       nekterych registru pred zavolanim preruseni (kterych a
                       jak, to zalezi na konkretni situaci).


 Udelali jsme tedy tohle:

mov AX,$13 {do AX jsme vlozili kod pozadovaneho grafickeho rezimu...}
int $10    {...a zavolali jsme preruseni c. $10, ktere ma na starosti grafiku}

 Preruseni $10 vi, ze ma vzdycky napred kouknout do AX a podle jeho hodnoty se
zachovat. Kdyz v nem naslo $13, vedelo, ze po nem chceme spusteni tohoto
rezimu, a tak to provedlo.
 Nyni muzeme do videopameti primo pres segment $A000 cokoli kreslit.
Zpet do textoveho rezimu se dostaneme opet pres sluzby preruseni int $10:


asm
mov AX,3
int $10
end;


 Pozadovali jsme prechod do rezimu cislo 3, coz je bezny textovy rezim s 25
radky, 80 znaky na radek a 16 barvami.


 Mala odbocka: cisla v Assembleru muzeme psat ruznymi zpusoby:
- normalne desitkove: 5, 30, 128 atd.
- hexadecimalne (sestnactkove): $10, $A000, $123 atd. stejne jako v Pascalu
                       nebo 10h, 0A000h, 123h atd., ale to uz jde jenom tady.
                       Pozor, ze prvni cifra nesmi byt pismeno. Pokud je,
                       musime pred cislo pripsat nulu.
- binarne (dvojkove): 10110100b, 001101b atd., opet jen pro asm.
Je to uplne jedno, 1111b znamena totez co 0Fh, $F nebo 15. Jenom se kazdy
zapis z praktickych duvodu hodi na neco jineho. V sestnactkovem zapisu je
na cisle na prvni pohled videt jeho anatomie. Treba $1234. $12 je jeho horni
byte, $34 pak dolni. Kdybychom tohle cislo vlozili treba do AX (mov AX,$1234),
byl by AH=$12 a AL=$34. Kazda hexadecimalni cislice (0..F) znamena presne 4
bity (0000..1111). To je velka vyhoda proti desitkovemu zapisu, ktery si
musime pokazde prepocitat (treba 11111111b = $FF = 255). Take bitove operace
jsou pri hexa zapisu prehlednejsi: $1200 or $0034 = $1234 apod.


 No, konec klaboseni o formatech cisel, ted se podivame, jak spustit nejake
poradne rozliseni. To uz je trochu slozitejsi nez rezim 13h, ktery funguje na
vsech kartach stejne. Tady uz musime vyuzit sluzeb VESA rozhrani, na kterem se
nastesti vyrobci dohodli, jinak by se kazda grafarna musela programovat uplne
jinak. VESA sluzby se volaji pres zname int $10, ale v AH je vzdycky hodnota
$4F, aby preruseni poznalo, ze volame VESU.
 Ze vseho nejdriv musime zjistit, jestli nase karta vubec VESU podporuje.
To se provede takto:


asm
mov AH,$4F    {kod "volame VESU"}
mov AL,0      {cislo sluzby - zjisti informace o VESA rozhrani}
les DI,Zasobnik   {viz dale}
int $10       {zavolani preruseni, ktere se postara o zbytek}
end;


 Misto prvnich dvou instrukci muzeme psat jednu: mov AX,$4F00. Efekt bude
stejny a usetri se par taktu procesoru (zrovna tady nas ale rychlost netrapi).


 Pouzili jsme zde novou instrukci:
LES (Load address to ES). "Les Registr,Ukazatel" znamena "do registru ES uloz
segmentovou cast adresy z daneho Ukazatele a do daneho Registru uloz ofsetovou
cast teto adresy.
 Po provedeni teto instrukce tedy budou registry ES:DI obsahovat adresu
promenne, na kterou ukazuje ukazatel Zasobnik.


 Tento ukazatel obsahuje adresu predem pripraveneho bloku pameti, do
ktereho nam VESA nasype sve osobni udaje. Mel by byt velky aspon 512 B
(nekterym kartam staci 256 B, ale jistota je jistota). Asi nejjednodussi je
pouzit dynamickou promennou. Zasobnik je nutny, i kdyby nas v nem obsazene
udaje nezajimaly - VESA do nej vzdycky neco napise, a kdyby to nebyla radne
alokovana pamet, byla by z toho Nepovolena Aplikace (chybova hlaska, ktera
zatim v zebricku tech nejnenavidenejsich zaujima cestne 1. misto :-) ).


 Po provedeni preruseni je treba zkontrolovat registr AX. Pokud je v nem
hodnota $4F, je vse v poradku, VESA je podporovana. Pokud ne, podporovana neni
a mame smulu. Tohle by nam ve vetsine pripadu mohlo stacit - VESA funguje,
muzeme jit dal. Ale my se nejdriv podivame podrobneji na data vracena v
zasobniku, protoze by se jeste mohla hodit. Struktura dat by se dala zapsat
nejak takto:


StrukturaZasobniku = record
                     Znacka:array[0..3] of char;
                     Verze:word;
                     InfoOVyrobci:pointer;
                     Atributy:longint;
                     SeznamRezimu:^array[0..Cokoli] of word;
                     VelikostVideopameti:word;
                     Zbytek:array[1..492] of byte;
                     end;


- Znacka obsahuje znaky 'VESA' nebo v nekterych pripadech 'VBE2'. Je dobre to
  zkontrolovat (co kdyz se do AX to $4F dostalo nahodou).
- Verze je verze VESA rozhrani. Pokud chcete pouzivat nejake supermoderni
  funkce (treba LFB), ktere starsi verze nezvladaji, urcite ji zkontrolujte.
  Nam ted nic takoveho nehrozi, bankswitching zvladne kazda.
- InfoOVyrobci je ukazatel na retezec ukonceny znakem #0, ktery obvykle
  obsahuje jmeno graficke karty, jmeno vyrobce atd.. Muzete si ho precist, ale
  prakticky vyznam zadny nema.
- Atributy nas celkem nezajimaji, stejne z nich nic zajimaveho nevycteme.
- SeznamRezimu uz je uzitecnejsi. Je to ukazatel na pole wordu (za konstantu
  Cokoli si dosadte nejake velke cislo). Kazda hodnota v tomto poli udava
  cislo nejakeho grafickeho rezimu dostupneho na nasi karte, posledni polozka
  pole ma hodnotu $FFFF (to aby bylo jasno, ze uz je konec, protoze pocet
  podporovanych rezimu nam nikdo predem nerekne). Tento ukazatel si muzeme
  nekam ulozit pro pozdejsi pouziti.
- VelikostVideopameti neni v bytech, ale v 64 kB blocich. Takze pokud to
  chcete v B, nasobte cislem $10000 (65536). Hodi se, pokud se chystate napr.
  hybat s pocatkem zobrazeni, abyste vedeli, kam az muzete bezpecne kreslit
  (o tom az uplne nakonec), a jak velke rozliseni asi vase karta zvladne
  (obvykle neni problem, ale co kdyby).
- Zbytek je rezerovany (tj. nevyuzity) prostor, ktery tam ale musi byt uveden,
  aby nebylo neco zapsano do nealokovane pameti.


Pro zprovozneni si pred vyse uvedeny asm kod jeste pripiseme:


var zasobnik:^strukturazasobniku;
...
new(zasobnik);       {vytvoreni zasobniku}
...sem ten nas kod...
...zpracovani vracenych dat...
dispose(zasobnik);   {zruseni jiz nepotrebneho zasobniku}


 Nyni vime, ze VESA funguje a ze muzeme pokracovat. Nejdrive si zvolime
graficky rezim. V tomto textu se zabyvame pouze 256barevnou grafikou, takze
otazkou zustava pouze rozliseni. Moznosti mame:


           rozliseni:          kod:
           640 x  350          $11C
           640 x  400          $100
           640 x  480    *     $101
           800 x  600    *     $103
          1024 x  768    *     $105
          1280 x 1024          $107
          1600 x 1200          $120


Rezimy oznacene * mam bezpecne vyzkousene a doporucuji je pouzivat prednostne.
Prvni dva jsou tak trochu na houby (ruzne protahly obraz atd.), posledni dva
zase nemusi kazda karta a monitor zvladnout.


 Nejdriv si o zvolenem rezimu musime zjistit par informaci. To se dela takto:


asm
mov AX,$4F01      {kod sluzby "zjisti informace o rezimu"}
mov CX,KodRezimu  {kod z vyse uvedene tabulky}
les DI,zasobnik   {adresa zasobniku na navratova data}
int $10           {do toho!}
end;


 Prvni instrukce by se dala rozdelit na mov AH,$4F; mov AL,1 ale to uz sem ani
nebudu psat. Pokud vse probehlo bez chyb, bude v AX opet hodnota $004F.


 Zasobnik informaci o rezimu vypada trochu jinak nez ten, ktery jsme pouzili
pro zjistovani informaci o VESA rozhrani. Je velky opet 512 B, ale jeho
struktura vypada takhle:


type StrukturaZasobniku2 = record
                           Atributy:word;
                           AtributyOknaA:byte;
                           AtributyOknaB:byte;
                           Granularita:word;{[kB]}
                           VelikostOkna:word;{[kB]}
                           SegmentOknaA:word;{obvykle $A000}
                           SegmentOknaB:word;{obvykle neexistuje (to tvrdi AThelp)}
                           BankovaciProcedura:pointer;
                           BytuNaRadek:word;{nemusi se vzdy rovnat vodorovnemu rozliseni!}
                           {pokud (atributy and 2)=2, jsou nastaveny nasledujici hodnoty:}
                           RozliseniX:word;
                           RozliseniY:word;
                           SirkaZnaku,VyskaZnaku:byte;
                           PocetBitovychRovin:byte;{pro 256 barev je to 1
                                                  (treba 16 barev miva obvykle 4)}
                           BituNaPixel:byte;{pro 256 barev je rovny 8}
                           PocetBank:byte;
                           PametovyModel:byte;
                           VelikostBanky:byte;{[kB]}
                           PocetStranek:byte;{od verze 1.1}
                           Zbytek:array[1..482] of byte;{doplnek do nutnych 512 B}
                           end;


 To je veci, co? :-) Nas nastesti budou zajimat jen dve polozky:
- Granularita: je to cislo, ktere udava, o kolik kB dal za zacatkem jedne
               banky zacina na obrazovce dalsi banka. Ta totiz nemusi zacinat
               vzdycky tam, kde konci predchozi, ale muzou se ruzne prekryvat,
               jak uz jsem rekl, nejsou to zadne fyzicke promenne, ale jen
               virtualni rozdeleni videopameti.
- BankovaciProcedura: toto je adresa procedury, ktera nam prepinani mezi
                      bankami umozni.


 Tyto dve hodnoty si urcite musime nekam uschovat, budou potreba skoro porad.
Ostatni veci jsou vicemene jen pro kontrolu, jestli parametry rezimu opravdu
odpovidaji udanemu kodu. Pokud ne (nejaka divna karta), budeme muset prohledat
pole dostupnych rezimu (ktere nam vratila predchozi funkce) a kazdy timto
zpusobem otestovat (rozliseni a bity na pixel). Pokud dojdeme az na konec pole
(kod $FFFF) a zadny rezim nesedi, neda se nic delat a program budeme muset
ukoncit.


Nyni mame vsechny potrebne udaje a muzeme vesele prejit do grafickeho rezimu:


asm
mov AX,$4F02        {kod sluzby "nastav graficky rezim"}
mov BX,KodRezimu
int $10
end;


 Pokud se vsechno povedlo a grafika uspesne bezi, bude v AX opet hodnota
$004F, coz doporucuji prekontrolovat. Ehm... vite jak? Jestli jo, tak
preskocte nasledujici odstavec :-).


 Jsou dve moznosti. Ta jednodussi: hned za int $10 (jeste uvnitr bloku asm)
napiste:
mov pw,AX
(pw je nejaka normalni pomocna promenna typu word).
Tuto promennou si po skonceni asm bloku v klidu otestujte:
if pw=$004F then {je to OK}
            else {mame problem, grafika asi nebezi};
Druha moznost je provest porovnani s hodnotou $004F jeste uvnitr asm:

cmp AX,$004F        {porovnej obsah AX s hodnotou $004F}
je @JsouStejne      {pokud jsou stejne, skoc na navesti @JsouStejne}
 mov AL,false       {do AL vloz false}
 jmp @konec         {skoc na navesti @konec}
@JsouStejne:        {navesti}
mov AL,true         {do AL vloz true}
@konec:             {navesti}


 A mame tu hned nekolik novych instrukci:
CMP (CoMPare - porovnej). "Cmp neco1,neco2" znamena "porovnej hodnoty operandu
                neco1 a neco2 a podle vysledku nastav registr priznaku".
                Operand neco1 musi byt registr. Jestli jsou porovnavane
                hodnoty stejne, bude ZF = 1. Pokud jsou ruzne, bude 0.
navesti - muzeme je bud deklarovat v sekci Label (jako standardni pascalske),
          pak se muzou jmenovat uplne jakkoli, nebo je nikde deklarovat
          nemusime, ale pak musi zacinat znakem '@'. Budeme pouzivat druhou
          moznost, protoze je mene pracna.
JMP (JuMP - skoc). Skoci na dane navesti. "Jmp @konec" znamena "skoc na
             navesti se jmenem @konec".
JE (Jump if Equal - skoc, pokud jsou stejne). Skoci na dane navesti, ale jen
  pokud je v registru priznaku nastaven priznak ZF na 1 (vysledek predchozi
  matematicke operace byl nulovy). Tato instrukce je presne to same jako
  instrukce JZ (Jump if Zero), jen se jinak pise (aby se lepe pamatovala -
  nekde muzeme napsat je, nekde jz, ale pocitac si to prelozi stejne).
  Takze zde pouzita instrukce "je @JsouStejne" znamena: "pokud hodnoty, ktere
  jsme prave porovnali instrukci cmp, jsou stejne, skoc na navesti pojmenovane
  @JsouStejne".
  Kdyz uz jsme u toho, tak:


JNE (Jump if Not Equal - skoc, pokud nejsou stejne). Skace naopak pri ZF=0.
  Ekvivalentem je instrukce JNZ (Jump if Not Zero).


True a false jsou 8bitove hodnoty (sizeof(boolean) = 1 B), proto je muzeme
ukladat jen do 8b registru (_L nebo _H, ne _X).


 Takze ted mame v AL ulozeno primo ve formatu boolean, jestli bylo spusteni
grafiky uspesne. Cely tento kod muzeme treba sepsat do tvaru funkce, ktera
vraci typ boolean - v AL se totiz predava navratova hodnota. Tedy:


 v Pascalu:


function Neco:boolean;
Begin
...
neco:=hodnota;
End;


 v Assembleru:


function Neco:boolean; assembler;
Asm
...
mov AL,hodnota
End;


 Timto zpusobem se predavaji vsechny 8bitove navratove hodnoty - typ char,
byte, boolean nebo shortint. Vsechny v AL. 16bitove se vraci v AX (word,
integer). A ty vetsi uz nas tolik netrapi, protoze je zatim nebudeme
potrebovat.


 Samozrejme muzete nechat svuj Initgraph ve forme procedury, zalezi na chuti
a vkusu.


******************************************************************************
3) Ukonceni grafiky
******************************************************************************
 Graficky rezim nam bezi. Ted jak z neho ven? To je az trapne jednoduche:


asm
mov ax,3
int $10
end;


cili nastaveni textoveho rezimu 80x25x16, jako v prikladu s rezimem $13.

******************************************************************************
4) Nakresleni pixelu
******************************************************************************
 Nakreslit pixel je ta nejzakladnejsi operace, kterou urcite musime umet.
Potrebujeme k tomu:
1) vypocitat adresu pixelu
2) vypocitat, v jake bance se nachazi
3) prepnout se to te banky
4) naplnit prislusne adresove registry vypocitanou adresou
5) zapsat na tuto adresu hodnotu barvy (1 B)


Je celkem zbytecne pokouset se o zapis bez pouziti Assembleru, takze pujdeme
rovnou do zeleneho ;-).


1,2) Adresa: segment je a bude vzdy $A000. Ofset muzeme zkusit spocitat stejne
jako v rezimu $13: ofset:=sirka*y+x; (x a y jsou pozadovane souradnice pixelu
a sirka je sirka obrazovky v pixelech (napr. pri 640x480 to je 640).
Problem je v tom, ze vysledek se az na male vyjimky (horni cast monitoru)
nevejde do wordu, ale ofset musi byt word. A to je prave ten duvod, proc se
musi pouzivat bankovani.


 V Assiku mame na nasobeni instrukci:
MUL (MULtiply - nasob). "Mul Hodnota" znamena "AX vynasob Hodnotou".
 Styl nasobeni a misto ulozeni vysledku zavisi na typu Hodnoty:
  - pokud je 8bitova (byte), provede se: AX:=AL*Hodnota.
  - pokud je 16bitova (word), provede se: DXAX:=AX*Hodnota (tim DXAX chci
    rict, ze dolni word vysledku bude v AX, horni v DX, neni to zapis, ktery
    by fungoval).


Druha varianta je presne to, co potrebujeme. Protoze banka ma 64 kB (65536 B),
coz je presne rozsah wordu, bude ta "pretekla" cast vysledku vlastne rovna
"adresa div velikost banky", cili presne to cislo banky, ktere potrebujeme.
Zbytek v AX pak bude pouzitelny ofset, ktery - pokud se do te spravne banky
prepneme - nam umozni zapsat barvu na skutecnou pozici na obrazovce.
 Tedy vlastne ne, jeste celou adresu nemame. Zatim jsme vynasobili sirku
obrazovky yovou souradnici, takze mame adresu prvniho (vlastne nulteho, pocita
se vzdy od nuly) pixelu na ytem radku. Musime jeste pricist hodnotu x:


ADD (ADD - pricti). "Add A,B" znamena "A:=A+B".


Ale pozor! Tim, ze k ofsetu jeste neco pricteme, muze nam znovu pretect, coz
znamena, ze bychom se meli prepnout do dalsi banky. Jak detekovat preteceni?
Instrukce add nastavila registr priznaku. Pokud vysledek pretekl (cili pokud
by bylo potreba zapisovat do vyssiho wordu E_X registru, aby se do nej
vysledek vesel), bude priznak CF (Carry Flag) nastaven na 1. Nastesti existuje
jedna velice chytra instrukce:


ADC (ADd + Cf). Funguje skoro stejne jako add, akorat ze k vysledku pricte
 krome druheho operandu i priznak CF, tedy: Adc A,B znamena A:=A+B+CF.
 CF je bud 0 nebo 1.


Prictenim xove souradnice, ktera je typu word, muze ofset pretect maximalne
jednou, takze je to v poradku.
 Konecne muzeme napsat cely vypocet banky a adresy:

mov AX,$A000
mov ES,AX         {segment je jasny}


mov AX,SirkaObrazovky
mul y             {vynasobime AX ypsilonem}
                  {v DX je ted cislo banky na zacatku radku}
add AX,x
adc DX,0          {k DX pricteme CF (druhy operand je 0, protoze nic jineho
                   nez CF pricist nechceme)}
mov DI,AX         {ofset ulozime do registru k tomu urcenemu}


Ted tedy mame v ES segment, v DI ofset a v DX cislo banky bodu.


 Jeste musim vysvetlit, proc jsem nenapsal mov ES,$A000, ale tahal jsem to
pres AX. Proste proto, ze je to nutne. Neni totiz registr jako registr a ke
kazdemu si muzeme dovolit neco jineho. Do a z registru ES a DS nemuzeme
dosazovat (mov) hodnoty primo, ale vzdy musi jit pres nektery registr ke
vseobecnym ucelum (AX, BX, CD, DX). To same plati pro promenne.


 Konec odbocky, zpet ke kresleni pixelu. Mame tedy cislo banky. Nejdriv se
musime podivat, jestli uz nahodou neni nastavena, abychom neprepinali
zbytecne (coz by trvalo mnohem dele nez tato kontrola). Po startu grafiky je
implicitne nastavena banka cislo 0 (zacina v levem hornim rohu obrazovky a
konci o 64 kB = 65536 pixelu dal, pocitano po radcich). Takze si nadeklarujeme
pomocnou globalni promennou: var banka:word;, kterou pri spousteni grafiky
vynulujeme. Porovnani s vypocitanym cislem se provede jiz znamym zpusobem:

cmp DX,banka          {porovnej}
je @NemusimePrepinat  {pokud jsou stejne, preskocime prepinani}


 mov banka,DX     {ulozime do promenne banku, do ktere prepiname (pro priste)}
 ...prepnuti banky...


@NemusimePrepinat:


Prepnuti banky se provede zavolanim procedury, jejiz adresu jsme si ulozili
pri zjistovani informaci o rezimu. Pokud jsme ji nekde ztratili, da se misto
ni pouzit int $10 (nastaveni registru pred volanim bude stejne), ale ve
vetsine pripadu to bude o neco pomalejsi. Procedura potrebuje takoveto
nastaveni registru:
AX = $4F05             kod sluzby - VESA, ovladani okna
BX = 0                 podkod - nastav banku (1 by byla pro zjisteni aktualni
                       banky)
DX = cislo banky
 Proceduru (jakoukoli) zavolame instrukci:
CALL (CALL - volej). "Call procedura" znamena totez, jako napsat v Pascalu
 "procedura;" jako prikaz. "Procedura" je zde obecne 32bitova adresa. Bud
 je to normalni identifikator procedury nebo funkce (skutecnou adresu si pak
 dosadi prekladac) nebo ukazatel (to je zrejme nas pripad) nebo adresa zadana
 primo (napr. ES:[DI], ale to potrebovat ani pouzivat nebudeme).


 Ted se jeste podrobneji podivame na ono cislo banky. Tady vyuzijeme hodnotu
"granularita", kterou jsme ziskali (a nekam ulozili!) pri zjistovani informaci
o grafickem rezimu (viz kapitolu 2 - spusteni grafiky). Rika, o kolik kB dal
za zacatkem jedne banky zacina banka dalsi. K cemu to? Kdyz pocitame cislo
banky stylem mul, adc apod. (viz vyse), budou nam vychazet prave takova cisla,
ktera by odpovidala granularite 64 kB - zacatek dalsi banky lezi hned za
koncem predchozi. To je sice obvykla hodnota, ale ne vsechny karty ji maji
(banky se muzou prekryvat - dalsi zacina driv nez predchozi konci). Napr.
karty Cirrus Logic mivaji granularitu 4 kB (shodou okolnosti jednu takovou
vlastnim, tak presne vim, co to obnasi). Pokud nejaky program neosetri
granularitu a prepina banky s predpokladem, ze je 64, stane se na takovychto
kartach, ze cely obraz bude naplacan nekde v horni ctvrtine obrazovky (coz
jsem zazil u jedne jinak velice slusne graficke knihovny, u ktere takova
skolacka chyba docela zamrzi).


 Udaj, o kolik kB dal zacina dalsi banka je ale kapku nepouzitelny, a proto si
ho nejdriv prepocitame:


  if granularita=0 then granularita:=1
                   else granularita:=64 div granularita;


If je tam proto, ze nektere karty vam reknou, ze maji granularitu 0. Neverte
jim, maji ji 64.
 Hodnotou, kterou ted mame, staci vypocitane cislo banky jednoduse vynasobit.


Vsechno potrebne vime, muzeme se pustit do prepinani bank:

mov AX,DX {banku budeme nasobit granularitou, ale nasobeni funguje jenom s AX}
mul granularita    {vypocet skutecneho cisla banky}
mov DX,AX    {bankovaci procedura chce banku v DX, takze ji tam zase vratime}
{tyto tri radky bychom mohli preskocit, ale pak by se mohla stat chyba,
o ktere jsem mluvil v predchozim odstavci}


mov AX,$4F05
mov BX,0
call BankovaciProcedura    {zavolame prepinaci proceduru}

Mohli bychom opet zkontrolovat AX, jestli je v nem $004F, ale to uz by nas
prilis zdrzovalo - tenhle kod se bude volat kazdou chvili. Proto se spolehneme
na to, ze jestli dotedka VESA fungovala, tak nejspis bude fungovat i nadale.


 Ted tedy mame segment, ofset a nastavenou banku. Co vic si prat? Spravne,
nic :-). Ted konecne muzeme pixel vykreslit:

mov AL,barva       {barva je hodnota typu byte}
mov ES:[DI],AL     {zapsani hodnoty AL na danou adresu}


 No, a to je vse, pratele. Pokud byste chteli pixel nacist, cely predchazejici
cirkus se nemeni, jen tady na konci date:
mov AL,ES:[DI]
cili opacny smer presunu.


 Asi bych mel podrobneji vysvetlit, jak se zapisuji hodnoty na nejakou adresu.
Nejdriv musite mit segmentovou cast adresy ulozenou v ES nebo DS (segmentove
registry). Pak je vetsinou treba do nejakeho registru ulozit ofset. Sice se da
zadat i primo ciselnou hodnotou (konstantou), ale tu obvykle nezname. Na ofset
muzete pouzit DI, SI (ty jsou na to urcene) nebo BX (ke kteremu pak muzete i
pricitat nejake konstantni hodnoty znamenkem +) a mozna i AX, CX nebo DX, ale
tam si nejsem jisty (a zatim se mi jeste nestalo, ze bych to potreboval).
Adresa se pak pise tak, ze je bud cela: [ES:DI] nebo aspon jeji ofsetova cast:
ES:[DI] v hranate zavorce, aby prekladac pochopil, ze to je adresa a ne
hodnota tech registru.


 No, a na zaver zde vidite cely putpixel pohromade:


procedure PutPixel(x,y:word; barva:byte); assembler;
Asm
mov AX,$A000
mov ES,AX


mov AX,SirkaObrazovky
mul y
add AX,x
adc DX,0


mov DI,AX


cmp DX,banka
je @NemusimePrepinat
 mov banka,DX
 mov AX,DX
 mul granularita
 mov DX,AX
 mov AX,$4F05
 xor BX,BX     {*}
 call BankovaciProcedura
@NemusimePrepinat:


mov AL,barva
mov ES:[DI],AL
End;


 Na miste oznacenem {*} vidite o trosicku rychlejsi zpusob, jak vynulovat
registr, nez je mov registr,0.


XOR (eXclusive OR - exkluzivni nebo). A xor B je true (nebo bitove 1), pokud
 prave jeden z operandu je true (nebo 1). "Xor A,B" znamena "A:=A xor B".
 Plati, ze A xor A = false (0) pro jakekoli A. Kdyz tedy xor pustime v bitove
 forme na jedno cislo, vsechny jeho bity se vyxoruji do nuly. Dalsi for je, ze
 A xor B xor B = A, cehoz se hojne vyuziva napr. pri kresleni "inverzni"
 barvou, ktera se po opetovnem prekresleni vyrusi a necha puvodni pozadi
 (viz Setwritemode(Xorput) a Line nebo Rectangle z jednotky Graph).

******************************************************************************
5) Nakresleni vodorovne cary
******************************************************************************
 Chceme nakreslit vodorovnou caru z bodu x1,y do bodu x2,y.
Tady potrebujeme:
1) Spocitat adresu a banku prvniho pixelu cary a do te banky se prepnout.
2) Zjistit, jestli se cela cara vejde do aktualni banky, nebo jestli se budeme
   muset nekde v prubehu kresleni prepinat do dalsi.
3) Vykreslit caru.


Ad 1)
 Vypocet banky a ofsetu je totozny jako pri kresleni pixelu. Takze tim samym
zpusobem si nastavime:
 ES = $A000
 DI = ofset prvniho (toho nejvic vlevo) pixelu cary
 DX = cislo banky
Prepnuti banky uz taky nebudu opakovat.


Ad 2)
 Nejdriv si musime spocitat delku cary: delka:=x2-x1+1;. Jak na to v asm?

mov AX,x2      {AX:=x2}
sub AX,x1      {AX:=AX-x1}
inc AX         {AX:=AX+1}
mov delka,AX   {ulozeni do promenne typu word}

 A nove instrukce:
SUB (SUBtract - odecti). Pouziti je podobne jako u Add, akorat ze se
  nepricita, ale odecita (sub OdCeho,Co).
INC (INCrement - zvetsi). "Inc neco" znamena "neco:=neco+1". Neco muze byt jak
  registr, tak promenna. Tato instrukce nenastavuje priznak CF.


 Zname adresu prvniho pixelu a pocet pixelu. Jak je vykreslit? Na to jsou tzv.
retezcove instrukce. Ty budeme potrebovat kazdou chvili, proto je nutne je
dobre ovladat:


REP (REPeat - opakuj). "Rep Neco" znamena "opakuj Neco tolikrat, kolik je
  hodnota registru CX" (pokud je 0, neprovede se to ani jednou).


Instrukce, ktere se daji napsat za Rep (misto toho "Neco"):


STOSB (STOre String Byte). Zkopiruje hodnotu AL na adresu ES:[DI] a DI zvysi
  o 1 (kdyby nahodou byl nastaven priznak DF, misto zvysovani by se snizovalo,
  ale to nebudeme potrebovat).
STOSW (STOre String Word). Zkopiruje AX na adresu ES:[DI] a DI zvysi o 2.
Kdybychom meli 32bitovy prekladac (napr. Freepascal), slo by jeste:
STOSD (STOre String Dword). Zkopiruje EAX na ES:[DI] a DI zvysi o 4.


Vsechny tyto instrukce trvaji priblizne stejnou dobu. Proto je na prvni pohled
jasne, ze kdyz budeme kreslit po bytech, bude to nejpomalejsi (musime cyklus
vickrat opakovat), a naopak po dwordech by to bylo nejrychlejsi.


 Jdeme kreslit:

mov AL,barva
mov CX,delka
rep stosb

Takhle by to bylo jednoduche, ale pomale. My chceme rychlejsi variantu, takze
pujdeme do rep stosd. Prvni krok je naplneni celeho registru EAX tak, aby
kazdy jeho byte obsahoval hodnotu Barva:

mov AL,barva       {nejnizsi byte}
mov AH,AL          {oba byty AX jsou naplneny}
mov BX,AX          {schovame si to do BX}
db $66; shl AX,16  {posuneme EAX o 16 bitu do leva, takze 2 byty s barvou se
                    dostanou do horniho wordu EAX}
mov AX,BX          {v dolnim wordu (tj. AX) zustala nula, tak tam vlozime
                    ulozene dva byty s barvou}


 Ted toho je docela dost najednou, takze poporade:
SHL (SHift Left - posun doleva). "Shl registr,oKolik" posune dany registr
  o dany pocet bitu vlevo. Z AL=11001010b by "shl AL,3" udelalo 01010000b.
  Pozor, ze je treba zapnout generovani kodu pro 286 ({$G+}), jinak je
  povoleno posouvat jen o jeden bit nebo musite pocet bitu ulozit do CL.
  Bitovy posun se da pouzit misto nasobeni: A shl N = A*(2 na Ntou), je
  rychlejsi a jednodussi nez instrukce Mul.


DB (Data - Byte) slouzi k vytvareni promennych a vkladani hodnot do kodu.
  Pokud to ma byt promenna, musi se preskocit (jmp), jinak si ji program
  splete s instrukci a provede ji. Instrukce prelozena do strojoveho kodu
  totiz neni nic jineho nez nejake cislo nebo posloupnost cisel. To vyuzijeme.
  $66 je strojovy kod (rika se mu opkod, jako operacni kod) instrukce, ktera
  rika "nasledujici instrukci vyhodnot jako 32bitovou". Prekladac totiz nezna
  slovo EAX, proto ho s nim nebudeme trapit a napiseme mu tam instrukci rovnou
  ve strojovem kodu, kterou prekladat nebude a rovnou ji vlozi do EXE.
  Vzhledem k tomu, ze procesor tyhle instrukce zvlada (vetsina procesoru je uz
  dnes novejsi nez prekladac Turbo Pascalu :-) ), program pobezi bez problemu.
   Kdyz se tedy po instrukci $66 odvolame na AX, bude to pro program znamenat
  EAX.


 Ted tedy mame v EAX v kazdem bytu hodnotu barvy. K cemu to? Ted totiz budeme
kopirovat na obrazovku ne byty, ale rovnou dwordy, tj. cely EAX najednou.
Protoze 1 pixel = 1 B, vykresli se v jednom kroku 4 pixely a kazdy musi mit
tu spravnou barvu. Vzhuru do boje:

mov CX,delka          {pocet pixelu, ktere budeme kreslit}
mov BX,CX             {budeme ho ruzne prepocitavat, tak si ho musime ulozit
                       jeste nekam}
shr CX,2              {CX:=CX div 4}
db $66; rep stosw     {rep stosd - CXkrat kopiruj na obrazovku EAX}
and BX,3              {BX:=BX mod 4}
mov CX,BX             {dame to do CX, kde to Rep potrebuje}
rep stosb             {vykreslime zbytek}

 Nove instrukce:
SHR (SHift Right - posun doprava). Bitovy posun vpravo, pouziti stejne jako
  u Shl. Jde pouzit misto deleni: A shr N = A div (2 na Ntou). Je to o dost
  rychlejsi nez instrukce Div, ale v nasem pripade hlavne o HODNE jednodussi
  na pouziti.


AND (bitwise AND - bitovy and). "And Registr,X" znamena "na kazdy bit Registru
  pouzij operaci And s prislusnym bitem hodnoty X". Kde je v X 1, tam v
  Registru zustane bit nezmenen. Kde je v X 0, tam se vynuluje i prislusny bit
  v Registru. And jde pouzit misto instrukce pro zbytek po deleni (v Pascalu
  operator Mod, v Assembleru dava zbytek instrukce Div) mocninou dvojky:
  A mod (2 na Ntou) = A and (2 na Ntou -1).


 Co jsme to tedy vlastne delali: chceme kopirovat po dwordech, tj. co krok, to
4 byty najednou. Delku cary mame ale v pixelech - bytech. Tak ji musime
vydelit ctyrmi, abychom dostali potrebny pocet kroku. Protoze ale delka neni
vzdy delitelna ctyrmi, muzou nam zbyt jeste 0..3 pixely (zbytek po deleni 4),
ktere jeste musime dokopirovat. Kolik jich bude zjistime pomoci andu,
jejich dokopirovani pak obstara rep stosb.


 Tim bychom vlastne meli caru hotovou, nebyt jedne nepatrne drobnosti, a to
rozdeleni obrazovky na banky. Ty totiz mimo specialnich pripadu (sirka
obrazovky 1024 pixelu) nevytvareji obdelnikove pruhy, ale muzou skoncit klidne
uprostred radku obrazovky (vznikne jakysi zlom), takze nase cara muze zacinat
v jedne bance a koncit az v te nasledujici.
 Co s tim. Mohli bychom kreslit pixel po pixelu a u kazdeho zkontrolovat,
jestli uz nam DI nepreteka (max. hodnota $FFFF, pokud ho zvysite o 1, bude mit
hodnotu 0), a pokud ano, prepnout se do dalsi banky. To by bylo trochu na ho-
uby, to uz bychom rovnou mohli kreslit caru putpixelem. Takze si nejdriv DI
nekam ulozime a zkusime k nemu "nanecisto" pricist delku cary. Pokud
nepretece, je to dobre, kreslime celou caru najednou vyse uvedenym postupem.
Pokud ano, spocitame si, jak dlouhy usek budeme kreslit v prvni bance a jak
dlouhy usek ve druhe, nekam si to ulozime, pak vykreslime prvni usek, prepneme
banku a vykreslime zbytek. Takze prakticky:

mov AX,DI        {ulozime DI jinam, protoze si ho nechceme prepsat}
mov BX,delka
dec BX           {BX:=BX-1 (pozor na pricitani a odcitani jednicek pri
                  pocitani s delkami car, radku a podobne!)}
add AX,BX        {AX:=AX+BX, vyjde ofset posledniho pixelu cary}
jnc @najednou    {pokud AX nepreteklo, tedy se vejdeme do jedne banky,
                  skocime dal, jinak vyplnime nejdriv konec prvni banky:}


 or CX,$FFFF     {stejny efekt jako mov CX,$FFFF, ale malinko kratsi cas}
 sub CX,DI       {CX:=CX-DI}
 inc CX          {CX:=CX+1, vyjde pocet pixelu v prvni bance}
 mov AX,delka
 sub AX,CX       {AX:=AX-CX, vyjde zbyvajici pocet pixelu ve druhe bance}
 mov delka,AX    {delka = pocet pixelu ve druhe bance}


 {ted rozkopirujte barvu do EAX a spustte kreslici cyklus, v CX je nastaven
  spravny pocet pixelu pro prvni cast}


 {ted prepnete o banku dal}


@najednou:


mov CX,delka     {Pokud vysla cara do jedne banky, skocili jsme sem z mista,
                  kde je v Delce ulozena delka cele cary. Pokud vysla do dvou,
                  mame ted prvni cast cary nakreslenou, banku prepnutou, CX
                  nulove po prvnim kreslicim cyklu, DI taky, protoze se
                  kreslilo az do konce banky, procez preteklo, a v Delce uz
                  mame ulozenou delku zbyvajici casti cary.}


{zde opet rozkopirovat barvu do EAX, projet kreslici cyklus a cara je hotova}

 Az na par novych instrukci by to mohlo byt vsechno.
DEC (DECrement - zmensi). "Dec neco" znamena "od registru nebo promenne Neco
  odecti 1". Pozor, ze nenastavuje priznak CF, stejne jako Inc.


JNC (Jump if Not Carry - skoc, pokud nebyl prenos). Skoci na dane navesti,
  pokud je CF=0, tedy nedoslo k preteceni wordu v predchozi operaci.
  A kdyz uz jsme v tom:
JC (Jump if Carry - skoc, pokud byl prenos) - skoci, pokud CF=1.


OR (bitwise OR - bitovy or). Pouziti podobne jako u And, ale s bity provadi
  operaci Or: kde je v hodnote X bit 0, tam se v Registru hodnota nezmeni, kde
  je 1, tam se zmeni na 1. Hodnota $FFFF je binarne "vsechny bity na 1".


 A to je cele. Jeste si to shrneme. Potrebujeme:
- ES:DI nastavit na cilovou adresu
- byt ve spravne bance
- do AX (EAX nebo AL podle situace) vlozit hodnotu barvy
- do CX vlozit pozadovany pocet kroku
- zavolat Rep stosb nebo stosd (db $66; rep stosw)


Prikladam cely algoritmus vodorovne cary; pokud byl predchozi vyklad malo
srozumitelny, mate alespon neco pro kontrolu :-)


procedure VodorovnaCara(x1,x2,y:word;barva:word); assembler;
var delka:word; {i v asm procedure muzeme beznym zpusobem deklarovat promenne}
Asm
mov AX,barva
mov AH,AL
mov barva,AX     {Barva ma ted hodnotu barvy v dolnim i hornim bytu, coz nam
                  pozdeji usetri praci. Proto byl parametr word a ne byte.}
mov AX,$A000
mov ES,AX


mov AX,x2
sub AX,x1
inc AX
mov delka,AX


mov AX,SirkaObrazovky
mul y
add AX,x1
adc DX,0
mov DI,AX


cmp DX,banka
je @neprepinej
 mov banka,DX
 mov AX,DX
 mul granularita
 mov DX,AX
 mov AX,$4F05
 xor BX,BX
 call BankovaciProcedura
 mov AX,DI
@neprepinej:


mov BX,delka
dec BX
add AX,BX
jnc @najednou


 or CX,$FFFF
 sub CX,DI
 inc CX


 mov AX,delka
 sub AX,CX
 mov delka,AX


 mov AX,barva
 db $66; shl AX,16
 mov AX,barva


 mov BX,CX
 shr CX,2
 db $66; rep stosw
 and BX,3
 mov CX,BX
 rep stosb


 add DX,granularita  {o banku dal}
 mov banka,DX
 mov AX,DX
 mov DX,AX
 mov AX,$4F05
 xor BX,BX
 call BankovaciProcedura


@najednou:


mov CX,delka


mov AX,barva
db $66; shl AX,16
mov AX,barva


mov BX,CX
shr CX,2
db $66; rep stosw
and BX,3
mov CX,BX
rep stosb
End;{VodorovnaCara}

******************************************************************************
6) Nakresleni svisle cary
******************************************************************************
 Protoze VRAM je usporadana po radcich, nemuzeme ve svislem smeru pouzit
32bitove kopirovani. Takze o to to bude jednodussi.
 Nejdriv jako obvykle spocitame ofset a nastavime banku nejhorejsiho pixelu
cary. Potom v cyklu vzdy nakreslime pixel, pricteme k ofsetu sirku obrazovky,
coz nas hodi pod ten nakresleny pixel, otestujeme, jestli nam pri tom
pricitani ofset nepretekl (pokud ano, prepneme do dalsi banky) a nakreslime
dalsi pixel.
 Algoritmus je celkem jednoduchy, proto se rovnou podivame na kompletni
proceduru:


procedure SvislaCara(x,y1,y2:word; barva:byte); assembler;
Asm
mov AX,$A000
mov ES,AX           {ES = $A000}


mov AX,SirkaObrazovky
mul y1
add AX,x
adc DX,0            {DX = pocatecni banka}
mov DI,AX           {DI = pocatecni ofset}


cmp DX,banka        {neni nahodou potrebna banka uz nastavena?}
je @neprepinej      {jestli ano, preskocime prepinani}
 mov AX,DX
 mov banka,DX
 mul granularita
 mov DX,AX
 mov AX,$4F05
 xor BX,BX
 call BankovaciProcedura
@neprepinej:


mov AL,barva
mov ES:[DI],AL        {prvni pixel je nakreslen}


mov CX,y2
sub CX,y1          {CX = kolik pixelu jeste zbyva nakreslit}
jz @konec          {Nezbyva zadny (CX = 0)? Tak koncime.}


 @cyklus:
 add DI,SirkaObrazovky   {pricteme sirku radku, takze se dostaneme presne
                          o pixel niz}


 jnc @PoradVJedneBance    {jestli DI nepretekl, banka zustava}
  inc banka
  mov AX,banka
  mul granularita
  mov DX,AX
  mov AX,$4F05
  xor BX,BX
  call BankovaciProcedura
  mov AL,barva        {do AL musime vratit barvu, protoze jsme ho o tri radky
                       vyse prepsali}
 @PoradVJedneBance:
 {jestli DI pretekl, je v nem ted ofset v nove bance ("vytekla" presne
  velikost banky - 64 kB)}


 mov ES:[DI],AL       {dalsi pixel je nakreslen}
 loop @cyklus


@konec:
End;{svislacara}


 Jedina nova instrukce je:
LOOP (LOOP - smycka). "Loop Navesti" znamena, prelozeno do pascalstiny, toto:
  CX:=CX-1; if CX<>0 then goto Navesti;. Cili na zacatku vlozime do CX treba
  3. Prvni loop od nej odecte 1, zbyde 2, coz neni 0, takze hop na zacatek
  cyklu. Druhy loop odecte 1, zbyde 1, zase hop. Treti odecte 1, zbyde 0,
  takze konec. Vysledek - cyklus se provedl tolikrat, kolik bylo zadano v CX.
  Pozor! Pokud je CX = 0 uz na zacatku a pouzijete na nej loop, CX "podtece"
  a zbyde v nem $FFFF, cili cyklus probehne 65536krat! Pokud zapisujete do
  normalni pameti, vetsinou to skonci "oblibenou" zpravou Program Provedl
  Neplatnou Aplikaci A Bude Ukoncen, protoze se zapisovalo nekam, kam se
  nemelo. Pri zapisu do VRAM Nepovolena Aplikace nehrozi, protoze do segmentu
  $A000 si muzeme zapisovat kam chceme, ale zase se nam cela banka zaplni
  necim, co jsme chteli mit jenom na par pixelech.


 A jak by se nakreslila obecna (sikma) cara? To uz je trochu slozitejsi, ale
z hlediska pouziti grafiky jde jenom o to spocitat souradnice kazdeho pixelu
a ten pixel vykreslit (Putpixel), coz uz umime. Na pocitani souradnic existuje
nekolik algoritmu (nejrychlejsi je asi Bresenhamuv), ale to uz je matematika
a ne prace s Vesou, proto pripadne zajemce odkazuji na jine prameny.

******************************************************************************
7) Nakresleni obdelnika nebo obrazku
******************************************************************************
 Obrys obdelnika (jako standardni Rectangle) se nakresli dvema svislymi a
dvema vodorovnymi carami, cili brnkacka.
 Vyplneny obdelnik (jako Bar) je trochu komplikovanejsi, ale ne zase o tolik.
V podstate je to jenom kombinace postupu: vodorovna cara, na dalsi radek,
dalsi vodorovna cara atd.. Budeme tedy potrebovat dva cykly: jeden vnitrni pro
kresleni radku (rep stos_) a jeden vnejsi pro prechazeni na dalsi radek (inc,
cmp a jnz).
 Z hlediska logiky tedy nic obtizneho, ale z hlediska delky kodu a poctu mist,
na kterych se muzeme zamotat, uz je to horsi. Hlavne je nutne neustale si
pamatovat, co v kterem registru je, co tam bylo, co tam byt ma a kdy se to
meni. Pak take jakou mame nastavenou banku, kdy ji potrebujeme prepnout a kdy
jsme ji prepnuli. A hlavne kde zrovna jsme, odkud jsme tam skocili a co jsme
pred tim vsechno udelali. Ted pochopite, proc kazdy rika, abyste v Pascalu
nepouzivali skoky, ze to pak nebude prehledne :-).


 Kresleni obrazku (Putimage) je vlastne skoro to same, jenom tam misto
kresleni jednou barvou budeme kopirovat data bitmapy. Nacitani obrazku
(Getimage) se lisi jenom smerem kopirovani dat. Vnejsi cykly a vypocty jsou
vsude stejne, proto se na to podivame nejdriv obecne.


 Parametry teto procedury budou x1,y1 - levy horni roh (pro vsechny spolecne),
dale bud x2,y2 - pravy dolni roh, nebo sirka a vyska, podle toho, co mate
radsi, pro obdelnik samozrejme Barva:word a pro obrazky Bitmapa:pointer
(vzdy bez var - nehybeme s ukazatelem, ale s daty, na ktera ukazuje).


procedure ObdelnikoviteCosi(x1,y1:integer; ... ); assembler;
var incr:word; {kolik musime pricist k ofsetu po nakresleni radku, abychom se
               dostali na zacatek dalsiho}
    sirka,{sirka celeho radku}
    sirka2:word;{sirka pripadne druhe pulky, pokud radek prochazi mezi dvema
                 bankami}
Asm
mov AX,x2
sub AX,x1
inc AX
mov sirka,AX   {sirku obdelnika (delku radku) budeme urcite potrebovat, takze
                jestli nebyla zadana primo v parametrech, vypocitame si ji}
 {vyska, pokud je potreba, se vypocita uplne stejne}


mov AX,SirkaObrazovky
sub AX,sirka
mov incr,AX     {incr nastaven}


mov AX,$A000
mov ES,AX       {ES = cilovy segment}


mov AX,SirkaObrazovky
mul y1
add AX,x1
adc DX,0     {DX = prislusna banka toho bodu}
mov DI,AX    {DI = ofset leveho horniho rohu (zacatku prvniho radku)}


@zacatek: {zacatek cyklu, ktery probehne pro kazdy radek}


mov BX,DI      {vezmeme ofset...}
mov CX,DX      {...a banku,}
add BX,sirka   {k ofsetu pricteme sirku...}
adc CX,0       {...a k bance pripadne preteceni}
dec BX         {vyjde: BX = ofset bodu na konci radku (vlastne ZA koncem
                radku) a CX = banka toho bodu}


{ted je v DI ofset zacatku radku, v DX jeho banka
 a v BX a CX to same pro konec radku}


cmp DX,banka  {prepnuti do banky pro zacatek radku}
je @neprepinej
 {prepnuti banky - zname.
  Mimochodem, zrusi hodnotu v BX, ale ta stejne nebude potreba.}
@neprepinej:


cmp CX,banka {je banka na konci radku stejna jako na zacatku?}
je @najednou  {je stejna => hop dal ***}
 {neni => nakresli se prvni cast radku v prvni bance,
          pak se prepne o banku dal a nakresli se zbytek:}


 or CX,$FFFF  {= mov CX,$FFFF}
 sub CX,DI
 inc CX       {CX = pocet pixelu v prvni bance}


 mov AX,sirka
 sub AX,CX
 mov sirka2,AX   {sirka2 = pocet pixelu ve druhe bance}


 {KOPIROVACI CYKLUS PRO PRVNI CAST RADKU}


 inc,banka
 {prepnuti o banku dal - zname}


 {DI je ted 0, protoze pretekl, takze budeme kreslit na zacatek dalsi banky}


 mov CX,sirka2  {CX = pocet pixelu ve druhe bance}
 jmp @jedeme


@najednou:     {*** (sem se skakalo odjinud)}
mov CX,sirka  {CX = pocet pixelu}


@jedeme:


 {KOPIROVACI CYKLUS PRO DRUHOU CAST RADKU
  (nebo pro cely radek, pokud byl v jedne bance)}


{radek je vykreslen}


mov DX,banka
add DI,incr      {na obrazovce o radek dal}
adc DX,0         {ted vyslo cislo banky na zacatku dalsiho radku}


{Ted je v DI ofset a v DX banka, presne tak, jak to je na zacatku cyklu
potreba. Nyni zkontrolujeme, jestli se ma jeste neco kreslit a pripadne
skocime na zacatek cyklu:}


{bud takhle:}
inc y1    {uz funguje jen jako pomocna promenna, ofsety z ni pocitat nebudeme}
mov AX,y2
cmp AX,y1    {jsou stejne? (uz jsme nakreslili posledni radek?)}
jne @zacatek  {jeste ne => jdeme na dalsi}
{nebo takhle, pokud pocitame s vyskou a ne s y2:}
dec vyska
jnz @zacatek
End;


 Jednotlive modifikace teto procedury se budou lisit jen kopirovacim cyklem a
nekolika detaily na zacatku.


 Pro jednobarevny vyplneny obdelnik na zacatku procedury bude, stejne jako
u vodorovne cary, rozkopirovani barvy do obou bytu. Kopirovaci cyklus bude
zacinat rozkopirovanim barvy do celeho EAX a pokracovat bude stejne jako u
cary (rep stosd atd.).


 Pro vykresleni obrazku to bude jinak.
Budeme potrebovat pomocnou promennou typu word, nazveme ji treba
SegmentObrazku. Na zacatku procedury (jeste pred ES:=$A000) si nacteme adresu
obrazku:
les SI,Bitmapa
mov AX,ES
mov SegmentObrazku,AX
(Bitmapa je ukazatel na data obrazku)
Predpokladam, ze narozdil od BGI, kde data obrazku obsahovala i udaje o vysce
a sirce, bude bitmapa zacinat prvnim pixelem prvniho radku obrazku a rozmery
zadate napr. pres parametry (bude to jednodussi i bezpecnejsi - pokud omylem
nechate zobrazit nedefinovany obrazek, zobrazi se jenom bordel o zadanych
rozmerech a ne bordel o nedefinovanych rozmerech, coz by bylo horsi).
Obrazek bude nekomprimovany, ulozeny po radcich, co byte, to pixel, tedy
presne jako na obrazovce.
 Kopirovaci cyklus bude pouzivat nove retezcove instrukce:


MOVSB (MOVe String Byte) - zkopiruje byte z adresy DS:[SI] na adresu ES:[DI]
                           a SI i DI potom zvysi o 1.
MOVSW (MOVe String Word) - to same, ale kopiruje word a ofsety zvysuje o 2
MOVSD (MOVe String Dword) - to same, ale kopiruje dword a ofsety zvysuje o 4.
Posledni instrukci 16bitovy TP opet nezna, tak ho osidime zapisem:
  db $66; rep movsw
jako obvykle :-).


Vypada to jednoduse, ale je tu jeden hacek: DS. Protoze tento registr obsahuje
segment vsech globalnich promennych, musime si jeho hodnotu nekam ulozit a
obnovit ji hned, jakmile dokoncime kopirovani. Takze:

{zacatek procedury:}


{nacteni adresy do SI a SegmentObrazku, viz vyse}
mov AX,DS
mov DSsave,AX {DSsave je pomocna promenna typu word}


{kopirovaci cyklus:}


 mov AX,SegmentObrazku
 mov DS,AX        {odtedka pozor - nesmime pouzivat globalni promenne!!!!!}


  mov BX,CX
  shr CX,2
  db $66; rep movsw   {kopirovani po dwordech...}
  and BX,3
  mov CX,BX
  rep movsb           {...a zbyle max. 3 byty}


 mov AX,DSsave
 mov DS,AX        {rychle obnovit DS}

Pozor, ze nesmite nechat DS nasmerovany na obrazek a zkouset prepinat banku
(napr. mezi kreslenim prvni a druhe pulky radku rozdeleneho do dvou bank).
Promenna Banka i ukazatel BankovaciProcedura jsou totiz (predpokladam)
globalni promenne, ke kterym v dobe prepsaneho DS nemame pristup. Kdybyste to
zkusili... no, aspon se potesite pohledem na oblibene okenko hlasici
Nepovolenou Operaci :-).


 Nacitani obrazku z obrazovky bude podobne, jenom prohodite zdroj a cil:
DS bude $A000, SI ofset na obrazovce, ES segment obrazku (ktery muze zustat
po celou dobu provadeni procedury nastaven) a DI ofset v obrazku.


 Casto je potreba nakreslit obrazek s vynechanou urcitou barvou (aby se treba
kolem nejake postavicky nemusel zobrazovat obdelnik v barve pozadi). V tom
pripade budeme muset ozelet 32bitovou rychlost, kopirovat pixel po pixelu,
u kazdeho kontrolovat barvu a vykreslit ho jen pokud barva neni rovna predem
definovane "pruhledne":

{nastavit DS na obrazek}


  @kopirovani:
  lodsb         {nacteni pixelu z obrazku}
  cmp AL,PruhlednaBarva
  je @NicNekresli
   mov ES:[DI],AL   {vykresleni pixelu na obrazovku}
  @NicNekresli:
  inc DI        {na obrazovce o pixel dal (i kdyz se nic nekreslilo)}
  loop @kopirovani


{obnovit DS!}


Nova instrukce:
LODSB (LOaD String Byte) - dosadi hodnotu (byte) z adresy DS:[SI] do registru
                            AL a potom zvysi SI o 1.
                            Jednodussi na napsani nez mov a inc.
                            Nenastavuje registr priznaku.


Pro prakticke pouziti je take vhodne osetrit orezavani obrazku, pokud vycuhuje
z obrazovky (coz mimochodem v BGI osetreno nebylo).

******************************************************************************
8) Pouziti bitove masky
******************************************************************************
 Kresleni obdelniku vyplneneho nejakym vzorkem (jako kdyz v BGI nastavite
Setfilstyle(neco jineho nez 0 nebo 1)) nebo ruzne prerusovane cary se da
vyresit pomoci bitove masky. O co jde? Mame cislo (byte nebo word). To se
sklada z prislusneho poctu bitu (8 nebo 16). Rekneme si treba, ze bity 1
kreslit budeme a bity 0 ne.
Nadefinujme si napr. masku pro cerchovanou caru: 11101011b = $EB. Pri kresleni
pixel po pixelu vzdy odrotujeme masku o jeden bit doleva nebo doprava a pokud
se rotaci prenesla jednicka, kreslime, jinak vynechame pixel.
Vysledek bude vypadat nejak takhle: --- - ----- - ----- - -- atd.
Rotace se provadi instrukci:


ROR (ROtate Right - rotuj doprava). "ROR cislo,oKolik" posune cislo o oKolik
       bitu vpravo, a to, co by pri obycejnem posunu (SHR) "vyteklo" pravym
       koncem cisla ven, to "natece" levym koncem zase dovnitr. Takze:
       mame AL=01001001b. Ror AL,1 z nej udela tohle: 10100100b.
       Opet ror AL,1 a mame: 01010010b.
       Pokud takhle zprava doleva pretekl bit s hodnotou 1, bude nastaven
       priznak CF na 1, pokud pretekla 0, tak 0.
ROL (ROtate Left - rotuj doleva). Totez, ale obraceny smer.


 Nastaveny priznak CF bude presne to, co potrebujeme.

{dejme tomu, ze ES:DI = obrazovka, AL = barva, CX = delka cary, maska je
promenna treba typu word}
 @cyklus:
 rol maska,1      {rotace masky}
 jnc @BylaToNula {takze kreslit se nebude}
  mov ES:[DI],AL  {kresleni}
 @BylaToNula:
 inc DI           {o pixel na obrazovce dal, at uz se kreslilo nebo ne}
 jnz @PoradStejnaBanka {pokud DI preteklo (dosli jsme na konec banky), bude
                        v nem 0 (pozor, ze INC nenastavuje CF!)}
  {prepnuti o banku dal}
 @PoradStejnaBanka:
 loop @cyklus


 Modifikace pro obdelnik: misto jednoho cisla si udelame pole cisel, treba
array[0..7] of byte. pred kreslenim radku si do nejakeho registru nebo
promenne ulozime jedno cislo z pole a pak ho rotujeme a kreslime podle nej.
Pro dalsi radek nacteme dalsi cislo atd., pro osmy zacneme zase od nuly...


 Zobrazovani textu: obycejne jednobarevne rastrove pismo se nejlepe uklada
prave ve forme bitovych masek. Jednotlive znaky fontu jsou pole
array[0..VyskaPisma-1] of byte (kazda polozka je jeden "radek" znaku) a cely
font je tedy pak array[#0..#255,0..VyskaPisma-1] of byte. To je 2D pole,
prvnim indexem vyberete znak, druhym radek znaku.
 Na pripade fontu je dobre videt uspornost bitovych masek. Font obvyklych
rozmeru 8x8 pixelu bude velky 8*1*256 = 2048 B = 2 kB. Kdybychom kazdy znak
ulozili jako 2D pole (1 byte = 1 pixel), dalo by to 8*8*256 = 16384 B = 16 kB,
coz je docela rozdil.


 V nekterych pripadech ale bytovou masku misto bitove vyuzijeme. Protoze
takova maska je vlastne totez jako data pro Putimage, nejsme omezeni dvema
barvami (vykresleno/nevykresleno), ale muzeme pouzit celou paletu. Dale:
kdyz nam nebude vadit, ze "nulove" pixely nebudou pruhledne, muzeme toto pole
vykreslovat 32bitove, a tedy velice rychle.
Zkratka obvykle dilema: obetovat pamet nebo rychlost? :-)

******************************************************************************
9) Virtualni obrazovka aneb jak na neblikavou grafiku
******************************************************************************
 Prvni vec, ktera trkne kazdeho zacinajiciho autora her, je blikani
animovane grafiky. At si napisete Putimage seberychlejsi, blika to porad.
Navic vetsina pohyblivych obrazku se stejne musi kreslit s jednou barvou
"pruhlednou", cili pixel po pixelu a bez moznosti 32bitoveho urychleni.
 Co s tim? Potrebovali bychom nejdriv vsechno vykreslit nekam mimo obrazovku,
kde to nebude videt, a az kdyz bude hotovo, presunout vsechno naraz na
monitor. Protoze animace obvykle probiha v cyklu, kde se musi nejakou dobu
cekat, aby clovek vubec stacil koukat a reagovat, nebude vlastne rychlost
kresleni nijak zasadni, protoze kreslit budeme ve "ztratovem" case, kdy bychom
stejne jen tak cekali.
Mistu, kam kreslime a neni to videt, se rika virtualni obrazovka.


 Tolik teorie, ted jak na to.
V rozliseni 320x200, kdy ma cela obrazovka 64000 B, si muzeme nadefinovat
virtualni jako array[0..199,0..319] of byte, kreslit do ni stejne jako
kdybychom kreslili na normalni obrazovku a az bude hotovo, prekopirovat ji do
VRAM napriklad pomoci procedury Move (nebo nejake rychlejsi, napr. 32bitove
varianty). To nam ale ve vetsim rozliseni nepujde (napr. pri 640x480 ma
obrazovka 300 kB a tak velke pole nejde alokovat), proto potrebujeme neco
jineho. A tady nam pomuze jedna velice chytra VESA funkce, posun pocatku
zobrazeni:


procedure NastavPocatek(x,y:word); assembler;
Asm
mov AX,$4F07
xor BX,BX
mov CX,x
mov DX,y
int $10
End;


Jak vidite, nejsou potreba zadne zdlouhave presuny dat - jedine, co karte
posilame, jsou dve souradnice.


Normalne je pocatek zobrazeni na souradnicich 0,0. To znamena, ze levy horni
roh monitoru odpovida "levemu hornimu rohu" (tedy pocatku) VRAM. VRAM je
usporadana po radcich, ktere jsou stejne siroke jako obrazovka. A ted se
zkuste podivat, jak velka vase videopamet doopravdy je (viz uplny zacatek,
zjistovani informaci o VESA rozhrani). Takovych megabajtu :-O. Kde jsou?
Prece pod obrazovkou! Kdyz budete kreslit na souradnice pod spodnim okrajem
monitoru, opravdu tam vsechno bude, jenomze to nebude videt. A presne to
chceme. Takze recept na animaci bez blikani, popsany na priklade rozliseni
640x480:


1) Nakreslime pozadi a vsechny pohyblive objekty, ale yovou souradnici vzdycky
zvetsime o 480.


2) Presuneme pocatek zobrazeni na souradnice 0,480.


4) Kreslime dalsi fazi animace, ale y zase od 0 (ted to bude nad monitorem,
takze neviditelne).


5) Presuneme pocatek na 0,0.


A tak porad dokola.


 Prakticky se to udela tak, do veskerych kreslicich procedur ve sve graficke
jednotce pridate na zacatek pricteni hodnoty nejake pomocne promenne, ktera
urcuje yovou souradnici pocatku (dejme tomu var PocatekY:word). Ta je bud 0
nebo 480 (obecne Getmaxy+1). Pak si napisete prepinaci proceduru, ktera pri
PocatekY=0 presune obrazovku na 0,0 a PocatekY nastavi na Getmaxy+1 a pri
PocatekY>0 presune obrazovku na 0,Getmaxy+1 a PocatekY nastavi na 0.
Pozor, nepoplest: promenna PocatekY urcuje misto, kam kreslim, skutecna
poloha pocatku zobrazeni urcuje, co vidim. Takze na zacatku, kdyz koukame
na pocatek 0,0, musi byt PocatekY nastaven na Getmaxy+1, jinak bude v prvnim
cyklu trochu zmatek.


 Pro jistotu je dobre na zacatku zkontrolovat, jestli VRAM opravdu vystaci na
dve obrazovky pod sebou. Kdo vi, na jake plecce vas program treba nekdo bude
chtit spustit. Pozor hlavne na laptopy - obvykle maji jen tolik pameti, aby
vystacila na nejlepsi displejem podporovany videorezim, coz u starsich modelu
neni zrovna moc.


 Hmmm, virtualni obrazovka funguje, ale blika mi to porad. Co ted?
Aha, jeste je tu jeden drobny hacek. Monitor nezobrazi vsechno hned, jakmile
mu to hodime do videopameti. Obraz je kreslen elektronovym paprskem a tomu
chvili trva nez obehne celou obrazovku. Takze jestli obsah VRAM zmenite, kdyz
je paprsek zrovna uprostred cesty, vykresli se jenom spodni pulka obrazu, pak
bude chvili trvat nez se paprsek vrati zpatky do leveho horniho rohu a pak
teprve se nakresli zbytek a tim vznikne blikani. Tento jev jsem pozoroval i na
displeji laptopu, kde zadny elektronovy paprsek nebeha... zajimave.
 Nastesti se da zjistit, kdy se paprsek zrovna vraci od spodniho okraje
nahoru. Kdyz presne v tuto chvili stihnete neco nakreslit, bude to zobrazeno
hezky klidne a bez blikani. Posun pocatku zobrazeni je nastesti tak rychly, ze
se do tohoto kratkeho okamziku bohate vejde.
 Informace o paprsku precteme z portu $03DA. Kdyz se paprsek vraci, je ctvrty
bit zprava (pocitano od 1) roven 1, jinak 0. Nejlepsi tedy bude pred presunem
obrazovky pockat, az se paprsek bude vracet:


    while (port[$03DA] and 8)=0 do ;


Nebo to same v zelenem:

mov DX,$3DA {cislo portu}
 @cekani:
 in AL,DX  {precteni hodnoty z portu}
 test AL,8 {otestovani prislusneho bitu (nedestruktivni and)}
 jz @cekani {jestli je 0 (paprsek se nevraci), cekame dal}

 Nove instrukce:


IN (INput from port - cti z portu). "In Kam,Odkud" znamena "do registru Kam
  dosad hodnotu z portu s cislem Odkud". Kam muze byt pouze AX nebo jeho cast,
  Odkud muze byt bud primo ciselna hodnota, ale v tom pripade jen typu byte,
  nebo registr DX, v tom pripade hodnota jakakoli.


TEST (TEST - vyzkousej). Operandy stejne jako u instrukce and. Priznaky se
  nastavi, jako kdyby se skutecne provedl and, ale hodnota v registru se
  nezmeni (podobne jako cmp a sub).
  Nejspis je nepatrne rychlejsi nez and.


A kdyz uz jsme v tom, tak:


OUT (OUTput to port - zapis na port). "Out Kam,Odkud" znamena "na port Kam
  posli hodnotu z registru Odkud". Odkud je opet pouze AX, AL nebo AH, Kam
  bud 8bitove cislo nebo DX.


 Kdybychom chteli cekani na paprsek dokonale, zaradili bychom cekaci cykly
dva: prvni by cekal, dokud by se paprsek vracel (co kdyby byla procedura
zavolana v okamziku, kdy by se paprsek vracel, byl uz tesne pred cilem a tudiz
by bylo malo casu) a az ten druhy by cekal, dokud by se nevracel. Mam ale
vyzkouseno, ze presun pocatku je tak rychly, ze takovehle vymozenosti nejsou
vubec potreba. Vyuzilo by se to treba v rezimu 320x200, kde se obrazovka
fyzicky kopiruje a kazda milisekunda navic je dobra.



**************************  A to je vse, pratele. ****************************


 P.S.: Tento text jsem se rozhodl napsat proste proto, ze jsem jeste nic
podobneho na siti nenasel. Mozna jsem spatne hledal, ale mozna jeste opravdu
nikoho nenapadlo sepsat podrobnou prirucku pro zacatecniky, kteri chteji
zvladnout grafiku na vlastni pest, naprosto netusi do ceho jdou a prace
s AThelpem v jedne ruce, ucebnici Assembleru v druhe a neciho zdrojaku
ve treti (ehm) jim nepripada jako ten nejprijemnejsi zpusob zabijeni casu :-).


             Takze doufam, ze jsem tim alespon nekomu pomohl.

TXT verze tohoto článku vhodná pro prohlížení v IDE TP

2006-11-30 | Mircosoft
Reklamy: