Az elveszett VESA

Anno, még a kilencvenes évek elején hatalmas káosz kezdett el eluralkodni a videovezérlők piacán, már ami ezeknek a videovezérlőknek a programozását jelenti. Minden egyes hardver gyártó cégnek megvoltak a saját megoldásai, amelyeknek természetesen meglehetősen kevés közös tulajdonsága volt a konkurens cégek hasonló termékeivel, minden videokártya gyártó más módon inicializáltatta a bizonyos SuperVGA felbontásokat. Mivel nem volt egy jól átgondolt és logikus rendszer, mindenki csinálta amit jónak látott.

Szegény programozók meg csak ültek középen, és fogták a fejüket: ha megírom a programot a Trident videokártyás gépemen, biztos hogy futni fog a kliens S3 kártyáival? Ha nem használok SuperVGA módot, lehet, hogy nem fogja átvenni a programot, mert csúnyának tartja a 16 színt? Ilyen és ehhez hasonló kérdések ezrei merültek fel a kétségbeesett programozók fejében, amikor valaki "magasabb szinteken" is megelégelte a káoszt, és a Video Electronics Standards Association bevezette a standardizált SuperVGA felbontásokat, ezeknek a beállítási módszerét, lekérdezését, és más hasonló hasznosságokat.

Mivel azonban a Microsoft ® Windows™ is ezekben az időkben kezdett lábra kapni, a világ inkább a Windows Driverek felé fordult, és a VESA valahogy a háttérbe szorult. Mi mégis úgy gondoltuk, hogy legalább egy cikk erejéig fogunk ezzel a méltatlanul elfeledett standardizálási kísérlettel, és bemutatjuk, miként lehetett annak idején "csúcsgrafikázni".

Ahhoz, hogy belevághassunk a munkába, szükségünk lesz egy DOS alatti C fordítóra, kevés ASM illetve C tudásra, és rengeteg lelkesedésre. Ha mindezeket beszereztük, akkor kezdjük el…

Én itt vagyok...

Ahhoz, hogy megtudjuk, hogy a videokártyánk támogatja-e a VESA-t (ugyanis nem minden videokártya gyártó fogadta el a VESA-t, de ne aggódjunk, minden mai kártya valamilyen mértékben támogatja) a következőt kell művelnünk:

int Init()

{

char tmp;

asm {

  mov ax,0x4f00

  mov di, offset InfRec

  push ds

  pop es

  int 0x10

  mov tmp,al

 }

 return (tmp==0x4f);

}

vagyis, hogy érthetőbb legyen: a 10h megszakításnak adjunk át egy 4f00h-t az AX-be, illetve az ES:DI –be egy TInfRec struktúrának a címét, amely a következőképpen néz ki:

struct TInfRec {

 char _Signature[4];

 unsigned int _Version;

 char* _pszOEM;

 unsigned long _Abilities;

 unsigned int* _Modes;

 unsigned char _FormInfoRec[18];

 unsigned char _Data[238];

 unsigned char _nothing[256];

} InfRec;

 

Most jöjjenek a magyarázatok, mert gondolom, ez így nagyon száraz volt. A _Signature mindig azt tartalmazza, hogy "VESA". A _Version magasabbik rendű bájtjában a nagyobbik, alacsonyabb rendű bájtjában a kisebbik verziószám (ne feledjük, DOS alatt vagyunk, ahol az int 16 bites). A _pszOEM tartalmazza a gyártó azonosítóját, vagy bármi más információt, amit a gyártó szövegként a kártyához mellékelt. Az _Abilities nulladik bitje, ha be van állítva, akkor a DAC szélességén tudunk változtatni, ha nem akkor nem.

A _Modes tömb tartalmazza az összes videómódot, amit a kártya kezelni tud, a legutolsó ffffh. Ez nem videómód. A _Data első négy karakteréből alkotott long értéke megmondja, hogy hány darab 64k-s memóriablokk van a videokártyán, a többi nem publikus.

Az egész eddig leírtakból a legérdekesebb talán az a tömb, amely a videómódokat tárolja. És valóban, hisz ez mondja meg, hogy egyes videómódok mire képesek, mekkora a felbontásuk, a színmélységük, satöbbi. Természetesen, a következő kérdés, ami felmerül: Jó, Jó, de abba a tömbbe csak számok vannak, honnan tudom én meg egy számból, hogy a hozzárendelt videómód mire képes. A válasz pofonegyszerű, a következő függvénnyel:

void GetModeInfo(unsigned int mode)

