Poznámka na úvod: Tento článek je určen pro pokročilé programátory, kteří již mají nějaké zkušenosti s grafikou. Pokud jste začátečníci, naučte se nejprve dobře programovat. V DirectX musíte všemu rozumět, není možné pouze kopírovat kousky kódu a skládat je k sobě!
Vítejte v prvním dílu seriálu o programování v DirectX. V tomto seriálu si ukážeme klíčové možnosti tohoto rozhraní, přejdeme přes 2D grafiku ke 3D zobrazením, ukážeme si zvuk a mnoho dalších věcí. Tak tedy vzhůru do toho.
Primární funkcí rozhraní DirectX je rychlé vykreslování grafiky. Vykreslovat na obrazovku můžeme například pomocí rozhraní GDI+ přes náš známý jmenný prostor System.Drawing, což je vhodné pro jednoduché kreslení a pro primitivní hry. Ale co do výkonu je toto rozhraní pomalé. Vykresluje totiž přes knihovny operačního systému, které se musí přizpůsobit veškerému hardwaru, tzn. i starým grafickým kartám, které mnoho funkcí nemají a tudíž je po nich nemůžeme chtít. Naproti tomu DirectX přistupuje k hardwaru přímo, komunikuje přímo s grafickou kartou a využívá veškeré její možnosti a funkce.
Většina grafických karet, které se dnes prodávají, má svůj procesor (GPU), a DirectX se postará o to, aby co nejvíc výpočtů prováděl tento procesor. Tím "odlehčíme" procesoru hlavnímu, který se tak může soustředit na různé jiné činnosti, ve hrách třeba na umělou inteligenci, propočítávání fyzikálních situací atd. GPU je také lépe uzpůsoben pro výpočty ohledně grafiky. Pokud karta některou funkci nemá, DirectX má k tomuto účelu tzv. hardware emulation layer, což v praxi znamená, že pokud karta něco neumí, spočítá to procesor, anebo to "se to nějak zašvindluje". Výhoda je, že toto dělá DirectX za nás a my už jen využíváme jeho funkce. Takže nás nezajímá, jestli karta umí to a to. DirectX to zařídí.
Co budeme potřebovat
Abychom mohli s rozhraním DirectX pracovat, musíme si stáhnout DirectX SDK, neboli Software Development Kit, česky něco jako soupravu nástrojů pro vývoj. Je to poměrně velký balík (kolem 500MB), ale obsahuje dokumentaci, ukázkové příklady a spoustu nástroj, zkrátka vše, co by se nám při vývoji mohlo hodit.
V ukázkových příkladech používám DirectX SDK August 2006, kterou si můžete stáhnout z webu Microsoftu.
Až budete aplikaci distribuovat, nemusíte se bát, že by vaše (byť jen jednoduchá hra), musela mít 500MB. SDK se nedodává s žádnými produkty, je to sada nástrojů pro vývojáře. Uživatelé si musí nainstalovat DirectX End User Runtime, které se také dodává s většinou her, takže je velmi pravděpodobné, že jej již mají.
Až budete mít DirectX SDK staženo, nainstalujte jej. Instalace je jednoduchá, předpokládám, že ji zvládnete bez problémů.
Základní filozofie rozhraní DirectDraw
DirectDraw je rozhraní DirectX pro rychlé vykreslování 2D grafiky. Právě tomu se budeme věnovat ze začátku.
Abychom mohli data dostat do grafické karty, potřebujeme jim v paměti vyhradit prostor (tzv. Surface, někdy také Buffer). Monitor má nějakou obnovovací frekvenci (většinou kolem 60 - 90 Hz, tedy snímků za sekundu). Pro každý 1 snímek karta čte data z paměti a posílá je do monitoru. Na monitor můžeme ale vždy vykreslit jen hotový snímek, nesmí se stát, že tam polovina informací nebude.
Princip vykreslování spočívá v použití dvou bufferů, tzv. FrontBufferu a BackBufferu. FrontBuffer je oblast, která obsahuje již připravený vykreslený snímek a která se vykresluje na obrazovku. Do BackBufferu vždy vykreslíme 1 kompletní snímek, jakmile je snímek vykreslen, provedeme tzv. flip, což je prohození bufferů. Z BackBufferu, který obsahuje kompletní snímek, se stane FrontBuffer, který vykresluje na obrazovku. Naopak původní FrontBuffer se stane BackBufferem a můžeme jej vymazat a nakreslit na něj nový snímek. Tím máme zaručeno, že se na obrazovku vždy vykreslí hotový snímek se vším, co na něm má být. Také nám nevadí, když budeme stíhat vykreslovat méně nebo více snímků než je obnovovací frekvence monitoru. Pokud vykreslujeme pomaleji, monitor ukáže některé snímky víckrát, pokud vykreslujeme rychleji, monitor některé snímky zobrazit nestihne.
První aplikace v DirectX
Jako první ukázkový příklad si napíšeme jednoduchou aplikaci, která poběží na celé obrazovce. Celou obrazovku vybarví modrofialově a zobrazí na ní zelený čtverec.
Vytvořte ve Visual Basicu novou standardní Windows aplikaci a v průzkumníku souborů projektu dvakrát klikněte na My Project.
Zobrazí se okno vlastností projektu s mnoha nastavovacími prvky. Přejděte na záložku References a použijte tlačítko Add.... Ze seznamu vyberte knihovny Microsoft.DirectX a Microsoft.DirectX.DirectDraw.
Projekt uložte a otevřete okno kódu. Úplně nahoru nad začátek třídy Form1 přidejte ještě tyto dva řádky, které zajistí, že nebudeme muset před použitím objektů DirectX vypisovat celou cestu.
Imports Microsoft.DirectX
Imports Microsoft.DirectX.DirectDraw
Do globálních deklarací Form1 přidejte tyto 4 řádky:
Dim dev As Device
Dim backBuffer As Surface, backBufferDesc As SurfaceDescription
Dim frontBuffer As Surface, frontBufferDesc As SurfaceDescription
Dim clip As Clipper
Device je objekt, který reprezentuje zařízení (grafickou kartu resp. výstup na monitor). O BackBufferu a FrontBufferu jsme již mluvili, každý ještě má objekt, který popisuje jeho vlastnosti a použití. Objekt Clipper slouží k vymezení oblasti renderování na formulář, my ale budeme používat režim fullscreen.
Dále budeme potřebovat funkce pro vytvoření a inicializaci zařízení. Stačí vytvořit objekt Device a použije se výchozí zařízení (výběr zařízení se naučíme později). Ještě je potřeba nastavit režim spolupráce metodou SetCooperativeLevel, která nastaví vše potřebné tak, abychom mohli vytvořit buffery a pak už jen renderovat.
Poznámka: Pokud jsme okno minimalizovali (např. klávesami Alt+Tab), a pak zase obnovili, je třeba tuto funkci zavolat znovu.
Napíšeme tedy funkci createDevice:
Public Sub createDevice()
dev = New Device()
dev.SetCooperativeLevel(Me, CooperativeLevelFlags.FullscreenExclusive)
End Sub
Dále musíme vytvořit buffery. Nejprve nastavíme jejich parametry (objekt SurfaceDescription) a pak vytvoříme objekty Surface, kde také určíme, ke kterému zařízení objekty patří.
FrontBuffer musí být nastaven jako PrimarySurface, protože se zobrazuje přímo na monitor. Dále musíme nastavit, že budeme používat operaci Flip a také nastavíme vlastnost BackBufferCount na 1, abychom řekli, že budeme mít 1 BackBuffer.
BackBufferu musíme akorát nastavit, že je to BackBuffer, pak jej již jen stačí vytvořit.
Nakonec musíme vytvořit Clipper a nastavit jej na okno, do kterého budeme zobrazovat. Také jej připojíme na FrontBuffer.
Takže můžeme napsat proceduru createBuffers.
Poznámka: Pokud je okno minimalizováno, objekty Surface se přestanou používat a při pokusu o manipulaci se vyvolá výjimka SurfaceLostException. Po obnovení okna musíme zavolat proceduru createBuffers, která musí zavolat SetCooperativeLevel a pokud objekty Surface již existují, pouze na nich zavolá metodu Restore, není nutné je znovu vytvářet. Také je důležité kontrolovat, jestli je okno v popředí, jinak nemá cenu objekty Surface vytvářet, automaticky hned zaniknou.
Public Sub createBuffers()
If Not Me.Focused Then Exit Sub
dev.SetCooperativeLevel(Me, CooperativeLevelFlags.FullscreenExclusive)
If frontBuffer IsNot Nothing Then
frontBuffer.Restore()
Else
frontBufferDesc = New SurfaceDescription()
frontBufferDesc.SurfaceCaps.PrimarySurface = True
frontBufferDesc.SurfaceCaps.Flip = True
frontBufferDesc.SurfaceCaps.Complex = True
frontBufferDesc.BackBufferCount = 1
frontBuffer = New Surface(frontBufferDesc, dev)
End If
If backBuffer IsNot Nothing Then
backBuffer.Restore()
Else
backBufferDesc = New SurfaceDescription()
backBufferDesc.SurfaceCaps.BackBuffer = True
backBuffer = frontBuffer.GetAttachedSurface(backBufferDesc.SurfaceCaps)
End If
clip = New Clipper(dev)
clip.Window = Me
frontBuffer.Clipper = clip
End Sub
Teď máme vše připraveno, můžeme začít renderovat.
Pro jednoduchost umístíme na formulář komponentu Timer, příště si ukážeme lepší způsoby. Při každém "tiknutí" tohoho časovače musíme nejprve zkontrolovat, jestli je okno v popředí a jestli existují buffery.
Pak jen na BackBuffer vykreslíme vše, co má na snímku být, a nakonec zavoláme metodu Flip. Vykreslování a volání metody Flip musí být umístěno v Try...Catch...End Try bloku, a pokud nastane výjimka SurfaceLostException, znamená to, že se okno po minimalizaci zase obnovilo, ale buffery nejsou nastaveny, takže zavoláme proceduru createBuffers a vše se dá dopořádku. V příštím tiku cyklu můžeme renderovat.
Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick
If Not Me.Created Then Exit Sub
If frontBuffer Is Nothing Or backBuffer Is Nothing Then Exit Sub
If Not Me.Focused Then Exit Sub
Try
backBuffer.ColorFill(Color.DarkSlateBlue)
backBuffer.FillStyle = 1
backBuffer.FillColor = Color.Green
backBuffer.DrawBox(200, 200, 500, 500)
frontBuffer.Flip(backBuffer, FlipFlags.Wait)
Catch ex As Exception
If ex.GetType() Is GetType(SurfaceLostException) Then
createBuffers()
End If
End Try
End Sub
Ještě musíme napsat proceduru Form1_Load, která vytvoří zařízení, nastaví buffery a spustí časovač. A nezapomeneme na proceduru Form1_KeyDown, která odchytí klávesu Escape a ukončí aplikaci.
Private Sub Form1_KeyDown(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles Me.KeyDown
If e.KeyCode = Keys.Escape Then
Timer1.Enabled = False
End
End If
End Sub
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
createDevice()
createBuffers()
Timer1.Enabled = True
End Sub
To je protentokrát vše. Příště se naučíme používat obrázky a nahradíme časovač smyčkou.