In questo nuovo articolo riguardante l’uso di DirectX inizieremo a lavorare direttamente con questo framework, andando ad impostare in ogni minimo dettaglio le caratteristiche della nostra applicazione.
Per prima cosa create una nuova applicazione Windows Forms Application in Visual Basic .NET 2008, quindi cliccate col tasto destro sull’icona della radice della vostra soluzione nella finestra “Solution explorer” e scegliete “Add Reference…”.
Nella finestra che vi apparirà selezionate la tab “.NET” ed individuate le voci “Microsoft.DirectX”, “Microsoft.DirectX.Direct3D” e “Microsoft.DirectX.Direct3DX”. Selezionatele tutte facendo Ctrl+Click su ciascuna di esse e cliccate su Ok. Se per qualsiasi motivo queste voci non esistono dovrete scaricarvi ed installarvi le managed DirectX 9.0c.
Bene, ora avete aggiunto al vostro progetto le librerie di base su cui si basano le DirectX 9 per piattaforma Net.
In questo esempio inizializzeremo solamente l’ambiente e, pertanto, non sarà necessario aggiungere alcun tipo di controllo sul form.
Ora un po’ di teoria…
DirectX utilizza un oggetto chiamato “Device” che in pratica si occupa di effettuare il rendering di ciò che vogliamo nel modo in cui vogliamo ed inizializzarlo consiste nel definire una serie di opzioni che verranno utilizzate dal device stesso per regolare la qualità ed il tipo di rendering. Queste opzioni sono contenute all’interno un oggetto di tipo PresenteParameters. Vediamo cosa significano le proprietà di questo oggetto, alcune richiamanti concetti visti nel primo tutorial su DirectX:
AutoDepthStencilFormat - Formato del DepthStencilBuffer. Normalmente 4 sono i formati supportati: DepthFormat.D16 (16 bit di superficie, solo depth buffer), DepthFormat.D32 (32 bit di superficie, solo depth buffer. La più usata se non dovete utilizzare lo stencil), DepthFormat.D24X8 (32 bit di superficie, 24 bit di depth buffer e 8 inutilizzati. Si usa per adattare la superficie da 24 bit a schede che supportano solo multipli di 2) o DepthFormat.D24S8 (32 bit di superficie, 24 di depth buffer e 8 di stencil buffer. L’unica che supporta lo stencil). Gli altri formati sono scarsamente supportati o non sono rilevanti.
BackBufferCount - Numero dei backbuffers da utilizzare, varia da 1 a 3 a seconda del modello della scheda video.
BackBufferFormat - Formato del backbuffer. Indica quanti colori supporta la superficie e in che misura. La proprietà è un enumerativo che presenta un numero enorme di opzioni ma in realtà questa proprietà ne supporta solo alcune, cioè Format.X8R8G8B8 (formato a 24 bit adattati a 32, 8 bit per canale che comportano un numero di variazioni per canale pari a 256 ed un numero di colori massimo pari a 16.777.216. E’ il formato più usato in assoluto, dei 32 bit totali 8 vengono ignorati), Format.R8G8B8 (Formato a 24 bit identico a quello di sopra. Alcune schede non digeriscono formati che non siano multipli di due e pertanto questo è scarsamente supportato), Format.R5G6B5 (Formato a 16 bit con 5 bit per i canali rosso e blu e 6 bit per il canale verde. Consente variazioni di rosso e di blu tra 32 valori diversi e variazioni di verde tra 64. Ampiamente supportato.), Format.X4R4G4B4 (Formato a 16 bit con 4 bit per canale. 4 bit non sono usati. Difficilmente supportato). Normalmente utilizzerete sempre o il primo o il terzo. Piccola curiosità: perchè nel formato a 16 bit il verde ha un bit in più e non magari il rosso o il blu? La risposta è meno intuitiva di quello che sembra ma è breve: l’occhio umano può percepire più tonalità di verde piuttosto che di rosso o di blu.
BackBufferWidth, BackBufferHeight - La risoluzione del gioco, corrispondente alla dimensione del backbuffer. Normalmente si usano valori come 640×480, 800×600, 1024×768, 1280×1024 oppure risoluzioni leggermente “allungate” per i widescreen.
DeviceWindow - Per poter renderizzare la scena il device deve essere associato ad un oggetto su cui verrà visualizzata la scena. Di solito è un form o una picturebox.
DeviceWindowHandle - Ha la stessa funzione della proprietà di sopra solo che invece che un controllo richiede un puntatore ad esso, ricavabile tramite la proprietà Handle di un qualsiasi controllo. Ovviamente si imposta o questa come proprietà o quella di sopra.
EnableAutoDepthStencil - In pratica è un booleano che indica se l’applicazione farà uso del DepthStencilBuffer o meno. Attivare questa proprietà indica che l’applicazione farà un uso “normale” del 3D, con gli oggetti più lontani renderizzati dietro a quelli più vicini. Se viene disattivata, invece, il device renderizzerà gli oggetti in base all’ordine con cui vengono renderizzati, ovvero l’ultimo rendering sovrascrive tutti quelli precedenti. Per applicazioni quali giochi deve essere messo a true.
FullScreenRefreshRateInHz - Questa proprietà è valida solo per applicazioni in fullscreen. La proprietà serve per impostare la frequenza massima di aggiornamento dell’immagine su schermo. Aumentare il valore evita alcuni effetti di sfarfallio ma è computazionalmente più dispendiosa. Se l’applicazione gira a velocità superiori a questa il processo di rendering si blocca ed aspetta che lo schermo si liberi prima di effettuare una nuova rasterizzazione.
MultiSample - Può assumere fino a 17 valori differenti e serve per impostare l’antialiasing. Un valore pari a None disattiva l’effetto del tutto, tutti gli altri, eccetto NonMaskable, corrispondono alla traduzione in lettere del sampling che vogliamo applicare alla scena, cosicché se desideriamo un applicazione con anti aliasing (abbreviato in FSAA, fullscreen anti aliasing) 4x sceglieremo MultiSampleType.FourSamples e così via. Occhio alla compatibilità con la vostra scheda. Non è possibile indicare un valore diverso da None a meno che lo SwapEffect non è pari a Discard (vedi dopo).
MultiSampleQuality - Proprietà che acquista un senso solo se la precedente è impostata a MultiSampleType.NonMaskable e permette di inserire in cifre il valore di sampling. Se vogliamo 4x di FSAA allora inseriremo 4 e così via. Come per la scorsa proprietà questa è valida solo se lo SwapEffect è Discard.
PresentationInterval - Proprietà che serve principalmente per debuggare le applicazioni. Normalmente DirectX non consente al device di renderizzare immagini ad una frequenza più alta di quella dello schermo, questo per evitare che scene successive vengano renderizzate contemporaneamente producendo un fastidioso effetto di tearing. A cosa serve renderizzare più immagini del dovuto? A niente… o quasi; questa proprietà ci permette di cambiare tale comportamento. Può assumere fino a 5 valori: PresentInterval.Default (il valore di default, non sortisce nessun effetto in pratica), PresentInterval.One (è identico a quello precedente), PresentInterval.Two (il device renderizza fino al doppio della frequenza di rasterizzazione), PresentInterval.Three (renderizza fino al triplo della frequenza di rasterizzazione), PresentInterval.Four (renderizza fino al quadruplo della frequenza di rasterizzazione), PresentInterval.Immediate (renderizza al massimo della frequenza, è in assoluto la più utilizzata per debuggare).
SwapEffect - Effetto di page swapping. Indica come il front buffer deve essere alternato ai backbuffer e questi ultimi gli uni agli altri. Può assumere tre valori: SwapEffect.Copy (Valido solo se il numero di backbuffer è pari a 1. Indica al device che quando il backbuffer viene inviato allo schermo per essere rasterizzato il suo contenuto viene copiato interamente sul frontbuffer lasciando la superficie inalterata. E’ l’unico procedimento che non comporta nessun page flipping), SwapEffect.Discard(Quando il front buffer viene renderizzato esso viene immediatamente scartato e questo influenza in positivo la velocità dell’applicazione. Il device sceglie per noi il metodo più efficiente per la rasterizzazione e quindi questo valore è il più usato. Usare questo valore quando si usano 1 o più backbuffer ci permette di utilizzare l’effetto di FSAA, cosa impossibile per gli altri effetti di swap. In fase di debug ha un comportamento molto particolare in quanto la scheda video riempie con dati random il vecchio frontbuffer per permettere agli sviluppatori di vedere se tutto funziona correttamente e questo influenza negativamente le prestazioni limitatamente alla fase di debug), SwapEffect.Flip (Valida solo se i backbuffer sono 1 o più. Indica al device che se un backbuffer deve essere rasterizzato, esso diventa immediatamente il frontbuffer e il frontbuffer precedente diventa un comune backbuffer. La superficie che ora non è ne frontbuffer e ne lo è stata durante il flipping attuale è la nuova superficie candidata per il rendering. Tutte le superfici rimangono intatte).
Windowed - Proprietà booleana molto intuitiva. Mettetela e true se desiderate un’applicazione in finestra o a false se ne volete una in fullscreen.
Come potete ben vedere le opzioni sono tantissime… e non sono nemmeno finite!
Per inizializzare la nostra applicazione dovremo richiamare il metodo costruttore dell’oggetto device che utilizzeremo:
New Device (Adapter, DeviceType, RenderWindowHandle, BehaviorFlags, PresentationParameters).
Adapter - E’ un intero che indica la scheda video da utilizzare. Posto a 0 indica la scheda video di default. Per le configurazioni SLICrossfire potete mettere tranquillamente a 0 il valore.
DeviceType - Supporta tre valori possibili: Hardware (Tutto il processo del rendering viene effettuato sfruttando la potenza dell’accelleratore grafico. E’ la migliore in assoluto perchè abilita l’HAL), Reference(Il processo di rendering viene simulato da un driver standard, sfrutta l’HEL), Software (Identico al Reference solo che il driver non è standard ma può essere definito dall’utente. Non lo userete mai).
RenderWindowHandle RenderWindow - A seconda dell’overload della funzione che usate per creare il device essa deve essere pari alla proprietà DeviceWindowHandle o DeviceWindow definita nell’oggetto di tipo PresentParameters. Indica l’oggetto su cui verrà mostrata la scena.
BehaviorFlags - Può assumere diversi valori e se ne possono concatenare più di uno attraverso l’operatore di somma logica OR. I valori che può assumere sono AdapterGroupDevice (Utile per abilitare la gestione multi-gpu (non solo SLICrossfire ma anche multischermo etc.) dell’applicazione. Se impostate questo flag l’Adapter deve essere necessariamente una scheda video con funzione di Master altrimenti l’applicazione andrà in errore), DisableDriverManagementDisableDriverManagementEx (Obbliga DirectX a gestire le risorse al posto dei driver presenti. Lo sconsiglio), FpuPreserve (Indica al driver che l’applicazione necessita di valori reali a precisione doppia. Intacca le prestazioni dell’applicazione), HardwareVertexProcessing (I vertici verranno processati via hardware, può migliorare le prestazioni), SoftwareVertexProcessing (I vertici verranno processati via software), MixedVertexProcessing(I vertici verranno processati via hardware o via software a seconda delle necessità, può migliorare le prestazioni), Multithreaded (Informa DirectX che la vostra applicazione farà uso di più thread il che potrebbe talvolta bloccare DirectX a causa di deadlock. Questo flag evita questo genere di blocchi ma intacca le prestazioni per via dei controlli massivi che vengono richiamati frequentemente). Gli altri valori non vengono quasi mai utilizzati. Attenzione! Hardware, Software e Mixed VertexProcessing sono mutuamente esclusivi e non potrete sceglierne una combinazione dei tre.
PresentationParameters - E’ un oggetto di tipo PresentParameters (visto prima) che indica tutti gli altri settings.
Prima di iniziare ad analizzare il codice vi sono due funzioni che dovete conoscere:
Device.Clear(Target,Color,Depth,Stencil)
Cancella il contenuto del backbuffer colorandolo con un colore user defined. E’ un operazione che se si può evitare si evita, specialmente se ogni rendering della scena ridisegna l’intero buffer…
Target - Può essere composto da un flag o da una combinazione tramite l’operatore OR: Target (Cancella il contenuto del backbuffer), ZDepth (Cancella il contenuto del depthbuffer), Stencil (Cancella il contenuto dello stencil). Con un po’ di fantasia permette di creare effetti particolari…
Color - Il colore del nuovo backbuffer
Depth - Se il depth buffer viene cancellato esso viene riempito con questo valore (di solito viene messo a 1)
Stencil - Se lo stencil buffer viene cancellato esso viene riempito con questo valore (di solito viene messo a 0).
Device.Present()
Dice al device che l’immagine è pronta: il contenuto del backbuffer viene copiato o cambiato con quello del frontbuffer (a seconda dello swapeffect utilizzato) e l’immagine viene rasterizzata. Ci sono un sacco di overloads della funzione ma si usano difficilmente e comunque sono molto intuitivi…
Ed ora il tanto atteso codice:
‘Importa tutti i namespaces necessari
Imports Microsoft.DirectX
Imports Microsoft.DirectX.Direct3D
‘Inizio della classe
Public Class MioForm
Public Dev As Device ‘Gestisce il rendering
Public DevSettings As New PresentParameters ‘Opzioni per il device
Public Sub InitGraph(Target As IntPtr, Width As Integer, Height As Integer, BackBufferCount As Integer, Optional _Windowed As Boolean = False, Optional _16Bit As Boolean = False, Optional _ASync As Boolean = False, Optional _FSAA As Integer = 0)
With DevSettings
.DeviceWindowHandle = Target ‘Dove verrà visualizzata la scena
.BackBufferWidth = Width ‘Risoluzione orizzontale
.BackBufferHeight = Height ‘Risoluzione verticale
.BackBufferCount = BackBufferCount ‘Conteggio dei BB
.Windowed = _Windowed ‘Finestra o FullScreen
‘16 bit o 32 bit
.BackBufferFormat = IIf (_16bit, Format.R5G6B5, Format.X8R8G8B8)
.PresentationInterval = IIf (_ASync, PresentInterval.Immediate, PresentInterval.Default)
If _FSAA Then
‘AntiAliasing attivo
.MultiSample = MultiSampleType.NonMaskable
.MultiSampleQuality = _FSAA
Else
‘Nessun AntiAliasing
.MultiSample = MultiSampleType.None
End If
.EnableAutoDepthStencil = True ‘Attiva lo ZStencil
.AutoDepthStencilFormat = DepthFormat.D24S8 ‘Formato a 32 bit
.SwapEffect = SwapEffect.Discard ‘Necessario per il FSAA
End With
‘Crea il device sfruttando l’HAL e con processo vertici misto. Usa la scheda video di default
Dev = New Device ( 0, DeviceType.Hardware, Target, CreateFlags.MixedVertexProcessing, DevSettings)
End Sub
Private Sub MioForm_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
‘Mostra il form e lo aggiorna
Me.Show
Me.Refresh
InitGraph(Me.Handle, 1024, 768, 2) ‘Inizializza DirectX
Dev.Clear(ClearFlags.Target, Color.Black, 1, 0) ‘Cancella il backbuffer
Dev.Present() ‘Rasterizza la scena
End Sub
Private Sub MioForm_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Click
Dev.Dispose() ‘Cancellazione delle parti unsafe del device
End ‘Fine programma
End Sub
End Class
Il codice è molto semplice e pulito. Se lo avviate visualizzerete una schermata completamente nera, se ciò avviene l’inizializzazione è riuscita altrimenti c’è qualcosa che non va. Cliccate una volta dove volete per terminare l’applicazione. Fate prove su prove…
Alla prossima…


