Egy DirectX váz      

 Annak, aki DirectX programozásra adja fejét javaslom, hogy kezdje egy tankönyvvel. Olvassa el egyszer az alapokat, legyen tisztában a kifejezésekkel, lássa át az egész rendszer működését. Miután ezek mind lekristályosodtak, csak azután álljon neki magának a kódolásnak. Egy könyvtárban mindig akadnak Windows programozási tankönyvek (most az egyetemi könyvtárakról beszélek), és ha az embernek szerencséje van, az éppen aktuális DirectX könyv is bent van.

Természetesen, nem árt egy kevés (J) matematikatudás, illetve nagyon jóba kell lenni a Windows programozásával, mert ezek nélkül csak kínlódás lesz az érthetetlen DirectX kódrészletek böngészése. Mondanom sem kell, hogy az OOP is igencsak szükséges lesz, a C++ -t pedig már nem is említem, hisz ez magától értetődő.

Ha ezek után a bevezető után még mindig van remény, hogy a cikket el fogják olvasni, hát akkor…

Vágjunk bele

Most a DirectDraw-t fogom bemutatni, később talán, ha lesz érdeklődés, akkor tovább fogunk lépni más irányokba is, mint pld. Direct3D, DirectSound stb.

Mi is ez a DirectDraw? A DirectDraw egy szoftver interfész, amelyet felhasználva direkt kapcsolatot hozhatunk létre a programunk, illetve a videokártyánk közt. Szoros kapcsolatban áll a Windows GDI-vel (erről többet az MSDN-ből lehet megtudni) ami alatt azt értjük, hogy a GDI parancsok működnek a DirectDraw alatt is, ugyanazokat a típusú HANDLE-kat használja mindkettő stb.

A DirectDraw egy eszköz független megoldás a játékkészítőknek, demoíróknak, és más hasonló csodabogaraknak. Eszköz alatt szinte csak videokártyát értek ebben a pillanatban, mert valóban másra nem is nagyon szokta használni egy mezei programozó, mint animál, rajzol, mozgat, és hasonló dolgokat művel.

Az előnyeiről nem fogok sokat mondani, mert mindenkinek nyilvánvaló, hogy abból rengeteg van.  Itt megemlíthetjük a sebességet (hisz "direkt" kapcsolatban áll a videomemóriával), a hardverfüggetlenséget (megírom az én S3 Virge gépemen, és a szomszéd GeForce 4-esén is ugyanúgy fut csak gyorsabbanJ), a DirectX automatikusan a legoptimálisabb, és legperformensebb képességeket fogja választani, amelyekkel a videokártya fel van vértezve, a platformfüggetlenséget (vagyis inkább Windows függetlenség), és még más rengeteg pozitív tulajdonság.

Nézzük a rókát

Rövidke bevezetőnk után már nem maradt más hátra, mint végigvezetni önöket egy programon, mely a DirectDraw-t fogja inicializálni, ki fogunk rajzolni egy szép és érdekes képet a képernyőre, majd mozgatni fogunk valamit a képernyőn, amihez a DirectDraw flipping (képernyőváltás, de ha ezt így lefordítom, akkor senki se fogja tudni miről van szó) képességeit fogjuk használni.

Egy dolgot még el kell mondanom, bármivel is kerül lefordításra a program, hozzá kell még linkelni a ddraw.lib statikus könyvtárat, hogy a DirectDraw rutinokhoz hozzáférésünk legyen.

Az első dolog, amit létre kell hoznunk, ha DirectDrawt akarunk használni, az egy példánya a DirectDraw-nak, ami nekünk magát a display adaptert fogja jelenteni. Ez pedig a következő módon történik:

HRESULT      ddrval;   // Ez fogja a visszatérési kódot tartalmazni
LPDIRECTDRAW lpDD;     // Ez lesz a DirectDraw objektumunk
 
