Kurz DirectX (8.)


V tΘto lekci zcela zakonΦφme komponentu DirectDraw a s chutφ se pustφme do dalÜφ nemΘn∞ zajφmavΘ Φßsti a to DirectInput. DirectInput zajiÜ¥uje programßtorovi p°φm² p°φstup k vstupnφm za°φzenφm PC. M∙₧ou to b²t ·pln∞ ty nejzßkladn∞jÜφ za°φzenφ  jako je klßvesnice a myÜ nebo ty pokroΦilejÜφ jako je joystick nebo volant (a samoz°ejm∞ mnoho dalÜφch za°φzenφ).

8.1 RozlouΦenφ s DirectDraw

I kdy₧ se s DirectDraw louΦφme, tak v∞zte, ₧e je toho jeÜt∞ hodn∞, co je pot°eba se nauΦit. I po dneÜnφ lekci vßm t°eba n∞kterΘ v∞ci nebudou zcela jasnΘ nebo vßs bude zajφmat n∞jak² konkrΘtnφ problΘm, kter² tu m∙₧eme spolu vy°eÜit. StaΦφ napsat email.

8.1.1 Ztrßcenφ povrch∙

Fakt, ₧e povrch se m∙₧e ztratit jsem Vßm zprvu zatajil, ale v dneÜnφ lekci se dozvφte, ₧e se takovß v∞c m∙₧e stßt a nauΦφte se, jak nalo₧it se ztracen²m povrchem.

Slovo ztracen² z nφ dost divn∞. Vlastn∞ si asi nedokß₧ete p°edstavit, ₧e by se kousek pam∞ti ztratil. Mßte pravdu! Povrch se vlastn∞ neztratφ, jen se dealokuje mφsto v pam∞ti a vy musφte tuto pam∞¥ znovu alokovat. Ukazatel na povrch je i po "ztracenφ" platn², tak₧e m∙₧ete zavolat Φlenskou funkci Restore(), kterß realokuje pam∞¥, ale u₧ nenahraje do povrchu p∙vodnφ bitmapu, to musφte ud∞lat zcela sami (pokud je to t°eba).

Kdy ke ztrßcenφ vlastn∞ dochßzφ?
Je to nap°φklad tehdy, kdy₧ zm∞nφte rozliÜenφ b∞hem chodu vaÜeho programu. Nap°φklad, p°epnete-li se do Windows pomocφ klßves Alt-Tab (nebo jinak), ztratφ se Front i Back buffer. M∞li byste zastavit flipovacφ smyΦku a spustit ji znovu a₧ tehdy, kdy₧ se u₧ivatel vrßtφ zp∞t do programu. P°i nßvratu se rozliÜenφ p°epne zp∞t a prßv∞ v teto chvφli (d°φve ne₧ spustφte smyΦku), musφte volat funkce Restore() vÜech povrch∙ kterΘ se ztratili.

Funkce Blt() a Flip() vracejφ hodnotu DDERR_SURFACELOST, kdy₧ povrch je ztracen² a je pot°eba volat metodu Restore(). Tak₧e byste asi m∞li testovat prßv∞ tuto nßvratovou hodnotu.

Jak ji₧ bylo °eΦeno, funkce Restore() pouze realokuje pam∞¥ pro povrch, ale u₧ nenahraje p∙vodnφ bitmapu. Pokud volßte n∞kde funkci Blt(), budete pot°ebovat testovat hodnotu DDERR_SURFACELOST a p°φpadn∞ povrch obnovit a naplnit p∙vodnφ bitmapou. V p°φpad∞, ₧e volßte funkci Flip(), tak tam staΦφ pouze obnovit hlavnφ povrchy (viz dßle).

Zprßva WM_ACTIVE
Abyste odhalili, kdy aplikace ztrßcφ fokus a p°ichßzφ o v²hradnφ prßva na fullscreen, musφte odchytnout zprßvu WM_ACTIVATE. Tu dostane okno ve chvφlφ, kdy je bu∩ aktivovßno nebo deaktivovßno. Zprßva s sebou nese dva parametry (jako ka₧dß zprßva) typu WPARAM a LPARAM, co₧ jsou 32-bitovΘ hodnoty (jako DWORD). Dolnφch 16-bit∙ (low word) parametru WPARAM  nßm °φkß, zda-li okno bylo aktivovßno nebo deaktivovßno (p°φpadn∞ jak²m zp∙sobem). M∙₧e nab²vat t∞chto hodnot:

