DirectX/Inizializzazione/Device e SwapChain

Indice del libro

Entriamo ora nel vivo della programmazione Direct3D. Tutto diventa ancora più complicato ma cercherò di semplificarvi la vita il più possibile.

Il Device è una struttura che useremo continuamente. Questa struttura è la nostra interfaccia con Direct3D. Una volta creato il Device, quasi tutte le altre funzioni di Direct3D che useremo saranno metodi della classe Device.

SwapChain

modifica

Disegnare direttamente sullo schermo può essere controproducente, infatti all'utente può essere mostrata il frame "in costruzione", vedendo i pezzi che si aggiungono pian piano. Per evitare questo spiacevole effetto è stato introdotto il Double Buffering. Questa tecnica consiste nel disegnare su una superficie in memoria centrale, ed una volta terminato il disegno, inviare l'immagine completa allo schermo. Quando disegneremo usando il Device, in realtà andremo a disegnare sul Back Buffer ossia, sulla superficie "nascosta". Lo SwapChain è la struttura che si occupa di copiare il Back Buffer sul Front Buffer. Una volta terminato il disegno, con un apposito metodo di SwapChain, faremo avvenire la copia.

Creare le strutture

modifica

In quasi tutti i libri e tutorial online troverete una chiamata a questa funzione:

HRESULT WINAPI D3D10CreateDeviceAndSwapChain(IDXGIAdapter *pAdapter, D3D10_DRIVER_TYPE DriverType, HMODULE Software, UINT Flags, UINT SDKVersion, DXGI_SWAP_CHAIN_DESC *pSwapChainDesc, IDXGISwapChain **ppSwapChain, ID3D10Device **ppDevice);

Questa funzione come ricorda il nome crea Device e SwapChain contemporaneamente. Tralasciamo per un secondo l'enorme numero di argomenti. Per quanto possa essere "comodo" creare entrambe le strutture con una sola istruzione, questo porta almeno un aspetto negativo: nella funzione trovate un puntatore a struttura DXGI_SWAP_CHAIN_DEST *pSwapChainDesc. Questa struttura, simile a WNDCLASSEX, serve a descrivere le caratteristiche del programma (risoluzione, profondità di colore, frequenza), tra cui l'Antialiasing.

Antialiasing

modifica

L'Antialiasing (AA) è uno strumento della scheda video che serve a "smussare i bordi". Provate a disegnare una linea con Paint, avvicinandovi allo schermo noterete che presenta una seghettatura (alias). Questo problema deriva dal fatto che i pixel non sono infinitamente piccoli e che quindi i "punti" che conpongono la linea sono abbastanza grossi da poter essere distinti. L'Antialiasing è una soluzione a questo problema, che consiste nello "sfumare" i pixel circostanti. In questo modo è possibile avere l'illusione di una linea senza seghettature. Questo processo è molto costoso per la scheda video. Con l'Antialiasing disattivato le prestazioni sono nettamente superiori che con quest'ultimo attivo. Inoltre generalmente le schede video lasciano indicare con quanta precisione è necessario smussare. Un Antialiasing 2x è più veloce di uno 4x, che però lascia più a desiderare (come risultato) di uno 16x (però molto più lento).

Il problema dello SwapChain

modifica

