Grafické aplikace ve Visual C++ (3.)


V této lekci si konečně povíme o základních pojmech a principech DirectDraw. V první části si rozebereme důležité pojmy jako jsou například povrchy (surfaces) a v druhé části vám vysvětlím, jak vlastně DirectDraw pracuje. Nakonec si ukážeme konkretní příklad, jak vytvořit objekt DirectDraw a nastavíme další vlastnosti aplikace. Popisuju zde jen režim fullscreen (celoobrazovkový režim), ale DirectDraw samozřejmě pracuje i v okně.

3.1. Základní pojmy

Základem každé aplikace založené na principu DirectDraw je jeden objekt typu IDirectDraw7 (je to vlastně ukazatel na rozhraní COM), pomocí kterého vytvoříte všechny ostatní objekty potřebné pro kreslení apod. Takže první, co musíte udělat je, že vytvoříte tento objekt.

Sprite je vlastně vámi vykreslovaný obrázek. Sprite je většinou vytvořen z bitmapy, kterou načtete z externího souboru. Pokud budete mít pohybující se letadlo, právě letadlo bude sprite.

Další velmi důležitou součástí je tzv. povrch (surface). Povrch je vlastně buffer tzn. kousek paměti, buď ve video paměti nebo v systémové RAM. Je to něco podobného jako kontext zařízení (DC - viz minulá lekce). Do tohoto bufferu zapisujete data, které posléze vidíte na monitoru v podobě obrázku.

Pokud budete používat 8-bitovou barevnou hloubku tzn. 256 barev, měli byste vytvořit objekt palety. Pak je tento objekt velice důležitý, jinak se bitmapy zobrazí chybně (mají proházené barvy a některé barvy dokonce chybí). Program, který pracuje v 16-bitech (65 tisíc barev) žádnou paletu nepotřebuje, ale běží pomaleji, takže pokud nevlastníte moc vykonný počítač doporučuji používat 8-bitů.

Dalším užitečným objektem je tzv. clipper. Abyste pochopili jak pracuje musíte znát následující látku. Prozatím vám musí stačit, že zajišťuje tzv. clipping, což už jste možná slyšeli. Jednoduše řečeno Clipper zabraňuje vykreslování spritů mimo monitor.

3.2. Základní principy DirectDraw

Jak jsem se již zmínil DirectDraw, dokáže vykreslovat pouze ve 2D tzn., že všechny povrchy jsou ploché (mají dvě souřadnice x a y). Bitové kopírování mezi povrchy, což je vlastně kopírování jednotlivých bitů vašich bitmap, se v terminologii DirectDraw nazývá blitting (bit block transfer - přenos bloku bitů).

Máme dva základní povrchy :

  • Front surface (FS)
  • Back surface (BS)

Front surface (přední povrch) je, jak napovídá název, ten povrch, který je právě vidět na monitoru. Back surface (zadní povrch) je ukryt někde v paměti jakoby za FS. Důležité je, že vždy zapisujete jen do BS nikdy do FS. Kdybyste totiž zapisovali do FS přímo, docílili byste podobného efektu jako u GDI.

Takže vy zapíšete nějaké data do BS, ale co dál?
DirectDraw má jistou funkci, která dokáže oba povrchy prohodit. Takže FS je pak vzadu a není vidět a BS je vpředu a normálně viditelný. Toto prohození je velmi rychlé takže lidské oko ho samozřejmě nepostřehne. Funkce navíc vždy počká na tu kratičkou dobu, kdy se paprsek monitoru přesouvá z pravého dolního rohu do levého horního, takže vlastně nemáte žádnou šanci postřehnout nějaké problikávání ani kdybyste se rozkrájeli.

Tomuto systému prohazování povrchů se říká flipping. Flipping totiž prohazuje ukazatele na oba povrchy a nepoužívá běžný blitting, který se používá pro přenos bitů mezi povrchy a který je časově mnohem náročnější.

Velikost těchto dvou bufferů záleží na rozlišení, které právě používáte. Tyto buffery se zásadně vytváří ve video paměti, která je rychlejší než systémová RAM, zvláště u moderních grafických karet.

Dále si většinou vytváříte tzv. OffScreen surfaces, což jsou povrchy, které jsou mimo flipovací smyčku. Tyto povrchy používáme pro bitmapy, které teprve budeme chtít zobrazit tzn. jako pomocné buffery. Z těchto povrchů se pak blitují data do BS. Tyto buffery se vytvářejí v RAM a tudíž mohou být větší než FS a BS. Ve skutečnosti se vám nepodaří ve video paměti vytvořit buffer, který je větší než FS. Ledaže máte grafickou kartu, která má hodně paměti a podporuje funkci Wide surfaces (široké povrchy).