Hodnota Popis
WA_ACTIVE Tato hodnota nßm °φkß, ₧e okno bylo aktivovßno nap°φklad pomocφ klßvesnice nebo funkcφ SetActiveWidows().
WA_CLICKACTIVE Tato hodnota pouze up°es≥uje, ₧e okno bylo aktivovßno tlaΦφtkem myÜφ.
WA_INACTIVE Okno bylo deaktivovßno.
Ostatnφ hodnoty nßs nezajφmajφ, ale jen pro ·plnost si je stejn∞ uvedeme.
Hornφch 16-bit∙ (high word) parametru WPARAM (druhß polovina WPARAM) nßm °φkß, zda-li je okno minimalizovanΘ Φi nikoliv. Druh² parametr LPARAM je
handle na okno, ale zßvisφ na hodnot∞ WPARAM. Pokud je dolnφch 16-bit∙ parametru WPARAM rovno WA_INACTIVE, pak je to handle okna, kterΘ je deaktivovßno (tzn. na naÜe okno). Pokud je hodnota WA_ACTIVE nebo WA_CLICKACTIVE, pak handle pat°φ oknu, kterΘ bylo deaktivovßno, aby naÜe okno mohlo b²t aktivovßno (tj. p°edchozφ aktivnφ okno).

Poznßmka: Hornφch a dolnφch 16-bit∙ z typu DWORD dostaneme pomocφ maker HIWORD() a LOWORD().

P°φklad
Nynφ ji₧ zaΦneme upravovat nßÜ p°φklad a dovedeme ho tak k ·pln∞ "dokonalosti". Za prvΘ bychom m∞li zastavit flipovacφ smyΦku, tak₧e musφme odchytit zprßvu WM_ACTIVE. Mßme definovanou proceduru okna MainWndProc(), ve kterΘ to celΘ provedeme. KonkrΘtn∞ do p°φkazu switch vlo₧φme ke zprßv∞ WM_SETCURSOR zprßvu WM_ACTIVE a do t∞la v∞tve napφÜeme nßsledujφcφ k≤d:

case WM_ACTIVATE:
     low = LOWORD(wParam);
    
// Start flipping loop if app is activated
     if(low == WA_ACTIVE || low == WA_CLICKACTIVE) {
         theApp.GetControl()->Activate(TRUE);
     }
    
// Stop flipping loop if app is deactivated
     if(low == WA_INACTIVE) {
         theApp.GetControl()->Activate(FALSE);
     }
     break;

Jak vidφte pou₧φvßme novou metodu t°φdy CControl Activate(), kterß jako parametr p°ijφmß hodnotu BOOL a nastavuje atribut m_bReady. Pro ·plnost jsem do t°φdy doplnil i metodu pro opaΦn² sm∞r, kterß vracφ BOOL, zda-li je smyΦka aktivnφ Φi nikoliv. Dßle si vÜimn∞te makra LOWORD(), kterΘ vracφ dolnφch 16-bit∙ hodnoty wParam (tj. parametr WPARAM). Jinak zßpis je velmi jednoduch²: pokud je okno aktivovßno (jak²mkoliv zp∙sobem), pak je smyΦka spuÜt∞na, v opaΦnΘm p°φpad∞ je smyΦka zastavena.

Nynφ se podφvßme do funkce Present() t°φdy CDisplay.

HRESULT CDisplay::Present()
{
     HRESULT hr;

     if( NULL == m_pddsFrontBuffer && NULL == m_pddsBackBuffer )
         return E_POINTER;

    while( 1 )
    {
        if( m_bWindowed )
           hr = m_pddsFrontBuffer->Blt( &m_rcWindow, m_pddsBackBuffer, NULL, DDBLT_WAIT, NULL );
        else
           hr = m_pddsFrontBuffer->Flip( NULL, 0 );

        if( hr == DDERR_SURFACELOST )
        {
            m_pddsFrontBuffer->Restore();
// Funkce Restore() pro front buffer
            m_pddsBackBuffer->Restore(); 
// Funkce Restore() pro back buffer
        }

        if( hr != DDERR_WASSTILLDRAWING )
            return hr;
        }
}

Zde nic upravovat nebudeme, ale vÜimn∞te si funkce Restore() pro oba hlavnφ povrchy. Jsou volßny pokud Flip() vracφ DDERR_SURFACESLOST, jak bylo zmφn∞no v²Üe.

