SVGAlib

Allikas: Kuutõrvaja

Graafika konsoolil- SVGAlib

Sissejuhatus

Ma olen suutnud selle homesite'i hoida enamvähem vaba linux'i ja DOS'i võrdlemisest, kuid siin ei saa küll vaiki olla. Kasutades DOS'i all Pascal'it ja Assembler'it, isegi sellel vähesel oskuse tasemel, mis mul oli sain ma kenasti tööle mittevilkuvad liikuvad kujundid. Tõsi, resolutsioon oli jäme (320x200x256 st. ekraani laius jagati 320 punktiks ja kõrgus 200'ks ning erinevaid värve sai korraga olla 256); värve sai ise defineerida. See oli kena asi: videomällu otse kirjutamine ja puha. Põhimõtteliselt on see muidugi OS'ist natuke liiga lahke - lubada otse videomällu kirjutada; ei pigem on see kasutajast natuke liiga enesekindel võimalust kasutada. Või kes teab ....

Igatahes siin järkub jutt sellest, kuidas samasuguseid tulemusi linux'is saavutada. Mitte X-i all vaid konsooli peal. Kelle jaoks see ülevalolev jutt tähendust ei omanud siis polu hullu. Lugege edasi.

Tarkvara hankimine ja instaleerimine

Eks neid site'e on palju, otsitav on svgalib-1.3.0.tar.gz ja asub see näiteks http://www.svgalib.org/

Sisuliselt on kõnealune objekt hulk C library'eid, mis suudavad tegelda videokaartidega erinevates rezhiimides. (360x480x256, 320x200x256 ...) Tuleb kohale tõmmata ja ära installeerida. SVGAlib'i puuduseks loetakse seda, et ta on nö. kursis vaid vähese arvu videokaartidega. Standartseid ta siiski tunneb.

Produktiga on kaasas kümmekond demostratsioonfaili koos kommenteeriritud source'ga. Samuti man-paged. Kasutamine toimub programmeerides tavalist, st. mitteobjektorienteeritud C-d tarvitades SVGAlib library poolt pakutavaid funktsioone. Edasine jutt keerlebki praktilise kasutamise ümber töötavate näidete kommenteerimisel.

Videorezhiimi sisenemine ja väljumine, joon ekraanil

Kaasasoleva programmiga vgatest tuleks kindlaks teha millist resolutsiooni teie raudvara kannatab. Mina keskendun 480x360x256'le kusjuures mul on kasutada ATI Mach64 264VT 2MB videokaart ja tavaline multi-sync monitor (31.5-48-5 kHz, 55 - 90 Hz).

Programm joonistab ekraanile mõned jooned

 1. #include <stdio.h>
 2. #include <unistd.h>
 3. #include <string.h>
 4. #include "vga.h"
 5.
 6. void main(void)
 7. {
 8.     vga_init();
 9.     vga_setmode(8);
10.     vga_drawline(10, 10, 10, 300);
11.     vga_setcolor(40);
12.     vga_drawline(10,10,300,10);
13.     getchar();
14.     vga_setmode(TEXT);
15. }

Selgitus:

Esmalt ütleme, et ilus nii programmeerid ei ole. Vanad mehed nii ei tee; noored ammugi mitte. Asi on selles, et programm näiteks eeldab, et videmode 8 läheb valutult käima. Aga selguse huvides antakse mulle andeks. Tõsi ta ikka töötab, kui olete vgatest'iga veenunud, et teie arvuti toetab 8'dat moodi.

1. -  4. kohustuslikud header failide read, kui need puuduvad, et saa kompilaator 
aru mida getchar() või vga_init() tähendavad
6. algab põhifunktsioon, see tühjus (void) tähendab, et funktsioonil ei ole 
argumente ja ei tagasta ta ka midagi erilist
8.  initsialiseeritakse svga library'id nö. 'võistlejad kohtadele'!
9. lülitutakse ümber video mode nr. 8-sse.
10. joonistatakse joon algusega (10, 10) ja lõpuga (10, 300) ta tuleb valge. x, y 
teljestiku alguspunkt on traditsiooniliselt monitori vasakul üleval nurgas ja x telg on 
suunatud paremale ning y alla.  See on algul harjumatu
11. muudetakse värv millega arvuti joonistab
13. arvuti on jooned joonistanud ja jääb graafilises moodis ootama suvalise klahvi vajutust; kui 
see koht puuduks, siis ei oleks meie aega oma iluilti vaadata
14. lülitutakse tagase nn. mittegraafilisse ehk tekstirezhiimi

Kes tunneb muid programmeerimiskeele võib ütelda, et vhuhh, pikad ja keerulised käsud: vga_drawline, vga_setcolor .. Ei, see on inglise keeles ja tegelikult pole niivõrd vaja mõtelda käsu märkimiseks kasutatava sõna kuivõrd selle peale mida käsk teeb. All näeme, et SVGAlib'i poolt pakutavad käsud teevad kaunis normaalseid asju:

- joon määratakse nelja koordinaadiga
- punkt ehk pixel määratakse kahe koordinaadiga
- värvi määrang
- taustavärvi määrang

....

Retrace ja liikumise kujutamine ekraanil

Et mõista waitretrace'i tähendust peab veidi kõnelema monitorist ja videomälust.

Monitoril on ekraan jagatud meie rezhiimis 480 x 360 = 172 800 punktiks ehk pixliks (pixel - picture element). Kuna selles rezhiimis on igale pixlile võimalik omistada värvus 256 võimaliku seast, siis iga piksel võtab videomälus ruumi 8 bitti ehk 1B (2^8=256). Kuskil mälus on seega linux resrveerinud 172 800 B ekraani kujutise hoidmiseks.

Funktsioonid vga_drawline() ja teised sarnased seavad tolle mälu bitte muukui ümber. Kus konreetselt arvuti mälus need bitid asuvad pole kasutaja jaoks väga oluline. Parem kui ta neid bitte ei lähe otse sättima kuigi põhimõteliselt saaks. Nende sättimiseks ongi mõeldud see library. Videokaardi roll on teostada normaalsel juhul 75 korda sekundis (st. 75 Hz on sel juhul laotussagedus) ekraani kujutise vastavusse viimine sellele mälupildile. Järgnevas vaatame probleeme kui tahta kanda ekraanile järjestikuseid kujutisi, mis peaksid looma illusiooni et nt. 5 sekundi jooksul liigub kastike üle ekraani. Kui ei tehta asja nö. õigesti, siis liigub kastike küll üle ekraani, aga vilumise ja läbijooksvate triipude saatel.

Monitoril teostab pildi kandmist ekraanile elektronkiir, mis liigub ekraani 'vaataja' poolt vaadates ülemisest vasakust nurgast rida- realt alla paremale nurka ning siis tuuakse ta diagonaalis kiiresti aga mitte lõpmata kiiresti algasendisse tagasi. Seega üks ring antud juhu on 1/75 sekundit.

Et kujutada liikumist kvaliteetselt on oluline ajastada videomällu kirjutamine füüsilise elektronkiire liikumisega kirjutamise ajal. Muide, elektronkiir on nagu kõvaketas - töötab rütmiliselt ja seda rütmi on juba keerulisem muuta.

Arutleme selle üle milline oleks optimaalne ajastatus kujutise kandmiseks:

- kui ekraani kujutist muuta kord minutis, siis pole probleeme; ta muutub nii harva ja näib kvaliteetne - kui püüame ekraanil kujutada liikumist, siis on oluline see kunas teha videomällu muutusi - kui joonistamise ajal (st. kiire alla liikumise ajal) teha videomälus muutusi, siis on tulemuseks see, et ülemine ekraanipool on ekraanil nii nagu ta sai sinna enne muutuste tegemist elektronkiirel kantud; alumine poole vastab aga uuele mälu seisule. Ja pilt ongi ebakvaliteetne mis avaldub üle ekraani jooksvate triipudena või vilkumisena. - alt ülestuleku ajal (nn. retrace'i aeg) ekraanil ei kajastu videomälus tehtavad muutused; see aeg on küll lühike aga sellest peab piisama, et teha videomälus vajalikud muutused ja siis uusi muutusi teha alles järgmise elektronkiire alt ülestuleku ajal. Nii talitades on võimalik saada kvaliteetsemat liikumist kujutavat pilti kui muidu.

Toome ka asjakohase joonise:

Refr.gif

Joonisel kujutavad jooned teid, mida mööda elektronkiir liigub. Punane värv vastab sellele osale teest kus kiir ekraanile nähtava jälje jätab. Rohelisega on kujutatud nn. retrace ehk tagasipöördumine. 75 Hz puhul teeb elektronkiir ringi 0,013 sekundiga; oletades, et elektronkiir liigub kogu aeg pea sama kiirusega leiame tagasipöördumise aja:

kogu teest moodustab tagasipöördumine ca 1/720 'ndiku so 0,013/720 = 0,000018 s.

Tegelikult on see aeg vist veel väiksem ja see on see aeg mis jääb programmil retrace'i ajal uue ekraani kujutise arvutamiseks ja mällu kandmiseks. Et siin ei tohi raisata palju aega keeruliste matemaatiliste tehete tegemiseks, siis kasutatakse nt. sin() ja cos() arvutamiseks varem väljaarvutatud tabeleid. Samuti on otstarbekam teha tehteid täisarvude ja mitte ujukoma arvudega.

Tehniliselt nimetatakse reallaotussageduseks arvu mitu korda jõuab kiir sekundis rea algusest lõppu ja tagasi (minu puhul 31.5 - 48.5 kHz, st monitor peaks selles vahemikus toime tulema. Konkreetse sageduse dikteerib videokaart vastavalt settingutele)

Laotussagedusest oli juba juttu: minu puhul oli see mingi sagedus vahemikust 55 - 90 Hz. Jällegi videokaardi ja lõpuks kasutaja sättida.

teine näide, pöörlev joon:

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include "vga.h"
#include<math.h>

 1. void main(void)
 2. {
 3.     int x=0, y=0, n=0;
 4.     vga_init();
 5.     vga_setmode(8);
 6.     while(1)
 7.       {
 8.         vga_waitretrace();
 9.         vga_setcolor(0);
10.         vga_drawline(150-x, 200-y, 150+x, 200+y);
11.         y=145*sin(6.28*n/360);
12.         x=80*cos(6.28*n/360);
13.         vga_setcolor(63);
14.         vga_drawline(150-x, 200-y, 150+x, 200+y);
15.         n+=2;
16.         if (n == 360)
17.                 n=0;
18.       }
19.     vga_setmode(TEXT);
20. }

Selgitus (värvid vastavad ülemise joonise elektronikiire värvidele):

 8. oodatakse, et elektronkiir hakkaks üles liikuma
 9. pannakse joonistamise vähv samaks mis taustki
10. joonistatakse joon
11. - 12.arvutatakse joonte koordinaatide väärtused
13.  pannakase joonevärv nähtavaks
14. joonistatakse joon
15. suurendatakse nurka 2 kraadi (selle muutes saab pöörlemiskiirust muuta)
16. - 17. nii ei lähe arvud liiga suureks juhul kui programm peaks olema nt. screen saver
- Siin on kasutatud lisaks math.h-s deklareeritud library funktsioone sin() ja cos() leidmiseks.
- sin() ja cos() võtavad argumendiks nurga radiaanides sellepärast tehe (2*3.14*n/360)
-  programm töötab igaveses tsüklis millest saab tulla välja vaid Ctrl - c'ga. 
see pole ilus   eelviimast rida ei täideta tegelikult kunagi!
- vga_waitretrace() on väga kaval asi. See võtab vilkumise ära. 
Proovige programm käivitada  väljakommenteeritud wait_retrace'iga.

waitretrace tuleb anda peale millegi ekraanile saatmist: mõttekäik on selline:

  • kõigepealt kirjutatakse tausta värviga vana joon üle ja joonistatakse uus joon (9. 10. 13. 14)
  • seejarel teeb programm pausi (8.) hoides viimati pantud pilti ekraanil ja ootab kuni elektronkiir ekraani alla jõuab. Kui elektronkiir hakkab alt üles liikuma, siis programmi koodi täitmine jätkub ja loodetavasti suudab programm videomälu täis kirjutada enne, kui elektronkiirt hakkab alla liikudes uut kujutist ekraanile kandma. Seejärel jääb programm ootama kuni elektronkiir jälle alla jõuab ja teeb taas kiirelt vajalikud arvutused jne ...

Matemaatiline pool:

while(1)
    {
        y=145*sin(6.28*n/360);
        x=80*cos(6.28*n/360);
        vga_drawline(150-x, 200-y, 150+x, 200+y);
        n++;
        if (n = 360)
            n = 0;
    }

Need valemid ei vaja suurtele poistele selgitamist, aga väikesed võiks ka asjaga kursis olla:

Töötab igavene tsükkel ja muutuja n kujutab nurga pöörlemist vastu päeva. Et n liiga suureks ei läheks, siis arvestame asjaoluga, et 360 kraadi on sama hea kui 0 kraadi. y ja x on muutuvad ja nad on pöörleva punkti raadiuse projektsioonid y ja x - teljele.

                y
sin(nurgast) = ---
                r
               x
cos(nurgast) = ---
                r

Selline on kõige mugavam üleminek nn. polaarsetest koordinaatidest (r, n) ristkoordinaatidesse (x, y). Joone tõmbamisel paneme paika tsentri punkti (150, 200) ja kujutletav pulk hakkab pöörlema ümber oma keskpunkti (kui ta juba ekraanil liigutab, siis polegi ta enam nii 'kujutletav'). Nagu võib märgata tahavad siinus ja koosinus argumenti radiaanides: arvestame üleminekul kraadidest radiaanidesse, et 360 kraadi = 2*3.14 radiaani:

                   2 * 3,14 * samanurk kraadides
nurk radiaanides = -------------------------------
                            360

Et ring ikka ekraanil ring oleks tuleb veidi korrigeerida, sõltuvalt vajadusest y ja x funktsioone kordajatega: 145, 80.

Trig.gif