ddrval = DirectDrawCreate(NULL, &lpDD, NULL); 
if(ddrval == DD_OK) 
{ 
    // lpDD egy érvényes DirectDraw objektum. 
} 
else 
{ 
    // A DirectDraw objektumot nem tudta létrehozni. Hiba!!!
} 

A DirectDrawCreate függvény első paramétere egy GUID (Global Unique ID) amely a grafikus kimeneti egységet jellemzi, viszont a legtöbb esetben ez mindig NULL, ami arra készteti a függvényt, hogy az alapértelmezett képernyőt használja. Amennyiben mégis használni akarjuk ezt a paramétert, a következő két érték közül válasszuk ki azt, amelyik nekünk tetszik: DDCREATE_EMULATIONONLY (arra kényszeríti a függvényt, hogy csak, és kizárólag emulációt használjon, bármilyen szuper, hardveres gyorsítónk is lenne) illetve DDCREATE_HARDWAREONLY, amely az előző ellentettje.

A második paraméter LPDIRECTDRAW típusra mutató, amely magának a DirectDraw objektumnak a címét (LPDIRECTDRAW) jelenti, a harmadik paraméter pedig fenntartott a Microsoft jövőbeli fejlesztéseihez. A függvény visszatérési értéke egy HRESULT, amely ha nem DD_OK, akkor valami gond volt, és általában a hibakódot tartalmazza.

Most, hogy megvan a DirectDraw objektumunk, be kell állítanunk egy kooperációs módot, amely legtöbbször a teljes képernyős, exkluzív mód beállításában merül ki. Ez azt jelenti, hogy a programunk megkapja a teljes képernyőt, kényére, kedvére azt csinálhat vele, amit akar, más program nem írhat (rajzolhat) a képernyőre, sőt, még csak nem is lesznek láthatóak, de azért jól megvannak valahol a háttérben. Ezt a kooperációs módot a következőképpen állíthatjuk be:

 
ddrval = lpDD->SetCooperativeLevel(hwnd, DDSCL_EXCLUSIVE | 
    DDSCL_FULLSCREEN); 
if(ddrval == DD_OK) 
{ 
    // Siker! 
} 
else 
{ 
    // Az exkluzív mód beállítása sikertelen volt
    // De azért a program még futhat így is...
} 

Ha a visszatérési érték nem volt DD_OK azért a program még futhat, arra azért elég nagy esély van, hogy ha valamilyen speciális hardver által gyorsított funkciót akarunk elérni, az nem fog sikerülni. A hwnd paraméter egy olyan ablaknak a handle-je, amelyet létrehoztunk elkezdünk DirectDraw inicializálással foglalkozni. Ez azért szükséges, hogy a DirectX tudja, hogy mikor fog véget érni a programunk, ugyanis ennek az ablaknak az üzenetei alapján fogja a DX lezárni magát.

Ha beállítottuk a kooperatív módot, akkor már nem marad más hátra, mind a képernyőfelbon­tást megváltoztatni egy olyanra, amely nekünk tetszik, én például a 640x480x32bit –es felbontást választottam. Ez a következőképpen történik:

ddrval = lpDD->SetDisplayMode(640, 480, 32); 
if(ddrval == DD_OK) 
{ 
    // Siker!!!
} 
else 
{ 
    // Sikertelen próbálkozás
    // Vagy a videókártya nem bírja ezt a felbontást,
    // vagy más program már exkluzív módban van.
} 

Gondolom, ez nem szorul túl sok magyarázatra, a SetDisplayMode függvény első paramétere az oszlopok száma, a második a sorok száma, a harmadik pedig a színmélység. Negyedik illetve ötödik paraméternek berakhatjuk a Refresh Rate-tet illetve egy érdekes nosztalgikus módot (amazoknak, akik még mindig a 13h módot sírják vissza): DDSDM_STANDARDVGAMODE. Ha ezt megadtuk ötödik paraméternek, akkor nem a 320x200x8pp lesz beállítva, hanem a klasszikus 13h.