Nynφ ovÜem musφme takto upravit vÜechny mφsta, kde volßme funkci Blt(), proto₧e ta takΘ vracφ DDERR_SURFACELOST. V naÜem jednoduchΘm p°φklad∞ je to jen na dvou mφstech a to ve t°φd∞ CSprite a p°i p°ekreslovßnφ pozadφ ve t°φd∞ CControl.

CSprite
Zde musφte p°idat nov² atribut, proto₧e je nutnΘ si pamatovat jmΘno bitmapy odkud se vytvß°φ povrch spritu. Zvolil jsem typ CString, ale m∙₧e to b²t jednoduch² °et∞zec char. Tento atribut inicializujeme p°i vytvß°enφ spritu ve funkci CreateStaticSprite() a kupodivu do n∞j zkopφrujeme cestu bitmapy, ze kterΘ vytvß°φ povrch. A proΦ pot°ebujeme tento °et∞zec? U hlavnφch povrch∙ jsme pouze realokovali pam∞¥, ale u t∞chto off-screen povrch∙ je navφc pot°eba obnovit p∙vodnφ obsah a prßv∞ k tomu bude slou₧it tento °et∞zec. Dßle musφte ve funkci DrawSprite() testovat nßvratovou hodnotu funkce Blt() na DDERR_SURFACELOST a v tomto p°φpad∞ obnovit povrch spritu a nahrßt do n∞j znovu bitmapu. Upravenß Φßst funkce vypadß takto:

//
// Draw sprite at specified location

dwRet = m_pDisplay->ColorKeyBlt(m_ptPos.x, m_ptPos.y, m_psurSpriteSurface->GetDDrawSurface(), &rcSrc);
if(dwRet != DD_OK) {
    if(dwRet == DDERR_SURFACELOST) {
       m_psurSpriteSurface->GetDDrawSurface()->Restore();
       m_psurSpriteSurface->DrawBitmap((TCHAR*)(LPCSTR)m_csBitmap, 0, 0);
    }
    else {
       TRACE("Cannot render sprite due %d\n", dwRet);
    }
}

VÜimn∞te si novΘ Φßsti v podmφnce s DDERR_SURFACELOST. Za prvΘ volßme metodu Restore() povrchu a za druhΘ volßme funkci DrawBitmap(), kterß je Φlenem t°φdy CSurface. Tato metoda zkopφruje obsah bitmapy ze souboru do povrchu a je p°φmo urΦena pro tyto ·Φely. A zde je to vÜe!

CControl a pozadφ
Za druhΘ musφme trochu upravit t°φdu CControl. Op∞t p°idejte nov² atribut, °et∞zec, v n∞m₧ bude ulo₧ena cesta k bitmap∞ pozadφ (je to stejn² princip jako u p°edchozφ t°φdy). A pak upravte funkci UpdateFrame() tam, kde p°ekreslujete pozadφ takto:

dwRet = m_theDisplay.Blt(0, 0, m_surBackground, rcBackground);
if(dwRet != DD_OK) {
    if(dwRet == DDERR_SURFACELOST) {
       m_surBackground->GetDDrawSurface()->Restore();
       m_surBackground->DrawBitmap((TCHAR*)(LPCSTR)m_csBackground, 0, 0);
    }
}

Op∞t je zde vid∞t stejn² postup: nejd°φve obnovφme povrch funkcφ Restore() a potΘ zkopφrujeme bitmapu vesmφru.

A to je v tΘto Φßsti vÜe. Nynφ si zkuste program spustit. PotΘ se v pr∙b∞hu p°epn∞te do Windows (nap°φklad p°es Alt-Tab). M∞lo by se sprßvn∞ p°epnout rozliÜenφ (do p∙vodnφho rozliÜenφ plochy) a aplikace mß z∙stat minimalizovanß dole na liÜt∞. Pak se p°epn∞te zp∞t (nap°φklad klepn∞te na tlaΦφtko na liÜt∞) a program by se m∞l cel² obnovit a vÜe pokraΦovat dßl, jako by se nechumelilo.

P°φklad si m∙₧ete stßhnout v sekci Downloads jako DirectDraw.exe.

8.1.2. Uvol≥ovßnφ objekt∙ DirectDraw

V ·pln∞ poslednφ Φßsti o DirectDraw se budeme zab²vat tφm poslednφm, co musφte ud∞lat p°i ukonΦenφ aplikace pracujφcφ na bßzi DirectDraw. Vytvo°ili jste p°ece n∞jakΘ objekty a ty se musφ zruÜit. DirectDraw a celΘ DirectX pou₧φvß COM (Component Object Model), tak₧e vlastn∞ nevlastnφte ukazatel p°φmo na objekt DirectDraw, ale pouze ukazatel na jeho rozhranφ, pomocφ kterΘho s objektem pracujete.

