Für Vertex Buffer Steaming mehrere glBufferSubData VS Orphaning?

8

Ich habe kürzlich OpenGL gelernt. In Spielen müssen wir die Position von Spielobjekten häufig aktualisieren, und sie werden ständig auf dem Bildschirm angezeigt. Das bedeutet, dass wir beim Rendern auch den Scheitelpunktpuffer ziemlich oft aktualisieren müssen.

Im OpenGL-Kontext besteht eine intuitive Möglichkeit darin, glBufferSubData zu verwenden, um die geänderten zu aktualisieren.

Ich habe aber auch online einen Trick namens Orphaning gelesen, der neue Pufferdaten erstellt und die gesamten Scheitelpunktdaten hochlädt.

Auch aufgrund der Kosten für Status-Chagnes und des Hochladens können mehrere glBufferSubData auch mehr kosten.

Hier ist meine Frage,

  1. Welche Methode ist besser?
  2. Sind Stände in diesem Fall wirklich wichtig?
  3. Sind Statusänderungen und Upload-Kosten in diesem Fall wirklich wichtig?

Vielen Dank!

YiFeng
quelle
Warum möchten Sie die Geometrie erneut hochladen, wenn Sie die Matrix tatsächlich nur aktualisieren können? Mit anderen Worten, Sie müssen Scheitelpunkte nicht erneut hochladen, wenn sich nur Transformationen ändern müssen.
Konzept3d
@ concept3d Ja, du hast recht! Ich habe beim Schreiben der Problembeschreibungen einen Fehler gemacht. Danke, dass du mich daran erinnert hast! Ich habe die Beschreibungen bereits aktualisiert.
YiFeng

Antworten:

9

Dies ist eine komplexe Frage mit vielen kleinen Details, die wirklich wichtig sind. Die Leistung variiert je nach Plattform und Anwendung. Sie sollten sich daher auf mögliche Engpässe konzentrieren, bevor Sie in Optimierungen investieren.

Das heißt, erstens gehe ich davon aus, dass Sie Uploads und Updates so weit wie möglich reduzieren sollten, zum Beispiel Instanzen verwenden.

Beachten Sie zweitens, dass GPUs nicht gleichzeitig Puffer übertragen und rendern können, sodass alle OpenGL-Befehle in der Befehlswarteschlange vom Gerät nacheinander verarbeitet werden. Es gibt verschiedene Möglichkeiten, Daten zu kopieren und / oder für die Verwendung durch die GPU verfügbar zu machen.

Es gibt verschiedene Möglichkeiten, Daten zur GPU zu streamen

1- glBufferDataoder glBufferSubDataMethode

Verwenden glBufferDataoder glBufferSubDataist wie memcpy. Wenn Sie einen Zeiger übergeben, wird möglicherweise eine DMA-Operation ausgeführt, da der Speicher möglicherweise im CPU-Speicher fixiert und direkt von der GPU verwendet wird, ohne dass je nach Verwendungsflag (GL_STREAM) eine Speicherübertragung zur GPU erfolgt. Meiner Meinung nach sollten Sie dies zuerst versuchen, da es einfacher zu implementieren ist.

2- Abrufen eines Zeigers auf den internen Speicher mit glMapBuffer

Wenn das oben Genannte nicht gut genug ist glMapBuffer, erhalten Sie einen Zeiger auf den internen Speicher, und Sie können diesen Zeiger verwenden, um den Puffer direkt zu füllen. Dies ist gut bei Lese- und Schreibvorgängen für Dateien, da Sie die Dateidaten direkt zuordnen können in den GPU-Speicher anstatt zuerst in einen temporären Puffer zu kopieren. Wenn Sie nicht den gesamten Puffer zuordnen möchten, können Sie glMapBufferRangeeinen Teil des Puffers zuordnen.

Ein Trick besteht darin, einen großen Puffer zu erstellen, die erste Hälfte zum Rendern und die zweite Hälfte zum Aktualisieren zu verwenden.

3- Buffer Orphaning

In Bezug auf das Verwaisen von Puffern kann dies mit glBufferData mit null und denselben Parametern erfolgen. Der Treiber gibt den Speicherblock zurück, sobald er nicht verwendet wird. Und wird beim nächsten glBufferData-Aufruf verwendet (es wird kein neuer Speicher zugewiesen).

Alle genannten Methoden verursachen eine Menge teurer Synchronisierungen. Auch hier können GPUs nicht gleichzeitig Puffer übertragen und rendern.

4- Unsynchronized Buffers

Die schnellste (und am schwierigsten zu ermittelnde) Methode besteht darin, Puffer ohne Synchronisation zu verwenden, mit denen Sie das GL_MAP_UNSYNCHRONIZED_BITFlag verwenden glMapBufferRangekönnen. Das Problem besteht darin, dass keine Synchronisierung durchgeführt wird, sodass wir möglicherweise Daten in einen Puffer hochladen, der verwendet wird, und daher alles vermasseln. Sie können mehrere Puffer mit unsynchronem Bit verwenden, um die Arbeit ein wenig zu vereinfachen.

concept3d
quelle
0

Ich mache es anders. Vielleicht stimmt da etwas nicht. Einige versteckte gefährliche Dinge.

(Ich benutze C # + OpenTK.GameWindow)

Zur Instanziierung des Objekts verwende ich für jede Instanz ein separates Array-Pufferobjekt für den Satz von Modellmatrizen. Im Scheitelpunkt geteilt:

#version 400
layout (location = 0) in vec3 position;
layout (location = 1) in vec3 normal;
layout (location = 2) in vec2 texcoord;
layout (location = 4) in mat4 model_matrix;

uniform mat4 proj_matrix, view_matrix;

In meinem C # -Code werden Matrizen in einem Float-Array gespeichert float[] mat4_array

Dann binde ich das Array an das Array Buffer Object:

public void bind_data_instances()
{
    GL.BindBuffer(BufferTarget.ArrayBuffer, id_InstanceBO);
    GL.BufferData(BufferTarget.ArrayBuffer, mat4_array.Length * sizeof(float), mat4_array, BufferUsageHint.DynamicDraw);
}

In jedem Frame werden die Modellmatrizen aktualisiert. Zum Update mat4_arrayrufe ich einfach an:

Buffer.BlockCopy(mat4_array_new, 0, mat4_array, 0, sizeof(float) * mat4_models.Length);

und rendern Sie die Szene. mit GL.DrawElementsInstanced.

Es gibt keine zusätzlichen OpenGL-Aufrufe und es funktioniert perfekt.

Dennis
quelle
Was ist der Zweck der Modellmatrix als Scheitelpunktattribut?
Anton Duzenko
Es ist für die Instanz Objekt zeichnen. Und es ist schneller als ein Array, das als einheitliche Variable übergeben wird.
Dennis