Celý systém vypadá následovně :

Pokud budete mít rozlišení 640x480 a 8-bitů barev (256 barev) FS vám zabere přesně 640 x 480 (plus nějaké informace o povrchu), což je asi 300 kB paměti tzn. že když vytvoříte FS a BS musíte mít alespoň 600 kB video paměti na grafické kartě. Pokud tomu tak není, budete muset vytvořit BS v RAM, ale to se silně nedoporučuje. Samozřejmě pokud zvýšíte rozlišení nebo hloubku barev, paměťové nároky se rovněž zvýší, takže na to pozor. Dnešní grafické karty mívají běžně alespoň 16 MB paměti a tam se vám to vejde s přehledem. Dále je možno vytvořit více BS například dva, pak se systému říká Triple buffering (máme celkem tři buffery : dva back + jeden front) a to už jste určitě slyšeli.

Každá aplikace DirectDraw pracuje tak, že se snaží prohazovat oba povrchy jak nejrychleji to jde a přitom se občas něco zapíše do BS. Prohazovací funkce čeká až se dokreslí všechny blittovací operace, takže se nemusíte bát, že by se něco nedokreslilo nebo snad dokonce problikávalo! Čím více toho vykreslujete, tím je prohazování pomalejší.

Jak ale zařídit toto rychlé prohazování?
Určitě nezkoušejte psát nekonečnou smyčku for(;;), protože pak by se aplikace zablokovala a nepřijímala by žádné zprávy Windows a ty budeme potřebovat. Třída CWinApp má členskou funkci Run(), která spouští smyčku zpráv. Vy tuto funkci můžete přepsat a upravit tak, že aplikace normálně zpracovává zprávy Windows (windows messages), ale navíc volá vaši funkci, která obnovuje obraz a prohazuje povrchy. Toto provádí pouze pokud nemá, co na práci tzn., že nepřicházejí žádné zprávy od Windows.

3.3. Objekt DirectDraw

Takže teď již víme, že každá aplikace založená na DirectDraw má jeden objekt typu IDirectDraw7.

Jak ale tento objekt vytvoříme?
Za prvé musíme vložit hlavičkový soubor ddraw.h (nejlépe do souboru StdAfx.h) a za druhé přilinkovat dynamické knihovny ddraw.dll a dxguid.dll. Knihovny se vkládají v menu Project/Settings. Na kartě Link se do políčka Object/library modules vloží ddraw.lib a dxguid.lib. To je vše. Teď nám kompilátor automaticky vloží hlavičkový soubor ddraw.h a linker přilinkuje knihovnu ddraw.dll a dxguid.dll.


Poznámka: Pro níže uvedenený kód musíte mít nainstalované DirectX SDK 8.0, které bylo na řijnovém ChipCD. Dále zkontrolujte v Options VC++, že na kartě Directories, je cesta k hlavičkovým souborům naistalovaného SDK, pokud ne, musíte položku přidat sami.

Nyní můžeme použít globální funkci DirectDrawCreateEx() k vytvoření objektu DirectDraw. Funkce má následující deklaraci :

HRESULT DirectDrawCreateEx( GUID FAR *lpGUID, LPVOID *lplpDD, REFIID iid, IUnknown FAR *pUnkOuter );

  1. První parametr je globální unikátní identifikátor grafického ovladače. Pokud zadáte NULL, program vybere současný grafický ovladač. Navíc můžete zadat tyto hodnoty :
    • DDCREATE_EMULATIONONLY – využívá se pouze softwarová emulace tzn. že není podporováno žádné hardwarové urychlování
    • DDCREATE_HARDWAREONLY – využívá se pouze hardwarové vybavení grafické karty, pokud karta nepodporuje dané prvky, funkce vrací DDERR_UNSUPPORTED.

  2. Druhý parametr je ukazatel na ukazatel na objekt DirectDraw, který chceme vytvořit. Ve třídě CControl si deklarujeme členskou proměnou typu LPDIRECTDRAW7 m_lpDD, což je ukazatel na DirectDraw. Tento ukazatel potom dosadíme jako druhý parametr funkce. Pozor! Je to ukazatel na ukazatel!.

  3. Tento parametr musí být nastaven na hodnotuIID_IDirectDraw7. Je identifikátor rozhraní COM.

  4. Třetí parametr je pro integraci programování technologií COM (Component Object Model). Nyní funkce vrací chybu pokud zadáte něco jiného než NULL.

3.4. Nastavení dalších vlastností aplikace

Pomocí ukazatele na objekt DirectDraw nastavíme, jak se bude naše aplikace chovat vůči jiným programům Windows a jaké rozlišení a hloubku barev budeme používat.