U COMu platφ, ₧e samotn² objekt se zruÜφ sßm, pokud neexistujφ ji₧ ₧ßdnΘ odkazy (reference) na jeho rozhranφ (interface). Tφm, ₧e vytvo°φte objekt DirectDraw pomocφ funkce DirectDrawCreateEx() zφskßte prßv∞ odkaz na jeho rozhranφ. Objekt COMu si to zapamatuje, proto₧e si intern∞ poΦφtß reference (inkrementuje nebo dekrementuje poΦet referencφ). Pokud toto poΦφtadlo dosßhne hodnoty 0, objekt se sßm zruÜφ. Jde pouze o to, uvolnit vÜechny rozhranφ, kter² na ten kter² objekt mßte. V naÜem p°φpad∞ je to jedinΘ rozhranφ IDirectDraw7. Rozhranφ se uvol≥ujφ pomocφ funkce Release(). Po zavolßnφ tΘto metody se snφ₧φ poΦet referencφ na objekt. Metoda vracφ aktußlnφ poΦet referencφ.

Poznßmka:
VÜimn∞te si, ₧e nevytvß°φme ₧ßdn² objekt CDirectDraw7 (ten za nßs vytvo°φ COM). TakΘ si vÜimn∞te jak²m zp∙sobem zφskßvßme ukazatel. Nikde ₧ßdn² operßtor new!

Co musφme uvol≥ovat?

Obecn∞ musφme uvolnit vÜechny zφskanΘ rozhranφ. UrΦit∞ to bude objekt DirectDraw. Pokud vytvß°φte paletu nebo clipper, pak oba tyto objekty musφte uvolnit (my pou₧φvßme t°φdu CDisplay, kterß vÜe d∞lß za nßs, staΦφ se podφvat do funkce DestroyObject()) (tΘto funkci si jeÜt∞ vÜimn∞te navrßcenφ cooperative levelu na hodnotu normal). NutnΘ je ovÜem uvol≥ovat i odkazy na veÜkerΘ povrchy! Op∞t pokud pou₧φvßte objekt CSurface, nemusφte se o nic starat, proto₧e v destruktoru najdete uvoln∞nφ vnit°nφho povrchu.

Poznßmka: U slo₧it∞jÜφch objekt∙, kde je pot°eba uvol≥ovat n∞jakΘ objekty i p°i chyb∞ programu a nßsilnΘmu ukonΦenφ, je dobrΘ si vytvo°it funkci Clean(), kterß se bude volat nejen z destruktoru, ale prßv∞ i p°i nßsilnΘm ukonΦenφ. V tΘto funkci pak uvol≥ujte nejen rozhranφ COM, ale i dynamicky vytvo°enΘ objekty, vlßkna apod.

8.2. ┌vod do DirectInput

Tφm, ₧e jste se rozlouΦili s DirectDraw zdaleka neznamenß, ₧e jste se rozlouΦili s DirectX. DirectDraw v dneÜnφm DirectX vlastn∞ u₧ ani neexistuje a je pln∞ nahrazeno Direct3D. DalÜφ zajφmavou a d∙le₧itou Φßstφ DirectX je DirectInput. Ji₧ v ·vodu jsem nastφnil, Φφm se zab²vß. Seznamovßnφ nßm p∙jde rychleji, proto₧e princip je podobn² jako u DirectDraw.

Pokud budete programovat n∞jakou multimedißlnφ aplikaci (nap°φklad hru), m∙₧ete pou₧it bu∩ standardnφ zprßvy Windows WM_KEYDOWN, WM_KEYUP, WM_RBUTTONDOWN, WM_RBUTTONUP atd. nebo m∙₧ete pou₧φt komponentu DirectInput. M∙₧ete si zkusit chytat klßvesovou zprßvu a t°eba pohybovat spritem podle Üipek. Zjistφte ale nßsledujφcφ problΘm. SystΘm neposφlß zprßvy WM_KEYDOWN ·pln∞ pravideln∞ (to proto, ₧e t∞ch zprßv posφlß vφc a vaÜe zprßva si holt musφ n∞kdy poΦkat). Tato nepravidelnost se projevuje trhan²m pohybem spritu. M∙₧eme si pomoci tak, ₧e pokud okno zachytφ WM_KEYDOWN nastavφ n∞jak² flag a "nepustφ ho", dokud nep°ijde opaΦnß zprßva WM_KEYUP. Zdß se, ₧e problΘm s trhavostφ je vy°eÜen, ale nynφ si zkuste stisknout p∞t klßves najednou. Musφte mφt p∞t flag∙ pro ka₧dΘ tlaΦφtko (rovnou si vytvo°te pole pro 102 klßves). Sami jist∞ uznßte, ₧e pokud stisknete vφce tlaΦφtek, tak zaΦne b²t ve zprßvßch p∞kn² gulßÜ.