Il motivo per cui non usare la funzione sopra riportata è che nella struttura indicata, va riportato anche il livello di Antialiasing (1 - disattivato, 2, 4, 8, 16, 32). Tuttavia non tutte le schede video supportano 32 o 16 livelli, molte addirittura si fermano a 4. Prima di creare questa struttura è bene sapere quando ci si deve fermare, altrimenti il programma terminerà bruscamente nel caso in cui venga impostato un livello incorretto. Allora ci si potrebbe chiedere cos'è che ci impedisce di conoscere i livelli di Antialiasing disponibili prima di chiamare la funzione stessa: il problema è che per fare questa ricerca, è necessario avere l'oggetto Device già pronto. Ma quando chiamiamo CreateDeviceAndSwapChain il Device non è ancora disponibile (lo stiamo creando) e quindi saremo costretti a disattivare l'Antialiasing. La procedura corretta, consiste invece nel creare il Device, utilizzarlo per determinare i livelli di Antialiasing disponibili, e poi dopo averne scelto uno, creare lo SwapChain. La funzione sopra indicata, non può essere usata quindi nel caso in cui vogliate usare l'Antialiasing. Molti libri e tutorial online, per semplicità decidono di non usare questa funzione, oggigiorno indispensabile per qualsiasi applicazione grafica, e di usare CreateDeviceAndSwapChain. In questo modulo illustrerò entrambi i metodi (creazione contemporanea di Device e SwapChain, creazione separata con ricerca di

Strutture da conoscere

modifica

La prima struttura da saper usare è DXGI_MODE_DESC. Questa struttura contiene le informazioni riguardanti una determinata risoluzione video:

struct DXGI_MODE_DESC
{
    UINT Width;
    UINT Height;
    DXGI_RATIONAL RefreshRate;
    DXGI_FORMAT Format;
    DXGI_MODE_SCANLINE_ORDER ScanlineOrdering;
    DXGI_MODE_SCALING Scaling;
};
  • Width, Height, sono il numero di pixel orizzontali e verticali che la risoluzione offre (1024x768: Width = 1024, Height = 768).
  • RefreshRate è un'altra struttura con due membri: Numerator e Denominator. Questa consente di rappresentare un numero razionale. Il valore di questo campo indica la frequenza di refresh (Refresh Rate). Ne perleremo a breve.
  • Format indica come strutturata la modalità video: RGB, RGBA, GRB, RGB con float, RGBA con float, ecc.

ScanlineOrdering e Scaling non sono usati e potrete sempre lasciarli a zero.

Passiamo a DXGI_SAMPLE_DESC:

struct DXGI_SAMPLE_DESC
{
    UINT Count;
    UINT Quality;
};

Questa struttura indica le impostazioni di Antialiasing da applicare. Count è il livello scelto (1 - disattivato, 2, 4, 8, 16, 32). Quality è un campo a cui ogni scheda video può dare una propria interpretazione. Nonostante si chiami Qualità, aumentarne il valore non corrisponde sempre ad un miglioramento visivo. Impostarlo a valori errati, può far crashare il programma allo stesso modo in cui crasha, dando a Count un valore errato o non supportato.

Veniamo ora allo DXGI_SWAP_CHAIN_DESC, la struttura che ci farà creare lo SwapChain:

struct DXGI_SWAP_CHAIN_DESC
{
    DXGI_MODE_DESC BufferDesc;
    DXGI_SAMPLE_DESC SampleDesc;
    DXGI_USAGE BufferUsage;
    UINT BufferCount;
    HWND OutputWindow;
    BOOL Windowed;
    DXGI_SWAP_EFFECT SwapEffect;
    UINT Flags;
};
  • BufferDesc: è una struttura di tipo DXGI_MODE_DESC. Indica la modalità video (risoluzione, colore) che vogliamo usare nel caso in cui volessimo lavorare Fullscreen (a schermo intero). Quando lavoriamo in modalità Windowed (disegnamo dentro una finestra delimitata da bordi e barra del titolo), i campi Width e Height vanno impostati alla dimensione della finestra. Indicano insomma i lati del rettangolo da disegno che andremo ad usare.
  • SampleDesc: le impostazioni di Antialiasing.
  • BufferUsate: scopriremo che un buffer in Direct3D non deve per forza essere usato per disegnarvici sopra. In questo campo specifichiamo come intendiamo usare il BackBuffer.
  • BufferCount: quanti BackBuffer devono essere creati? E' possibile (ma poco usato) usare il Triple Buffering al posto del Double Buffering. Le immagini vengono create sul 3° buffer, e passano per il 2° per poi andare sullo schermo. Per un normale Double Buffering, impostarlo ad 1.
  • OutputWindow: qui va impostato l'HANDLE della finestra creata nei moduli precedenti.
  • Windowed: se FALSE l'applicazione sarà a tutto schermo, altrimenti sarà Windowed.
  • SwapEffect: ne parleremo dopo.
  • Flags: altri valori. L'unico che conosceremo sarà DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH.

SwapEffect

modifica

Questo campo indica in che modo deve essere sostituito il Front Buffer, con il Back Buffer. Normalmente si procede con una copia fisica dei bytes dal secondo al primo buffer. Questo metodo viene detto BitBlt. Un altro metodo disponibile però da DirectX 11 è il Flip. Questo consiste nel cambiare il puntatore del Front Buffer in modo che punti alla zona di memoria del Back Buffer. In questo modo l'area di memoria del Back Buffer diventa ora il Front Buffer e viceversa. Per lasciar scegliere DirectX, quale metodo usare (in base alla disponibilità e alle prestazioni) useremo DXGI_SWAP_EFFECT_DISCARD.

Esistono diverse flags per questa struttura ma la più interessante è DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH. Questa flag dal nome impronunciabile è fondamentale per qualunque applicazione FullScreen. Infatti questa ci permette di impostare la risoluzione che più ci aggrada. Senza questa, potremmo impostare i valori di BufferDesc come ci pare, ma i valori verranno ignorati e la risoluzione resterà quella che era impostata dal sistema operativo al momento dell'avvio. Usandola avremo la flessibilità di cambiare risoluzione, profondità di colore, frequenza di aggiornamento. Per avere un incremento di prestazioni potremmo ad esempio decidere di scendere da 1920x1080 (Full HD) a 1280x1024.

Creare le strutture con una sola chiamata

modifica

Nel caso in cui aveste deciso la strada "semplice", senza Antialiasing, e che crea le strutture in una sola volta dovrete usare questa funzione:

HRESULT WINAPI D3D10CreateDeviceAndSwapChain(IDXGIAdapter *pAdapter, D3D10_DRIVER_TYPE DriverType, HMODULE Software, UINT Flags, UINT SDKVersion, DXGI_SWAP_CHAIN_DESC *pSwapChainDesc, IDXGISwapChain **ppSwapChain, ID3D10Device **ppDevice);

Vediamo ora gli argomenti:

  • pAdapter: quale scheda video vuoi utilizzare? Impostando a NULL, chiediamo di usare quella predefinita. Vedremo dopo come trovare le schede disponibili.
  • DriverType: i valori possibili sono D3D10_DRIVER_TYPE_HARDWARE, D3D10_DRIVER_TYPE_REFERENCE, D3D10_DRIVER_TYPE_NULL, D3D10_DRIVER_TYPE_SOFTWARE. Normalmente useremo il primo che vuol dire "voglio usare la scheda video per disegnare". Il secondo invece è utilizzato per verificare il funzionamento del driver. E' estremamente lento in quanto non usa per nulla la scheda video e lascia tutta l'elaborazione alla CPU. Da non usare MAI in un software da rilasciare. Il tipo NULL invece non produce output. Potrete chiamare tutte le funzioni di Direct3D, ma non verrà mai mostrata nessuna immagine. Il tipo SOFTWARE è riservato ed attualmente non ha utilizzo.
  • Software: impostarlo sempre a NULL. Andrebbe usato in coppia con D3D10_DRIVER_TYPE_SOFTWARE, ma le specifiche classificano quest'ultimo come riservato. (Bah!)
  • Flags: solite noiose flags. Ne conosceremo due.
  • SDKVersion: qui indicate che versione di DirectX intendete utilizzare. Noi la impostiamo a D3D10_SDK_VERSION.
  • pSwapChainDesc: puntatore a quella struttura che avremo creato prima.
  • ppSwapChain: puntatore a puntatore a SwapChain (create una variabile IDXGISwapChain *SwapChain e passatela alla funzione così: &SwapChain, ossia il puntatore al puntatore).
  • ppDevice: puntatore a puntatore a Device.

Da notare che ppSwapChain e ppDevice non devono essere inizializzati (niente malloc ne new). Sarà infatti la stessa libreria DirectX a creare spazio per queste strutture. Dopo la chiamata i puntatori passati come argomento conterranno riferimenti a oggetti Device e SwapChain.

Solo due flags sono utili:

  • D3D10_CREATE_DEVICE_SINGLETHREADED: indica a DirectX che useremo un solo thread per questa applicazione e che quindi può evitare di usare Locks e Semafori risparmiando tempo.
  • D3D10_CREATE_DEVICE_DEBUG: chiede a DirectX di inviare eventuali messaggi di errore alla finestra Output di Visual Studio. Questa flag non va usata quando il programma è pronto per essere mostrato al pubblico, ma è un buono strumento per trovare alcuni errori. Nel corso di questo libro terrò la flag del DEBUG attivata.

Ecco il codice:

/* Determiniamo quale risoluzione vogliamo utilizzare. Nel prossimo capitolo vedremo. */
/* Supponiamo di voler usare 800x600 */
DXGI_SWAP_CHAIN_DESC scDesc;
ZeroMemory(&scDesc, sizeof(DXGI_SWAP_CHAIN_DESC));

scDesc.BufferDesc.Width = 800;
scDesc.BufferDesc.Height = 600;
scDesc.BufferDesc.RefreshRate.Numerator = 60;
scDesc.BufferDesc.RefreshRate.Denominator = 1;
scDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;

/* Antialiasing disattivato */
scDesc.SampleDesc.Count = 1;
scDesc.SampleDesc.Quality = 0;

/* Useremo questo buffer per disegnarci sopra */
scDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
scDesc.BufferCount = 1;

scDesc.OutputWindow = hWnd;

/* Disegna nella finestra */
scDesc.Windowed = true;

scDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
scDesc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;

IDXGISwapChain *swapChain;
ID3D10Device *device;
HRESULT hr = D3D10CreateDeviceAndSwapChain(NULL, D3D10_DRIVER_TYPE_HARDWARE, NULL, D3D10_CREATE_DEVICE_DEBUG, D3D10_SDK_VERSION, &scDesc, &swapChain, &device);
if (hr != S_OK)
    printf("Errore nella creazione del Device o dello SwapChain\n");