Az előző részben sikerült egy pixelt kigyújtani a képernyőn. Szép teljesítmény, de elég sokat kellene gépelni, ha valami összetett ábrát akarunk látni, nem igaz? Szerencsére nincs is rá szükség, mert a programozás tele van olyan nagyszerű elemekkel, mint a ciklusok, vagy a feltételek.
De előtte rajzoljuk tele a képernyőt. A megoldás nagyon hasonlít az előzőleg bemutatott kódra, de van benne egy plusz utasítás. Az inc. Ez nem csinál mást, mint eggyel növeli az adott regiszter tartalmát. Párja, a dec, csökkenti azt. Ha tehát minden egyes ugrás után megnöveljük a memóriacímet, ahova rajzolunk, elboríthatjuk fénylő pixelekkel a képernyőt. Rajta, ne tétovázzunk!
org 100h
section .text
start:
mov ax, 13h
int 10h
mov ax, 0A000h
mov es, ax
draw:
mov dl, 7
mov [es:di], dl
inc di
jmp draw
section .data
section .bss
A program végtelen ciklust hajt végre, soha nem jut el a jmp utáni kódrészletig. Persze, ez még mindig csak számítások egymásra hányása. A rendszer ugyanis túlcsordul, de ez nem hibaként jelentkezik, hanem azzal, hogy újból rajzolni kezd az elejéről. Ugyan azzal a színnel. Mit is mondunk az ilyen képre? Uncsi! Mi lenne, ha egy cilkust használnánk?
A ciklus megvalósítása nem nehéz. A loop utasítás segítségével visszaugorhatunk egy korábbi címre. A ciklus számlálója az CX regiszter. Amíg a regiszter nagyobb, mint egy, a loop visszaugrik. Tehát egy 10 lépéses ciklust a következő módon hozhatunk létre:
mov cx, 10
for:
; instructions
loop for
Kiváló! Sikerült felülkerekedni a jmp hátrányain! De ha kicsit belegondolunk, a szívünk összeszorul. Elhasználtunk egy csomó regisztert! Már nincs AX, nincs BX, ha ciklusokat akarunk használni, le kell mondani a CX-ről. A programunk komplexitása szinte a zérón áll, és máris elhasználtuk a regisztereket. Hol vagyunk még az Unreal Engine összetettségétől?
Nem kell aggódni, mert még alig használtuk gépünk memóriáját. Ott van például a stack (vagy verem). Ez pont olyan, mint a "Fontos!" feliratű doboz a munkahelyemen. Ha kapok egy fontos papírt, akkor azt oda teszem. Kapok még egyet, rárakom az előzőre. Aztán gondolok egyet, hogy most fontos dolgokkal kellene foglalkoznom, és kiemelem a legfelső papírt a dobozból, és elvégzem, ami rajta van.
Ez az analógia annyira jól működik, hogy ha nem dolgozom fel elég gyorsan a fontos dolgokat, akkor pontosan ugyan az történik velem is, mint a számítógéppel. Megtelik a verem. (Ekkor kapjuk a híres stack overflow hibaüzenetet.)
Két utasítással kezelhetjük a stack-et. Ez a push és a pop. Előbbivel egy regiszter értékét menthetjük oda, másodikkal az ott található értéket helyezhetjük el egy regiszterben. Ha tehát két ciklust akarunk egymásba ágyazni, akkor a CX regiszter mentéséről magunknak kell gondoskodnunk a stack segítségével. Nem kell lefoglalni a memóriát, vagy ellenőrizni, ez mindig hűségesen a rendelkezésünkre áll.
org 100h
section .text
start:
mov ax, 13h
int 10h
mov ax, 0A000h
mov es, ax
mov cx, 64000
cycle1:
mov di, cx
push cx
mov cx, 256
cycle2:
mov dl, cl
mov [es:di], dl
loop cycle2
pop cx
loop cycle1
mov ax, 3
int 10h
section .data
section .bss
A program telerajzolja a képernyőt, de lassabban, mert minden egyes pozícióban 256-szor megváltoztatja a képpont színét. De a végeredmény továbbra is egyszínű képernyő. Ezen sürgősen változtatni kell! Mi lenne, ha az elejét és a végét más színnel rajzolnánk?
Természetesen megtehetnénk, hogy két ciklust készítünk, de akkor kétszer kell leírni a kirajzoló rutint is. Inkább egy vizsgálat kellene. Ha a képernyő elején vagyunk, átállítjuk a rajzolás színét (esetünkben a dl regisztert). A vizsgálatotokat több utasítás alkalmazásával tudjuk megvalósítani. Először a cmp utasítás megvizsgálja, hogy két érték egyenlő-e, és beállít további regisztereket (most mindegy, hogy melyeket). Aztán egy másik utasítás a regiszterek alapján ugrik az általunk megadott címre. Ezért annyi ugró utasítás van, ahányféle összehasonlítás. Én csak hármat mutatok be. A je ugrik, ha a két érték egyenlő. jb akkor fog ugrani, ha a cmp-ben használt kifejezés bal oldali mennyisége kisebb, a ja utasítás pedig akkor, ha nagyobb. Módosítsuk a jmp-s példát, hogy mindez világos legyen:
draw:
mov dl, 2 ;green colour
cmp di, 8000
ja set
mov dl, 12 ;red colour
set:
mov [es:di], dl
inc di
jmp draw
Beállítjuk a színt zöldre, majd megnézzük, hogy a pozíció kisebb-e 8000-nél. Ha nagyobb, ugrunk a kirajzolásra. Ha nem nagyobb, akkor az ugrás nem valósul meg, a szín piros lesz. Most már tudunk készíteni egy programot, ami nem uncsi. Kirajzolhatjuk a Magyar zászlót.
draw:
mov dl, 2 ;green
cmp di, 21119
ja iftwo
mov dl, 12 ;red
jmp set
iftwo:
cmp di, 42239
ja set
mov dl, 15 ;white
set:
mov [es:di], dl
inc di
jmp draw
Először megnézzük, hogy a pozíció nagyobb-e 21119-nél. Ha nagyobb, ugrunk a második összehasonlításra. Ha nem, beállítjuk az első színt és ugrunk a kirajzolásra. Ha nem így tennénk, a program végrehajtaná a második összehasonlítást is. Majd hasonló logikával jön a második feltétel. Bonyolult? Ha a válasz igen, akkor ideje megszeretni a GOTO-t, mert alacsony szinten ez a király. Nagyon sok mindent nem érintettem a posztban, de ennek az ismertetőnek nem is célja, hogy mindent bemutassak. A cél az érdeklődés felkeltése.
Ezért, akinek van kedve, elgondolkodhat azon, hogy az utolsó példánál át lehet-e rendezni úgy a vizsgálatokat, hogy ne legyen szükség az első jmp utasításra.