Spoluprácí s ostatními programy zajišťuje tato funkce:
HRESULT SetCooperativeLevel(HWND hWnd, DWORD dwFlags);

Funkce má následující dva parametry :

  1. hWnd - To jest handle na hlavní rámcové okno – ten se zjistí velmi jednoduše: zavolejte funkci GetSafeHwnd(), která vrátí platný handle.

  2. dwFlags - Příznaky, které popisují, jak se vaše aplikace chová k ostatním. Mohou se samozřejmě kombinovat. My použijeme následující :

    • DDSCL_EXCLUSIVE – zajistí, že aplikace má výhradní právo. Tento příznak musí být použit s DDSCL_FULLSCREEN
    • DDSCL_FULLSCREEN – indikuje, že aplikace bude fullscreen (celoobrazovková). Musí být samozřejmě použit s DDSCL_EXCLUSIVE.
    • DDSCL_ALLOWREBOOT – povoluje klávesovou zkratku Ctrl + Alt + Delete při výhradním režimu tzn. že můžete z aplikace vyskočit přes Správce úloh. To je důležité, když nevíte jestli vám aplikace náhodou neshodí Windows.
        Příznaků je mnohem víc, ale my si vystačíme s těmito třemi.
Druhým velice důležitým nastavením je rozlišení a hloubka barev. Dále tedy nastavíme grafický režim pomocí této funkce:
HRESULT SetDisplayMode(DWORD dwWidth,
                       DWORD dwHeight,
                       DWORD dwBPP,
                       DWORD dwRefreshRate,
                       DWORD dwFlags);

Tato funkce má pět parametrů:
  1. dwWidth - Rozlišení v horizontálním směru v pixelech. Můžete hodnotu načíst z registrů, z ini souboru nebo si definovat konstanty v souboru. Možností je spoustu a zaleží na vás, jakou se vyberete. V příkladu definuji konstantu 640 pixelů.
  2. dwHeight - Rozlišení ve vertikálním směru v pixelech. Nastavíme 480 pixelů.
  3. dwBPP - Hloubka barev v bitech na pixel. Nastavíme 16 bitů.
  4. dwRefreshRate - Obnovovací frekvence monitoru. Zde můžete nastavit obnovovací frekvenci se kterou se bude váš monitor obnovovat. Problém je v tom, že pokud nastavíte natvrdo nějaké číslo, nemusí (a taky nejspíš nebude) program běhat na jiném počítači, protože ne všechny monitory zvládají vaše nastavení. Když zadáte 0, použije standardní nastavení DirectDraw (toto nastavení se dá nastavit v DXDiag). Takže nastavte 0.
  5. dwFlags - Příznaky pro budoucí použití:) Nastavte 0.

3.5. Příklad

Nyní dáme všechny nové poznatky dohromady. Ve třetím kurzu o VC++ jste vytvořili třídu CControl, kde vytvořte členskou funkci třeba DDInit(HWND hWnd). Budete ji volat vzápětí za funkcí WinInit() z InitInstance() a předáte ji handle vašeho okna. Ve třídě CControl také vytvořte členskou proměnnou m_lpDD typu LPDIRECTDRAW7 a nadefinujte konstanty RES_X = 640, RES_Y = 480 a RES_BITDEPTH = 16.

HRESULT CControl::DDInit(HWND hWnd)
{
    HRESULT dwResult;

    // DirectDraw object creation
    dwResult = DirectDrawCreateEx(NULL, (void**)&m_lpDD, IID_DirectDraw7, NULL);
    if(dwResult != DD_OK) {
        TRACE("Cannot create direct draw object due %d\n", dwResult);
        return dwResult;
    }

    // Setting cooperative level
    dwResult = m_lpDD->SetCooperativeLevel(hWnd, DDSCL_EXCLUSIVE|
                                                DDSCL_FULLSCREEN|
                                                DDSCL_ALLOWREBOOT);
    if(dwResult != DD_OK) {
        TRACE("Cannot set cooperative level due %d\n", dwResult);
        return dwResult;
    }

    // Setting display mode
    dwResult = m_lpDD->SetDisplayMode(RES_X,RES_Y, RES_BITDEPTH, 0, 0);
    if(dwResult != DD_OK) {
        TRACE("Cannot set display mode due %d\n", dwResult);
        return dwResult;
    }

    return dwResult;
}

Celý funkční příklad si můžete stáhnout v sekci Downloads.

4 . Závěr

Aplikace z dnešní lekce akorát přepne rozlišení, nic víc. Přístě si vytvoříme front a back buffer a vytvoříme flipovací smyčku.

Těším se příště nashledanou.

Jiří Formánek