{

 asm {

    mov di,offset mdInf

    push ds

    pop es

    mov ax,0x4f01;

    mov cx,mode

    int 0x10

   }

}

ahol a mdInf a következő struktúra:

struct TModInf {

 unsigned int _ModeAttrs;

 unsigned char _WinAAtrs;

 unsigned char _WinBAttrs;

 unsigned int _WinGran,

            _WinSize,

            _WinASeg,

            _WinBSeg;

 unsigned long _Handler;

 unsigned int _ScanLineW;

 unsigned int _HorzRes,

            _VertRes;

 unsigned char _CharWidth,

             _CharHeight,

             _NumPlanes,

             _BPP,

             _NumBanks,

             _MemModel,

             _BankSize,

             _Reserved[227],

             _Rest[256];

} mdInf;

 

Kicsivel bővebben:

A _ModeAttrs bitjei a következőket árulják el: be lehet állítani (0. Bit), a 10h megszakítás 0ah illetve 0eh karakterkiíró függvényeit támogatja (2.bit), színes mód (3. bit), illetve grafikus mód (4. bit).

Ami még érdekes ebből a struktúrából, az a _HorzRes illetve a _VertRes, amelyek magát a képernyő felbontást adják meg, illetve a _BPP ami a színmélységet tartalmazza. A _CharWidth illetve _CharHeight mezők határozzák meg egy karakter szélességét illetve magasságát ebben a felbontásban.

A _NumBanks megmondja, hány bank fogadja be a képernyőre kirakható pixelek összességét. A bank alatt egy lineáris zónát kell érteni, amelynek mérete általában 64k, de inkább a _BankSize-t használjuk erre, mivel ez biztosabb. A videomemória több, ilyen egymás utáni bankból áll, balról jobbra, és fentről lefele, és mindig meg kell vizsgálnunk, hogy az aktuális pixel, amit éppen ki akarunk rakni a hányadik bankba fog kerülni, de ez a folyamat lejjebb még bemutatásra kerül.

Most, hogy mindent tudunk a videómódról, nem ártana beállítani:

void SetMode(unsigned int mode)

{

 oldbank = 0 ;

 asm {

  mov di,offset mdInf

  push ds

  pop es

  mov ax,0x4f01;

  mov cx,mode

  int 0x10

  mov ax,0x4f02

  mov bx,mode

  int 0x10

 }

}

 

Amint látjuk, a függvény nemcsak beállítja a videomódot, hanem lekérdezi a módhoz tartozó információkat is. És most, hogy be van állítva ez a szépséges videomód, az alapművelet, egy pixel kirakása lesz a következő műveletünk. Viszont kijelentem, hogy ez nem a legoptimálisabb SVGA putpixel metódus, sokkal inkább oktatási célokra alkalmas: hogy NE írjunk SVGA putpixelt. Na, tessék:

void PutPixel(unsigned int x, unsigned int y, unsigned char col)

{                                                    

register unsigned long addr=((long)mdInf._ScanLineW*y)+x;

register unsigned int raddr = addr % 65536;

 newbank=addr>>16;

 if(oldbank!=newbank)

 {

  oldbank=newbank;

  asm {

   mov ax,0x4f05

   xor bx,bx

   mov dx,newbank

   int 0x10

  }

 }

 asm {

  mov ax,mdInf._WinASeg

  mov es,ax

  mov di,raddr

  mov al,col

  stosb

 }

}

 

Ez egy teljesen optimaliizálatlan putpixel, de akinek kedve van az csak fejlesztgesse tovább. Megfigyelhetjük, hogy miként kell vizsgálni, hogy túlléptünk-e az aktuális bankunkból, és ha igen, akkor hogy váltunk bankot. Ezt a legtöbb helyen nagyon leoptimizálják az aktuális felbontásra, és hajlamosak a programozók konstansokat használni, viszont ez a putpixel működik bármilyen felbontásra, ami 256 színen alapul. Igaz, hogy én is használtam egy 65536-ot, mint a bankméret, de általában ez mindenütt ennyi.

Utoljára meg kijelentem, hogy a mellékelt .h fájlt mindenki saját belátása szerint használhatja, viszont én nem vagyok felelős semmiféle mellékhatásért, kárért, bármiért, amit ennek a fájlnak a használata nyomán fedeztek fel a kedves olvasók. Még azt sem garantálom, hogy működik egyáltalán. Aránylag rég írtam, és nem nagyon fejlesztettem tovább, úgyhogy ez limitáltságában marad meg ilyennek amilyen.

Deák Ferenc