Una delle cose fondamentali utilizzando DirectX è il poter accedere alle risorse contenute nei buffer, che siano Constant Buffer, Texture o Vertex Buffer.
Ogni risorsa è creata in modo da poter essere letta o scritta solamente dalla GPU o dalla CPU. Questo ad esempio può significare che una certa risorsa può essere creata per poter essere letta dalla GPU (esempio una texture quando viene mandata a video) e scritta dalla CPU (per poter essere modificata), o viceversa oppure essere gestita completamente solamente dalla GPU o solamente dalla CPU.
In Direct3D11 infatti esistono 4 tipi di usage, ossia le caratteristiche di utilizzo:
- D3D11_USAGE_DEFAULT : può essere letta e scritta solo dalla GPU (esempio di lettura è il vertex buffer, mentre quello in scrittura è un render target, ossia una texture su cui si fa il rendering)
- D3D11_USAGE_IMMUTABLE : può essere utilizzata solamente in lettura dalla GPU
- D3D11_USAGE_DYNAMIC : può essere letta dalla GPU e scritta dalla CPU (ad esempio per creare un vertex buffer che deve cambiare continuamente)
- D3D11_USAGE_STAGING : può essere letta e scritta solo dalla CPU
Non tutte le risorse possono essere create in tutte le modalità. Ad esempio in Staging non possiamo creare una texture. Questo porterebbe a pensare che solo alcune tipologie di risorse possano essere lette. Inoltre come si vede solo la modalità Staging ha funzionalità di lettura da CPU e quindi è l'unica che può essere letta e modificata da codice, ma allo stesso tempo non si può utilizzare per il rendering.
In Direct3D11 si utilizzano quindi le funzioni di Copy che permettono di copiare una risorsa tra buffer. Potete quindi copiare risorse da una risorsa di tipo Default, Immutable e Dynamic in una di tipo Staging, modificarla e ricopiarla sul buffer di origine.
L'unica risorsa su cui non si può andare a scrivere è solo quella definita come Immutable. Per creare una risorsa di staging si utilizza la classica forma di creazione di un buffer con BindFlags impostato a zero, usage a Staging ed CPUAccessFlags in lettura e scrittura.
D3D11_BUFFER_DESC bd;
bd.ByteWidth = stride * vertexCount;
bd.MiscFlags = 0;
bd.BindFlags = 0;
bd.Usage = D3D10_USAGE_STAGING;
bd.CPUAccessFlags = D3D11_CPU_ACCESS_READ | D3D11_CPU_ACCESS_WRITE;
ID3D11Buffer* buffer;
HRESULT hr=device->CreateBuffer( &bd, NULL, &buffer );
Per poter utilizzare la funzionalità di copia dovete creare una risorsa di dimensione uguale a quella di origine
A questo punto potete utilizzare le funzionalità di copia
deviceContext->CopyResource(buffer,vertexBuffer);
Con questa istruzione ho copiato un vertexBuffer su uno staging.
Ora dovrete andare a leggere le risorse. Per farlo si utilizza l'istruzione Map presente nel DeviceContext.
Questa è la novità rispetto a Direct3D10 in quanto ora è il Context a gestire l’operazione di Map e non il Buffer stesso.
D3D11_MAPPED_SUBRESOURCE mapped;
hr = context->Map(buffer,0,D3D11_MAP_READ,0,&mapped);
//QUI MODIFICHERETE I DATI
context->Unmap(buffer,0);
L'istruzione map restituisce una struttura mapped che contiene un puntatore pData. Su questo puntatore potrete fare tutte le operazioni che vorrete. Nel nostro caso abbiamo usato il parametro D3D11_MAP_READ ma potete usare anche l’opzione WRITE o entrambe. L’importante è che siano supportate dal tipo di buffer (Staging le supporta entrambe). Alla funzione map vengono passati sia il buffer che altri valori che andrà a spiegare.
Quando avete finito dovrete utilizzare l'istruzione UnMap per liberare le risorse. Ora la struttura mapped è sganciata dal buffer ed andarci a leggere o scrivere potrà generare errori.
A questo punto potete nuovamente copiare il buffer modificato sul buffer originario ed utilizzarlo per il rendering.
Ora i parametri. L’ultimo zero indica i tempi di attesa consentiti. Il valore 0 indica che l’istruzione aspetterà che la risorsa sia disponibile mentre D3D11_MAP_FLAG_DO_NOT_WAIT potrà generare errori se andiamo a chiedere una risorsa mentre è occupata (problemi che si potrebbero generare soprattutto in multithreating). Il secondo parametro invece indica l’indice della subResource. Alcuni buffer sono strutturati in maniera complessa. Ad esempio esistono Texture contenenti più copie di se stessa (mipmapping) o array di texture o altre combinazioni. Il buffer contiene quindi più parti e a seconda di quella scelta dovremo variare il parametro.
Vi lascio con un pò di consigli
- Nel caso di risorse dynamic, non effettuate il CopyResource su quest'ultimo. Le risorse dynamic possono essere scritte direttamente usando Map ed Unmap. Potete quindi bloccare la risorsa in Staging per leggere i dati ma usare il Map del buffer dinamico come destinazione. Questo farà risparmiare un bel pò di lavoro alla macchina
- Il processo di copia e soprattutto di Map ed Unmap sono eseguite in CPU, quindi non sono veloci quanto le altre operazione DirectX
- Per conoscere le caratteristiche dei buffer usate l'istruzione GetDesc
- Usate risorse Dynamic per buffer con frequenti scritture, Default per le risorse da mandare a video alla massima velocità possibile. Utilizzate Immutable solo per motivi di sicurezza (nel caso non vogliate modificare il contenuto da altre parti del codice)
- La copia tra risorse può avvenire solo tra buffer di pari dimensioni e formati (nel caso di texture)
Non rilascerò un esempio vista la semplicità dell'operazione