Amennyiben meg szeretnénk ismerni a videokártyánk képességeit, használjuk az EnumDisplayModes függvényt, amely felsorolja az összes rendelkezésére álló videó módot.

Miután megvagyunk a videó mód beállításával, létre kell hoznunk a rajzfelületeket (surface), amelyekre rajzolni fogunk. Mi "flipping surface"–szeket fogunk létrehozni, ami azt jelenti, hogy a háttérből (back buffer) az előtérbe "rántja" a képet (front buffer) címcserével. Ha "blitting surface" –szeket hoztunk volna létre, akkor ott bitmásolással oldotta volna meg a két kép cseréjét.

Ezeket a felületeket a CreateSurface függvénnyel hozzuk létre, a következő módon:

// Létrehozunk egy elsődleges (primary) felületet, egy másodlagossal
// amit háttérnek fogunk használni
DDSURFACEDESC ddsd; //Ez a Surface Descriptor struktura
ddsd.dwSize = sizeof(ddsd); 
ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT; // Ez megmondja, hogy
// mely tagok fognak információt tartalmazni. Jelen esetben
// a dwCaps illetve a dwBackBufferCount
ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE | 
    DDSCAPS_FLIP | DDSCAPS_COMPLEX; 
// itt beállítottunk egy primary surface-t, megmondtuk, hogy flip
// felületekről van szó, illetve azt hogy complex felületeket használunk 
ddsd.dwBackBufferCount = 1; 
//megmondtuk, hogy egy darab backbufferünk lesz
LPDIRECTDRAWSURFACE lpDDSPrimary, lpDDSBack; 
 
ddrval = lpDD->CreateSurface(&ddsd, &lpDDSPrimary, NULL); 
if(ddrval == DD_OK) 
{ 
    // lpDDSPrimary az elsődleges rajzfelület 
} 
else 
{ 
    // Súlyos hiba... a program tovább nem futhat
    return FALSE; 
} 

 

Miután a Primary Surface-t létrehoztuk, nem marad más hátra, mint a backbuffert létrehozni:

ddscaps.dwCaps = DDSCAPS_BACKBUFFER; 
ddrval = lpDDSPrimary->GetAttachedSurface(&ddcaps, &lpDDSBack); 
if(ddrval == DD_OK) 
{ 
    // lpDDSBack a másodlagos (backbuffer) rajzfelület 
} 
else 
{ 
    return FALSE; 
} 

 

Ezek voltak az alapvető lépések, hogy inicializálhassuk a DirectDrawt. Ezentúl már valójában csak a Windows GDI függvényeket fogjuk használni, hogy ezekre a felületekre rajzolhassunk. Én például a következő műveleteket végeztem el ennek érdekében:

if (lpDDSPrimary->GetDC(&hdcFore) == DD_OK && lpDDSBack->GetDC(&hdcBack) == DD_OK)

{

      int i,j;

      for(i=0;i<255;i++)

      {

            for(j=0;j<255;j++)

            {

                  int v = (int)(1255*cos(j)*sin(i))%255;

                  COLORREF c = RGB(v,i,j);

                  SetPixel(hdcBack,i,j,c);

                  SetPixel(hdcFore,i,j,c);

            }

      }

    lpDDSPrimary->ReleaseDC(hdcFore);

    lpDDSBack->ReleaseDC(hdcBack);

}

 

Ahol a hdcFore illetve hdcBack változók teljesen hétköznapi HDC. Ezek után létrehozok egy Timert, majd az ablaknak küldözgetett WM_TIMER üzenetben fogom csereberélgetni a surface-szeket, miután kirajzoltam valami mozgó dolgot. De mindezek jobban kiderülnek, ha megtekintik a forráskódot.

Miután programunk befejezte a pályafutását, ne feledjük el a memóriát felszabadítani. Most pedig javallom, a példaprogramot valamilyen szövegszerkesztőben megnyitni, és átböngészni, amíg még friss az anyag.

Deák Ferenc