* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Zacatecnikuv pruvodce po 256barevne SVGA grafice od Initgraph po Putimage * * ========================================================================= * * sepsal Mircosoft (http://mircosoft.mzf.cz) * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Tento text byl napsan primo ve vyvojovem prostredi Turbo Pascalu. Doporucuji otevrit ho tamtez a zapnout zvyraznovani syntaxe pro TXT soubory nebo tento soubor prejmenovat na PAS (aby byl Assembler spravne zeleny :-) ). 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 end (tohohle si nevsimejte, je to jen kvuli spravnemu vybarveni syntaxe) 3) Ukonceni grafiky (velmi kratka kapitola :-) ) 4) Nakresleni pixelu asm: mul, add, adc, xor, pravidla presouvani hodnot mezi registry, psani adres end 5) Nakresleni vodorovne cary asm: rep, stosb, stosw, stosd, prefix db $66 pro 32bitovou instrukci, and, or, jc, jnc, inc, dec, shl, shr, sub end 6) Nakresleni svisle cary asm: loop end 7) Obdelniky a obrazky asm: movsb, movsw, movsd, lodsb end 8) Vyuziti bitovych masek asm: rol, ror end 9) Virtualni obrazovka aneb jak na neblikavou grafiku asm: in, out, test end ****************************************************************************** 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 segmenty jine a ofsety obecne libovolne. 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 ve skutecnosti 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 nejdriv 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] end 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: asm mov AX,$13 {do AX jsme vlozili kod pozadovaneho grafickeho rezimu...} int $10 {...a zavolali jsme preruseni c. $10, ktere ma na starosti grafiku} end 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. end 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: end 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) end napiste: asm mov pw,AX end (pw je nejaka normalni pomocna promenna typu word). Tuto promennou si po skonceni asm bloku v klidu otestujte: end 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} end 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: asm 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} end 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: asm 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: end 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: asm 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} end 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: asm mov AL,barva {barva je hodnota typu byte} mov ES:[DI],AL {zapsani hodnoty AL na danou adresu} end No, a to je vse, pratele. Pokud byste chteli pixel nacist, cely predchazejici cirkus se nemeni, jen tady na konci date: asm mov AL,ES:[DI] end 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} end 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: asm mov AL,barva mov CX,delka rep stosb end 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: asm 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} end 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: asm 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} end 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: asm 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} end 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: asm les SI,Bitmapa mov AX,ES mov SegmentObrazku,AX end (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: asm db $66; rep movsw end 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: asm {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} end 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": asm {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!} end 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 a rychlejsi nez kombinace 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. asm {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 end 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 vetsinou 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 bereme 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. {Mimochodem, pri posouvani obrazovky ve vodorovnem smeru budou okraje zacyklene. Kdyz posunu pocatek doprava, nebude u praveho okraje obrazovky cerne misto, ale oblast, ktera by mela byt schovana nekde za levym okrajem, takze pri vhodne zvolenych (navazujicich) texturach to bude vypadat, ze je vykreslena plocha sirsi nez obrazovka. Takhle se tedy delaji efekty typu zemetreseni i bez DirectX ;-). Da se nastavit i sirka radku VRAM, takze pak muze byt virtualni obrazovka opravdu sirsi nez skutecna. Ale ne kazda karta je schopna nastavit zrovna tu sirku, jaka by se nam hodila, a navic to obvykle nepotrebujeme, takze se o tom dale rozepisovat nebudu.} 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 opravdovy 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: asm 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} end 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 nezacal vracet. Mam ale vyzkouseno, ze presun pocatku je tak rychly, ze takovehle vymozenosti nejsou vubec potreba. Vyuzilo by se to spis v rezimu 320x200, kde se obrazovka fyzicky kopiruje a kazda milisekunda navic je dobra. Provedlo by se to jednoduse: while (port[$03DA] and 8)<>0 do ; , v asm by [ end ] stacilo zamenit jz za jnz. Ovsem pozor: Vlastni presun pocatku zobrazeni je sice velice rychly, ale karte chvilku trva, nez se k nemu odhodla. Muze se tedy stat, ze neco nakreslite, nechate presunem pocatku zobrazeni zobrazit virtualni obrazovku (tedy - poslete karte povel, aby ji zobrazila), presunete aktivni stranku do "neviditelne" oblasti a pak tam s klidnym svedomim zacnete kreslit neco dalsiho. Jenze se muze stat, ze vam to stihne jeste probliknout na puvodni obrazovce (nyni aktivni, drive viditelne strance, ktera ale jeste nedopatrenim zustala viditelna). Reseni je jednoduche. Pred vyslanim povelu k presunu pocatku zobrazeni pockame na zacatek navratu vykreslovaciho paprsku (viz vyse), pak povel vysleme a nakonec pockame na konec navratu paprsku a znovu na zacatek. Tim jednak zabranime veskeremu blikani a jednak poskytneme karte dostatek casu na skutecny presun obrazu. ************************** 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. (c) Mircosoft 12.6.2006 (drobna revize 24.4.2007) (eof)