Számítógépes Grafika DirectX 1. gyakorlat
Információk Előfeltételek: Linalg (erős) C++ (gyenge) A gyakorlat két részből áll 6 (7) hét DirectX 6 (5) hét OpenGL
Számonkérés Rendszeres: 2-2 „kisbeadandó” DirectX-ből és OpenGL-ből Évvégi: Géptermi zárthelyi – egy sorsolt API-ban feladat A kisbeadandó pontok beszámítanak a géptermibe Beadandó: géptermit kiváltó, egy adott feladat DirectX ÉS OpenGL alatt történő megvalósítása Csak a kis beadandókkal bizonyos pontszámot elérő hallgatóknak
Információk A DirectX-es gyakorlati diák és példaprogramok innen érhetőek el: Kérdések a gyakorlattal kapcsolatban:
Mire lesz szükség? Visual Studio DirectX SDK (2010 júniusi a ZH pack, de lehet későbbit is használni): b-514e-41d3-ad02-438a3ba730ba b-514e-41d3-ad02-438a3ba730ba
Segédanyagok Alapozó anyagok és egyebek: gyakorlatok gyakorlatok
Opcionális irodalom Nyisztor Károly: Shaderprogramozás
Tartalom 1. DirectX általános áttekintése 2. WinAPI 3. A keretrendszer
Mi a DirectX? A DirectX a multimédiás fejlesztéseket segítő API-k gyűjteménye Minden API gyors hozzáférést biztosít a hardverhez Vagyis alapvetően olyan egységes felületeket biztosít, amelyeken keresztül lehetővé válik a különböző típusú hardverek hatékony elérése
Mi a DirectX? A DirectX tehát API-kat biztosít például grafikus különböző bemeneti illetve audio eszközök hatékony programozására
Miért kellett? DOS-os idők
Pro: Hatékony hardver-elérés (nincsenek rétegek az alkalmazás és a program között) „Bármit” megtehetünk Kontra: Rendkívül időigényes (ha két HW másképp biztosít egy szolgáltatást, akkor mindkettő módon le kell kódolni)
Miért kellett? Windows Windows 3.1: Megszűnik a hardver közvetlen elérhetősége Nem született életképes alternatíva a hatékony elérésre Windows 95: Windows Games SDK (feat. DirectX 1.0)
Miért nem az OpenGL? A Windows NT-ben már volt OpenGL támogatás még mielőtt kidolgozták volna a DirectX-et –tovább lehetett volna ezt is vinni Viszont ezt csak a mérnöki és CAD rendszerek erős hardvereivel lehetett akkoriban (ki)használni A DirectX-et azonban arra szánták, hogy minél több gépen, minél gyorsabb programot lehessen írni (=> nehézkesebb programozás kezdetben)
DirectX Graphics felépítése Ezt írjuk mi A programunkban a Direct3D API által biztosított függvényeket hívhatjuk Amiket lefordítja a grafikus kártya driverének „nyelvére” az ún. HAL device
Az első DirectX specifikáció alkotói Craig Eisler Alex St. John Eric Ergstrom
DirectX verziók 1/ 5 1.0: rengeteg hiba (de: Pitfall, EarthWorm Jim) 2.0: instabil, még mindig rengeteg hiba 3.0a: a későbbi Win95, illetve WinNT része. Nőtt a driver támogatottság, de az API-k hirtelen változtatásait a programozók nehezen követték 4.0: ez a verzió kimaradt
DirectX verziók 2/ 5 5.0: lényegesen fejlesztő közelibb, ActiveX control- ok. Sokkal mélyebben épül bele az operációs rendszerbe (Win98) 6.1a: Win98SE, további fejlesztések és javítások, RM és IM 7.0: szorosabb együttműködés a szoftver- és hardverfejlesztőkkel (elfogadja az ipar), Retained Mode-ot befagyasztják, textúrázást átdolgozzák (ipari javaslatra) stb.
DirectX verziók 3/ 5 8.0: nagy fejlesztések, átalakítások: Az addig különválasztott 2D-s (DirectDraw) és 3D-s (Direct3D) részeket összevonták (DirectX Graphics) Point-sprite-ok 3D-s textúrák Továbbfejlesztett D3DX könyvtár (!) => az RM-hez hasonló magasabb szintű szemlélet teljesítményvesztés nélkül N-patch-ek Vertex és Pixel (pontosabban Fragment) Shader-ek
DirectX verziók 4/ 5 9.0: alapvetően a 8.1 továbbfejlesztése Újabb shader verziók és a HLSL támogatása Kiforrott framework (ld. SDK dokumentációjában DXUT Programming Guide) További apróbb fejlesztések Mi ezzel foglalkozunk és a gyakorlat diái mind erről a verzióról fognak szólni.
DirectX verziók 5/ , 10.1: a Vista driver modelljéhez igazítás Geometry shaderek megjelenése, ő már írhat a memóriába Hardverek megkötése (nem kell Caps-eket nézni – elvileg) DX9 (XP-s viselkedés), DX9Ex (ő már WDDM, már ha van driver), DX10 SM 4.0 (illetve 10.1-ben 4.1) 11: új shaderek, általános számítások shadertámogatása stb.
A DirectX 9 jelenlegi komponensei DirectX Graphics: 2D és 3D-s grafikus megjelenítés DirectX Input: (DirectInput) bemeneti eszközök kezelése (egér, billentyűzet, joystick stb) DirectX Audio: (DirectSound) audio fejlesztéshez, hang lejátszás és felvétel
Tartalom 1. DirectX általános áttekintése 2. WinAPI 3. A keretrendszer
Ajánlott irodalom Charles Petzold: Programming Windows 2006-os WinAPI alapozó anyag: nAPI/Index-Intro.html nAPI/Index-Intro.html
Mi a WinAPI? A WinAPI olyan függvények gyűjteménye, amelyeken keresztül az operációs rendszer szolgáltatásait vehetjük igénybe Szolgáltatásokat, mint például memóriakezelés, I/O, energiagazdálkodás, felhasználói felület kezelés, halózati kezelés, biztonság stb.
Megvalósítás Dinamikus szerkesztésű könyvtárakkal (DLL- ek) Mi a C-s, alap függvénykészletet használjuk Különböző wrapperek is vannak a kényelmesebb használat érdekében, C++- hoz például MFC, ATL, WTL stb. Ugyanis kissé nehézkes a programozása:
Charles Petzold: Programming Microsoft Windows with C# "The original hello-world program in the Windows 1.0 SDK was a bit of a scandal. HELLO.C was about 150 lines long, and the HELLO.RC resource script had another 20 or so more lines. (...) Veteran C programmers often curled up in horror or laughter when encountering the Windows hello-world program."
Egy Windows program vázlata InicializálásFutásKilépés Erőforrások lefoglalása Egyéb inicializálások Reagálás a beérkező üzenetekre Egyéb működés Erőforrások felszabadítása
Inicializálás A megszokott inicializáláson kívül még néhány Windows-os dolgot is el kell intézni Ablakosztály regisztrálása, üzenetkezelők beállítása stb.
Futás A Windows-ban az eseménykezelés üzenet alapú Ha történik valami az alkalmazásunkkal (például az ablakot átmeretezik), akkor a rendszer küld egy üzenetet erről nekünk Megadhatunk egy függvényt, ami kezeli ezeket a beérkező rendszerüzeneteket (ha nem tesszük, akkor az alapértelmezett fut)
Kilépés A beérkező üzenetek feldolgozását egy üzenetfeldolgozó ciklusban végezzük
1. példa Egy egyszerű „Helló Világ”
A program Először nézzük, hogy milyen egy nagyon egyszerű „Helló Világ” program Egyetlen WinAPI hívást fogunk használni, ami egy ilyen típusú szövegablakot fog megjeleníteni:
MessageBox int MessageBox( HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType ); Visszatérési érték: a lenyomott gombnak megfelelő érték
Magyar jelölés Változónevek első néhány karaktere a változó típusára utal, például p a pointerre, i az integerre, b a bool-ra, h a handle-re, sz a 0-ra végződő stringre (char*), stb. Microsoft mindenütt alkalmazza (Simonyi Károly után kapta nevét ez a jelölésmód), ezért érdemes legalábbis ismerni - ha használni nem is
MessageBox int MessageBox( HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType ); hWnd: a megjelenítendő szöveges ablak szülő ablakának kezelője, ha értéke NULL, akkor nincs szülő
MessageBox int MessageBox( HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType ); lpText: a megjelenítendő szöveg
MessageBox int MessageBox( HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType ); lpCaption: az ablak fejlécének szövege
MessageBox int MessageBox( HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType ); uType: a megjelenő ablak típusa (milyen gombok legyenek rajta, viselkedés,...), ha 0 (ami egyúttal az MB_OK értéke is), akkor csak egy OK gomb lesz
Kérdések De mi a HWND, LPCTSTR, UINT? WinAPI-ban általában a típusneveket csupa nagybetűvel írjuk
Típusok HANDLE HANDLE: minden objektum létrehozásakor a Windows kioszt egy egyedi azonosítót, úgynevezett leírót az új objektumnak 32 bites Windows alatt ez 4 bájtos, 64 bites alatt pedig 8 bájtos (typedef a void*-ra) A leíró több mint egy puszta azonosító (pl. process id): azonosító ismeretében pl. csak üzenhetünk, leíró ismeretében akár meg is szüntethetünk (megfelelő jogosultságok esetén)
Típusok HANDLE A HANDLE egy általános leíró, bármié lehet A következőknél adott, hogy milyen típusú dolgok leírói: HWND: ablakleíró, nem csak a „form”-ok, hanem a gombok, beviteli mezők, panelek,... HINSTANCE: egy futó process leírója HDC: egy ablak rajzterületének leírója
Típusok LPCSTR „Long Pointer to Constant String” Az LP-ből az L csak történeti hagyaték String: ANSI karakterlánc, C stílusú, azaz karakterek olyan tömbje, amit 0 zár le VS-ben jobb klikk egy LPCSTR szövegen és ott Goto Declaration:
Típusok LPCTSTR Mutató TCHAR-ok 0-val végződő láncára A TCHAR tényleges típusa attól függ, hogy UNICODE kódolást használ-e a programunk vagy sem: #ifdef UNICODE typedef WCHAR TCHAR; #else typedef char TCHAR; #endif
Típusok WCHAR A WCHAR egy kétbájtos változó (pontosabban ő is egy typedef az unsigned short-ra): typedef wchar_t WCHAR;
Típusok Szöveg megadása ANSI változatú szöveg ( char -ok tömbje): "Valami szöveg..." UNICODE változatú szöveg ( wchar_t -k tömbje): L"Valami szöveg...” A UNICODE alapértelmezett VS2005-ben, kikapcsolni így lehet: #undef UNICODE
Típusok Szöveg megadása Karakterláncokat a legegyszerűbb a TEXT makró segítségével megadni, így nem kell törődnünk azzal, hogy éppen UNICODE-os az alkalmazás vagy sem: TEXT("Valami szöveg..."); Arra vigyázzunk, hogy amikor explicit adott, hogy mit kérnek, ne használjuk a TEXT makrót!
MessageBox használata Tehát a szövegkiírás így néz ki: MessageBox( NULL, TEXT("Helló világ!"), TEXT("Üzenet"), MB_OK); De hová írjuk ezt be?
WinMain Amikor konzolos alkalmazást írunk, akkor a programunk belépési pontja a main() függvény: int main(int argc, char *argv[]); Ennek Windows-os megfelője a WinMain
WinMain int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR cmdLine, int nShow) A visszatérési érték funkciója ugyanaz, mint a konzolos main esetén
WinMain int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR cmdLine, int nShow) hInstance: a futó programunk processzének leírója
WinMain int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR cmdLine, int nShow) hPrevInstance: ma már NULL-al tölti fel az oprendszer, régebben a programunk egy korábban indított példányának azonosítója került ide
WinMain int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR cmdLine, int nShow) cmdLine: a parancssori paraméterek stringje
WinMain int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR cmdLine, int nShow) nShow: az alkalmazás főablakának kezdeti megjelenítési (maximalizált, tálcára küldött,...) paraméterei (már ha van ablaka)
Függvényhívási konvenciók Egyvalami még magyarázatra szorul a WinMain definíciójával kapcsolatban: int WINAPI WinMain A WINAPI itt egy függvényhívási konvenció megadását jelöli 32 bites Windows-ok alatt a WINAPI a következő makró: #define WINAPI __stdcall
Függvényhívási konvenciók Tehát általánosan így adhatjuk meg a függvényeinket: vt K fv(param) {...} vt: visszatérési típus K: konvenció fv: függvénynév param: paraméterek listája
Néhány konvenció
Mi a WINAPI-n kívül a CALLBACK-et fogjuk csak használni az üzenetkezelő függvényeinknél, ami 32 bites Windows-on így néz ki: #define CALLBACK __stdcall
A program #include int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR cmdLine, int nShow) { MessageBox(NULL, TEXT("Hello világ!"), TEXT("Üzenet"), MB_OK); return 0; }
Szövegkezelés Előfordulhat, hogy egy változó értékét akarjuk kiírni Mi most egy formázást használó függvénnyel oldjuk ezt meg: _stprintf_s a tchar.h-ból A t a TCHAR-ra utal, a záró s pedig arra, hogy secure, vagyis azt is megadjuk, hogy maximum milyen hosszú bemenetet engedünk meg
_stprintf_s _stprintf_s( TCHAR* _Dst, size_t _SizeInWords, const TCHAR* _FormatString,...) A _Dst egy mutató annak a memóriaterületnek az elejére, ahonnan kezdve _SizeInWords (Word-ökben adott) hosszan a _FormatString-nek megfelelő szöveg kerül
_stprintf_s TCHAR* tSzoveg = new TCHAR[255]; int i = 15; _stprintf_s( tSzoveg, 255, TEXT("A változó értéke: %d"), i);
_stprintf_s A harmadik paraméterben megadott sztringben %d helyett az i változó értéke fog megjelenni Néhány formázó utasítás: %d: előjeles egész szám (int) %f: lebegőpontos szám (double) %s: string (nullával lezárt)
_stprintf_s A formázó stringbe beírt összes formázó utasításhoz kell átadni értékeket a függvényhíváshoz! Vagyis a _stprintf_s függvényhívás vége így kell kinézzen:..., ”%d %f %d”, 4, 3.5f, 2);
Feladat Írjuk ki az _stprintf_s felhasználásával két float változó értékét és azok összegét!
Tartalom 1. DirectX általános áttekintése 2. WinAPI 3. A keretrendszer
DirectX-es keretprogram Elérhető innen: te.zip te.zip
Működése WinMainCDXMyApp cApp; cApp.Run()
CDXAppBase Ez az általunk írt program ősosztálya, belőle származtatjuk a programunk „főosztályát” Ő hozza létre igazából az ablakunkat, a DirectX-et ő kapcsolja be és több háttérmechanizmust is adminisztrál Lényegében az inicializációval járó lépéssorozatot fedi el előlünk
A lényeg Nekünk a leszármazott osztályban a következő függvényeket kell megvalósítanunk: HRESULTInitD3D(); HRESULTInitDeviceObjects(); VOIDReleaseDeviceObjects(); VOIDCleanUpD3D(); VOIDFrameUpdate(); VOIDRender(); VOID HandleInput();
Működése A WinMain-ben létrehozzuk egy példányát az általunk írt CDXMyApp (a CDXAppBase-ből leszármazott) osztálynak Ennek meghívjuk az Initialize() függvényét Ez létrehoz egy ablakot Meghívja az InitD3D() függvényét a CDXMyApp- nak A Run() függvényt hívja meg ezután a WinMain()
CDXAppBase::Run() MSG msg; ZeroMemory( &msg, sizeof(msg) ); while( msg.message!=WM_QUIT ) { if ( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) ) { TranslateMessage( &msg ); DispatchMessage( &msg ); } else {
CDXAppBase::Run() else { FrameUpdate(); RenderBase(); HandleInput(); } CleanUpBaseD3D();
Végleges programváz InitD3D InitDeviceObjects FrameUpdateRender CleanUpD3D ReleaseDeviceObjects HandleInput
A program váza Ablak létrehozása Direct3D objektum létrehozása Direct3D Device objektum létrehozása Rajzolás Direct3D objektum megsemmisítése Direct3D Device megsemmisítése Ablak törlése Inicializálás Futás „Takarítás”
DirectX Graphics felépítése Ezt írjuk mi A programunkban a Direct3D API által biztosított függvényeket hívhatjuk Amiket lefordítja a grafikus kártya driverének „nyelvére” az ún. HAL device
DirectX Graphics felépítése Az alkalmazás és a hardver közötti szoftveres réteg két részből áll: Direct3D API HAL
Direct3D API A Direct3D API-t programozzuk az alkalmazásunkból Ez felületek és függvények halmaza, amiken keresztül elérhetjük az aktuális DX verzió által nyújtott összes szolgáltatást Azonban ez nem jelenti azt, hogy a hardver is támogatja mindet
Hardware Abstraction Layer A HAL-nak köszönhetően vonatkoztathatunk el a tényleges gépi konfigurációtól Rajta keresztül történik a kommunikáció a hardverrel Megtudhatjuk például tőle, hogy milyen szolgáltatásokat valósít meg az aktuális hardver a D3D felületén keresztül elérhetőkből
Hardware Abstraction Layer Ugyanis csak azokat a dolgokat tudja „lefordítani” a videókártya nyelvére ez a réteg, amiket meg is valósítanak az adott hardverben Mivel közvetlenül használja a hardver nyújtotta szolgáltatásokat (a DDI-n keresztül), ezért a hardver gyártók fejlesztik (amit ki is használnak, ld. 3D Mark botrányok)
Mi van a nem megvalósított szolgáltatásokkal? Úgynevezett pluggable software device- okban minden szoftveresen meg van valósítva Ilyen például az SDK-val jövő Reference Rasterizer Ami tehát az aktuális DX verzió összes szolgáltatását megvalósítja Pontosságra és nem sebességre optimalizált
Mi van a nem megvalósított szolgáltatásokkal? Amikor létrehozzuk a Device-ot (ld. később) megadhatjuk, hogy HAL-on vagy Reference Rasterizer-en keresztül dolgozzon-e Illetve ha van, egyéb, harmadik fél által gyártott psd-vel