Pro takovΘ p°φpady tu mßme DirectInput. I kdy₧ se bez polφ neobejdete (budou dokonce dv∞), p°esto tφm, ₧e DirectInput nenφ zalo₧en na zprßvßch Window, se nem∙₧e stßt, ₧e by n∞jakΘ tlaΦφtko bylo ignorovßno, kv∙li jinΘmu, kterΘ bylo stisknuto o 5 ms d°φv.

JeÜt∞ v∞tÜφ v²hody najdete u pou₧φvßnφ myÜi. Zkuste pou₧φvat myÜ v DirectDraw bez pou₧itφ DirectInput a budete velice neÜ¥astnφ. Na ka₧dΘm poΦφtaΦi d∞lß myÜ n∞co jinΘho. Na prvnφm nemusφ b²t vid∞t v∙bec, na druhΘm zase bude blikat a na t°etφm bude vid∞t, ale zase bude potrhßvat. I tento problΘm °eÜφ DirectInput beze zbytku. M∙₧ete si ud∞lat animovanΘ kurzory, libovoln∞ velikΘ kurzory a vÜe bude chodit velmi p∞kn∞ (o tlaΦφtkßch ani nemluv∞).

Navφc DirectInput m∙₧ete pou₧φvat i u naprosto b∞₧nΘ "oknovΘ aplikace". Pro zajφmavost vßm p°ilo₧φm p°φklad, kter² vyu₧φvß DirectInput a p°itom se jednß o okno. Tento p°φklad takΘ najdete u SDK 8.0 (8.1). Stßhnout si ho m∙₧ete v sekci Downloads jako Scrawl.

Poznßmka: VÜimn∞te si, ₧e uvßdφm verzi 8.1do zßvorek za 8.0. To proto, ₧e my budeme vyu₧φvat rozhranφ 8, ale mßte nainstalovanΘ SDK verze 8.1. Rozdφly mezi 8.0 a 8.1 jsou v DirectInput minimßlnφ (p°φstup k nim samoz°ejm∞ mßte). Seznam novinek naleznete v nßpov∞d∞. Navφc n∞kdo z vßs m∙₧e mφt nainstalovanΘ SDK 8.0 a v tomto p°φpad∞ mu to nebude vadit.

8.2.1. Objekt DirectInput

Zßkladem vÜeho je objekt DirectInput (₧e u₧ jste to n∞kde slyÜeli?). Tento objekt se vytvo°φ velice podobn∞ jako objet DirectDraw. Jist∞ jste si vÜimli, ₧e rozhranφ v DirectDraw m∞ly verzi 7 (IDirectDraw7, IDirectDrawSurface7). To souvisφ s tφm, co jsem psal na zaΦßtku. Verze DX 8.0 ji₧ nemß DirectDraw samostatn∞ a tudφ₧ nevznikly ani novΘ rozhranφ (rozhranφ IDirectDraw8 neexistuje, i kdy₧ to vypadß p∞kn∞). DirectInput ovÜem verzi 8.0 mß, tak₧e koneΦn∞ budeme pln∞ vy₧φvat nainstalovanΘ SDK 8.0 (8.1), konkrΘtn∞ rozhranφ objektu DirectInput IDirectInput8.

8.2.2. Objekty za°φzenφ

V DirectInput pracujeme s tzv. za°φzenφmi (devices), kterΘ p°edstavujφ t°eba myÜ nebo klßvesnici. K objektu za°φzenφ budeme p°istupovat pomocφ rozhranφ IDirectInputDevice8. P°edpoklßdejme, ₧e budeme chtφt ovlßdat klßvesnici a myÜ. To jsou dv∞ za°φzenφ, pro ka₧dΘ bude vytvo°en zvlßÜ¥ objekt typu za°φzenφ. U₧ jist∞ p°em²Ülφte, jak bude vypadat novß t°φda CInput.

O konkrΘtnφm °eÜenφ si vÜak povφme a₧ v p°φÜtφ lekci.


T∞Üφm se p°φÜt∞ nashledanou.

Ji°φ Formßnek