Hi
eine kurze OT Anmerkung zum Thema dynamische vs statische Puffer.
Generell sind die Angaben eher ein Hinweis um dem Treiber mitzuteilen wie ein Puffer von der Anwendung genutzt wird. Basierend darauf kann der Treiber dann entscheiden wie die Ressource am besten angelegt/verwaltet werden sollte um möglichst viel Effizienz zu gewährleisten. Im DirectX Kontext wäre z.B. IMMUTABLE ein Hint dafür, die Resource möglichst direkt im lokalen Videospeicher zu hinterlegen, weil generell kein Schreibzugriff gewünscht ist und die GPU so am schnellsten lesend zugreifen kann. DYNAMIC hingegen erlaubt (erzwingt) einen Schreibzugriff von der CPU. D.h. der Treiber wird die Ressource irgendwo hinterlegen wo die CPU möglichst effizienten Schreibzugriff hat, z.B. im AGP/PCI Speicher o.Ä. Das wiederum hat zur Folge des der Lesezugriff der GPU nicht mehr ganz so effizient ist, weil die Daten über einen Bus gehen müssen.
Außerdem unterscheidet sich das Management von Ressourcen deutlich. Beispiel: wie bereits angedeutet wurde muss für dynamische Ressourcen davon ausgegangen werden, das diese sich jederzeit ändern können. Da GPU und CPU ihre Daten asynchron abarbeiten führt das in der Praxis zu Problemen, wenn ein Puffer geändert wird bevor die GPU die Daten konsumiert hat. Um solche Fälle zu vermeiden wird die Ressource "renamed", d.h. ein neuer Speicherbereich alloziert und (transparent) hinter den Handle geschoben. Die GPU kann dann problemlos ihre(n) Kommandpuffer abarbeiten, ohne das Daten überschrieben werden. Wenn eine Ressource gemapped wird aber keine Referenz auf irgendeinen Commandbuffer existiert dann ist der Zugriff wieder günstig (kein renaming).
So ein Management ist natürlich nicht umsonst und deshalb möglichst zu vermeiden. Falls eine Ressource kein update benötigt sollte sie also am besten "Immutable" sein. Falls eine Ressource häufig verändert wird (z.B. einmal pro Frame) sollte die Ressource dynamisch sein. Um Bufferrenaming zu vermeiden kann man folgendes Pattern verwenden:
|
C-/C++-Quelltext
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
// allocate
ID3D11Buffer* foo = createVertexBuffer(size * multiplier);
// map
uint32 numberOfMaps = 0; // member etc...
ubyte* ptr = 0;
if (numerOfMaps == 0)
{
ptr = (ubyte*)map(DISCARD);
}
else
{
ptr = (ubyte*)map(NO_OVERWRITE);
ptr = ptr + numberOfMaps * size;
}
numerOfMaps = (numberOfMaps + 1) % multiplier;
|
Damit wird der Puffer theoretisch nur alle 'n' Frames umbenannt (DISCARD). Praktisch kann das renaming damit effektiv vermindert werden, weil die 'gefahr' das die Ressource noch von der GPU verwendet wird minimert wird (angenommen die GPU ist 2-3 Frames zurück). Zum Teil werden solche Optimierungen auch intern schon vorgenommen, einige GCN Treiber allozieren für das renaming 8 MB Puffer und führen das o.g. Zugriffspattern intern durch.
D3D11_USAGE Enumeration gibt eine super Übersicht über die Zugriffsmodi (ganz unten). Und ist eigentlich eine ganz gute Hilfestellung.