CPU - GPU Speicherdatenfluss [geschlossen]

16

Ich bin ein neuer Grafikprogrammierer und habe mich kürzlich gefragt: Wie fließen Modelldaten (Maschen und Materialien) von der Anwendung (CPU-Speicher) zur Grafikkarte (GPU-Speicher)? Angenommen, ich habe ein statisches Modell (z. B. ein Gebäude), das ich einmal lade und einrichte und das sich während der gesamten Lebensdauer der App nicht ändert.

  • Werden seine Daten nur einmal an den GPU-Speicher gesendet und bleiben dort für immer?
  • Wenn das Modell tatsächlich gerendert wird, müssen GPU-Prozessoren jedes Mal ihre Daten aus dem GPU-Speicher abrufen? Was ich meine ist - wenn ich 2 Modelle jeweils mehrfach gerendert hätte - wäre es wichtig, wenn ich zuerst das erste mehrfach und dann das zweite mehrfach gerendert hätte oder wenn ich das erste nur einmal, das zweite nur einmal und immer wieder so verschachtelt? Ich könnte diese Frage in diesem Sinne als "internen GPU-Datenfluss" bezeichnen.
  • Offensichtlich haben Grafikkarten einen begrenzten RAM - wenn sie nicht alle Modelldaten enthalten können, die für das Rendern von 1 Frame erforderlich sind, wird sie vermutlich weiterhin von jedem Frame aus dem CPU-RAM abgerufen. Ist das richtig?

Ich weiß, dass es im Internet eine Menge Bücher und ähnliches gibt, aber vielleicht haben Sie ein paar allgemeine Richtlinien, wie Sie diesen Datenfluss verwalten (wann was und wie viel senden, wann und wie rendern)?

Bearbeiten: Ich habe vergessen, eine Unterscheidung zu treffen: Es werden die Daten an die GPU gesendet und die Puffer werden als aktuell gesetzt / gebunden . Verursacht letzteres einen Datenfluss?

Edit2: Nachdem ich Raxvans Post gelesen habe, möchte ich ein paar Aktionen unterscheiden:

  • Puffererstellung mit Initialisierung (wie er sagte, kann ich die Daten entweder im CPU-RAM oder in einer GPU speichern)
  • Pufferdatenaktualisierung (was meiner Meinung nach einfach ist, wenn die Daten im CPU-RAM gespeichert sind und das Abrufen von der GPU zum CPU-RAM (und dann zurück) erforderlich ist, wenn sie im GPU-RAM gespeichert sind)
  • Binden des Puffers als aktiv (ist dies nur eine Möglichkeit, der API mitzuteilen, dass dieser Puffer beim nächsten Aufruf von draw gerendert werden soll, und dass er selbst nichts tut ?)
  • API Draw Call (hier möchte ich von Ihnen hören, was dort tatsächlich passiert)
NPS
quelle
Ich bin in keiner Weise ein Experte, aber wenn Sie modernes (dh nicht sofortiges) OpenGL mit VAOs und VBOs verwenden, werden Daten an die GPU gesendet und im VRAM gespeichert, wenn Sie einen der glBuffer-Befehle verwenden. Jedes Mal, wenn Sie es zeichnen, werden die relevanten Eckpunkte aus dem VRAM abgerufen und gerendert. Wenn es sich um ein Modell handelt, das sich bewegt, neigen Sie dazu, es statisch zu speichern und Matrizen zu verwenden, um sich vom Modellraum in den Welt- / Kameraraum zu bewegen. Was den letzten Punkt betrifft, ich habe keine Ahnung, was passiert, wenn der Arbeitsspeicher knapp wird. Ich vermute, wenn Ihnen der VRAM ausgeht, werden die Daten einfach nicht gesendet, möglicherweise mit einem Fehlercode.
Polar
@Polar - nicht genau. Der GL gibt nicht an, in welchem ​​Speicher ein Pufferobjekt gespeichert ist, und kann es sogar zur Laufzeit basierend auf dem Verwendungsmuster verschieben. GL4.4 spricht dies etwas an, merkt jedoch an, dass das Beste, was es bieten kann, "einer dieser dummen Hinweise" ist; siehe opengl.org/registry/specs/ARB/buffer_storage.txt und insbesondere Issues 2 und 9.
Maximus Minimus
1
@JimmyShelter Ah, danke - es wäre schön, wenn wir weniger "diese dummen Andeutungen" und eine konkretere Spezifikation hätten.
Polar
@Polar - was ärgerlich ist , dass ARB_buffer_storage könnte noch einen weiteren Hinweis einschließlich hätte vermieden werden, aber die Designer die Gelegenheit verpasst. Na ja, vielleicht wird 4.5 es endlich richtig machen.
Maximus Minimus
2
Bitte bearbeiten Sie Ihre Fragen nicht, um auf Antworten zu "antworten". Stelle stattdessen eine neue Frage.

Antworten:

12

Werden seine Daten nur einmal an den GPU-Speicher gesendet und bleiben dort für immer?

Normalerweise ja, aber der Treiber kann frei tun, was "optimal" ist. Die Daten könnten im VRAM oder RAM gespeichert oder einfach hier zwischengespeichert werden. Dies ist ein Artikel, der erklärt, was tatsächlich mit dem VBO-Fluss passiert .

Wenn es beispielsweise als dynamischer OpenGL-Puffer (z. B. VBO) gekennzeichnet wurde, ist es wahrscheinlicher, dass es im RAM gespeichert wird. Die GPU verwendet den direkten Speicherzugriff (DMA), um ohne Eingreifen der CPU direkt auf den RAM zuzugreifen. Dieser wird vom DMA-Controller in der Grafikkarte und im Grafiktreiber gesteuert und im Kernel-Modus ausgeführt.

Wenn das Modell tatsächlich für jeden Frame gerendert wird, müssen die GPU-Prozessoren ihre Daten jedes Mal aus dem GPU-Speicher abrufen, auch wenn ein Modell mehrere aufeinanderfolgende Male gerendert wird?

Genau wie CPUs können GPUs GPU-Anweisungen und Speicherzugriffsvorgänge neu anordnen (lesen: Ausführung in falscher Reihenfolge ), sodass die GPU das von Ihnen erwähnte Szenario mit größter Wahrscheinlichkeit durch Zugriff auf den im Cache befindlichen Speicher bewältigen kann (auf den in der Regel kürzlich zugegriffen wurde) ), aber manchmal kann es das nicht.

Offensichtlich haben Grafikkarten einen begrenzten RAM - wenn sie nicht alle Modelldaten enthalten können, die für das Rendern von 1 Frame erforderlich sind, wird sie vermutlich weiterhin von jedem Frame aus dem CPU-RAM abgerufen. Ist das richtig?

Du willst nicht, dass das passiert. Unabhängig davon beginnt die GPU, den Speicher zwischen RAM und VRAM zu verschieben (der Befehlsprozessor auf der GPU ist dafür verantwortlich), wodurch das Rendern viel langsamer wird, was dazu führt, dass die GPU zum Stillstand kommt, da sie auf die Daten warten muss kopiert werden von / nach V / RAM.

Es werden die Daten an die GPU gesendet und die Puffer werden als aktuell festgelegt / gebunden. Verursacht letzteres einen Datenfluss?

GPUs enthalten einen Befehlspuffer , und alle API-Befehle werden an diesen Puffer übergeben. Beachten Sie, dass dies gleichzeitig mit dem Kopieren der Daten auf die GPU geschehen kann. Der Befehlsringpuffer ist eine Kommunikationswarteschlange zwischen der CPU und der GPU . Jeder auszuführende Befehl muss an die Warteschlange gesendet werden, damit er von der GPU ausgeführt werden kann. Genau wie bei jeder Operation, die neue Puffer bindet, muss sie an die GPU gesendet werden, damit sie auf einen bestimmten Speicherort zugreifen kann.

Dies ist einer der Gründe, warum glBegin / glEnd veraltet ist. Das Senden neuer Befehle erfordert die Synchronisierung der Warteschlange (mithilfe von Speicherzäunen / -barrieren).

Bildbeschreibung hier eingeben

Wie für Ihre anderen Punkte:

Puffererstellung mit Initialisierung

Sie können einen Puffer ohne Initialisierung zuweisen und für eine spätere Verwendung aufbewahren. Oder Sie können ihm einen Puffer zuweisen und gleichzeitig Daten kopieren (über die API-Ebene sprechen).

Aktualisierung der Pufferdaten

Sie können glMapBuffer verwenden, um den Speicher auf der GPU-Seite zu aktualisieren. Ob der Speicher vom / zum RAM kopiert wird, ist nicht Teil des Standards und hängt stark vom Hersteller, GPU-Typ und Treiber ab.

API Draw Call (hier möchte ich von Ihnen hören, was dort tatsächlich passiert).

Mein zweiter Punkt in der Hauptfrage betrifft dies.

Binden des Puffers als aktiv (ist dies nur eine Möglichkeit, der API mitzuteilen, dass dieser Puffer beim nächsten Aufruf von draw wiedergegeben werden soll, und dass er selbst nichts tut?)

Stellen Sie sich das Binden so vor this, als ob Sie einen Zeiger in einer beliebigen objektorientierten Sprache verwenden, obwohl dies nicht unbedingt das Gleiche ist. Alle nachfolgenden API-Aufrufe beziehen sich auf diesen Bindungspuffer.

concept3d
quelle
3

Im Allgemeinen sind die Grenzen und die Beteiligung von CPU und GPU auf der Plattform spezifisch, aber die meisten von ihnen folgen diesem Modell: CPU hat einen RAM, GPU auch und Sie können den Speicher verschieben (in einigen Fällen wird der RAM aber für die gemeinsam genutzt) der Einfachheit halber bleiben wir bei getrennten Widdern).

erster Punkt : Die Daten , die Sie initialisieren können Sie es in dem CPU RAM zu halten wählen oder auf dem GPU ram, und das sind Vorteile für beide. Wenn Sie etwas rendern, muss die GPU das schwere Heben übernehmen, sodass es offensichtlich ist, dass die Daten, die sich bereits auf dem GPU-Mem befinden, eine bessere Leistung bieten. für die CPU muss sie zuerst die Daten an die GPU senden (die sie für eine Weile behalten kann) und dann das Rendern durchführen.

Zweiter Punkt : Es gibt viele Tricks beim Rendern, aber die Hauptmethode ist das Rendern mit Polygonen. Auf einem Frame rendert gpu die Objekte, die aus Polygonen bestehen, nacheinander und nach Abschluss der Verarbeitung sendet die GPU das Bild an das Display. Es gibt kein Konzept wie Objekte, es gibt nur Polygone, und die Art und Weise, wie Sie sie zusammensetzen, erzeugt ein Bild. Die Aufgabe der GPU ist es, diese Polygone von 3D auf 2D zu projizieren und Effekte anzuwenden (falls gewünscht). Die Polygone gehen nur auf eine Weise CPU-> GPU-> BILDSCHIRM oder GPU-> BILDSCHIRM direkt (wenn sich die Polygone bereits im GPU-RAM befinden)

dritter Punkt : Wenn Sie beispielsweise Animationen rendern, ist es besser, die Daten in der Nähe der CPU zu halten, da er dort das Schwergewicht macht. Es wäre nicht optimal, die Daten in der GPU zu halten, sie in die CPU zu verschieben und jeden Frame zurückzusetzen. Es gibt viele andere Beispiele wie dieses, aber im Allgemeinen bleiben alle Daten in der Nähe desjenigen, der die Berechnungen durchführt. Normalerweise möchten Sie so viele Daten wie möglich auf den GPU-RAM verschieben, um die Leistung zu steigern.

Das eigentliche Senden der Daten an die GPU erfolgt über die von Ihnen verwendete API (DirectX / OpenGL oder andere). Das Konzept der Bindung und ähnliches sind lediglich Abstraktionen, sodass die API versteht, was Sie tun möchten.

Bearbeiten für die Bearbeitung:

  • buffer creation with initialisation: Es ist wie der Unterschied zwischen int a = new int[10]und a[0] = 0,a[1] = 1.... etc wenn Sie einen Puffer erstellen, schaffen Sie Platz für die Daten und wenn Sie die Daten initialisieren, legen Sie die gewünschten Daten dort ab.

  • buffer data updateWenn es sich auf dem CPU-RAM befindet vertex * vertices, können Sie damit spielen. Wenn es nicht vorhanden ist, müssen Sie es von der GPU verschieben. vertex * vertices = map(buffer_id);(Map ist eine mythologische Funktion, die Daten von der GPU auf den CPU-RAM verschieben soll. Sie hat auch das Gegenteil.) buffer_id = create_buffer(vertices);

  • binding the buffer as activeEs ist nur ein Konzept, das sie bindingRendering nennen. Es ist ein komplexer Prozess und es ist wie das Aufrufen einer Funktion mit 10000 Parametern. Bindung ist nur ein Begriff, mit dem sie angeben, welcher Puffer wohin geht. Hinter diesem Begriff steckt keine wirkliche Magie. Er konvertiert oder verschiebt keine Puffer und ordnet sie nicht neu zu. Er teilt dem Treiber lediglich mit, dass dieser Puffer beim nächsten Draw-Aufruf verwendet werden soll.

  • API draw callNach all den Bindungs- und Einstellpuffern ist dies der Ort, an dem Gummi auf die Straße trifft. Der Zeichnungsaufruf nimmt alle von Ihnen angegebenen Daten (oder die IDs, die auf die Daten verweisen), sendet sie an die GPU (falls erforderlich) und weist die GPU an, mit der Eingabe der Zahlen zu beginnen. Dies gilt nicht für alle Plattformen, es gibt viele Unterschiede, aber um die Sache einfach zu halten, wird die GPU durch das Ziehen angewiesen, ... zu ziehen.

Raxvan
quelle
2

Die richtigste Antwort ist, es hängt davon ab, wie Sie es programmieren, aber das ist eine gute Sache, um die Sie sich sorgen müssen. Obwohl die GPUs unglaublich schnell geworden sind, ist die Bandbreite zum und vom GPU-RAM nicht so hoch und wird Ihr frustrierendster Engpass sein.

Werden seine Daten nur einmal an den GPU-Speicher gesendet und bleiben dort für immer?

Hoffentlich ja. Für die Rendergeschwindigkeit möchten Sie, dass so viele Daten wie möglich auf der GPU gespeichert werden, anstatt sie bei jedem Frame erneut zu senden. VBOs dienen genau diesem Zweck. Es gibt sowohl statische als auch dynamische VBOs, wobei ersteres für statische Modelle und letzteres für Modelle am besten geeignet ist, deren Scheitelpunkte jeden Frame ändern (z. B. ein Partikelsystem). Selbst wenn es um dynamische VBOs geht, möchten Sie nicht alle Scheitelpunkte in jedem Frame erneut senden. nur diejenigen, die sich ändern.

Im Fall Ihres Gebäudes würden die Scheitelpunktdaten nur dort stehen, und das einzige, was sich ändert, sind Ihre Matrizen (Modell / Welt, Projektion und Ansicht).

Im Fall eines Partikelsystems habe ich einen dynamischen VBO erstellt, der groß genug ist, um die maximale Anzahl von Partikeln zu speichern, die jemals für dieses System existieren werden. Jedes Bild, das ich sende, enthält die Daten für die Partikel, die dieses Bild aussenden, sowie ein paar Uniformen, und das ist alles. Beim Zeichnen kann ich in diesem VBO einen Start- und einen Endpunkt angeben, damit ich keine Partikeldaten löschen muss. Ich kann nur sagen, dass ich die nicht zeichne.

Wenn das Modell tatsächlich gerendert wird, müssen GPU-Prozessoren jedes Mal ihre Daten aus dem GPU-Speicher abrufen? Was ich meine ist - wenn ich 2 Modelle jeweils mehrfach gerendert hätte - wäre es wichtig, wenn ich zuerst das erste mehrfach und dann das zweite mehrfach gerendert hätte oder wenn ich das erste nur einmal, das zweite nur einmal und immer wieder so verschachtelt?

Das Senden mehrerer Draw Calls anstelle von nur einem ist ein viel größeres Limit. Schauen Sie sich das instanziierte Rendering an. Es könnte Ihnen sehr helfen und die Antwort auf diese Frage unbrauchbar machen. Ich hatte einige Treiberprobleme, die ich noch nicht gelöst habe, aber wenn Sie es zum Laufen bringen können, ist das Problem gelöst.

Offensichtlich haben Grafikkarten einen begrenzten RAM - wenn sie nicht alle Modelldaten enthalten können, die für das Rendern von 1 Frame erforderlich sind, wird sie vermutlich weiterhin von jedem Frame aus dem CPU-RAM abgerufen. Ist das richtig?

Ihnen soll nicht der GPU-RAM ausgehen. Wenn Sie dies tun, ändern Sie die Dinge, damit Sie dies nicht tun. In dem sehr hypothetischen Szenario, das Ihnen ausgeht, wird es wahrscheinlich irgendwie abstürzen, aber ich habe es nie erlebt, also weiß ich es ehrlich gesagt nicht.

Ich habe vergessen, eine Unterscheidung zu treffen: Es werden die Daten an die GPU gesendet und die Puffer werden als aktuell festgelegt / gebunden. Verursacht letzteres einen Datenfluss?

Kein signifikanter Datenfluss, nein. Das ist mit Kosten verbunden, aber das gilt für jede Codezeile, die Sie schreiben. Finden Sie heraus, wie viel es Sie kostet und wofür Profiling gedacht ist.

Puffererstellung mit Initialisierung

Raxvans Antwort klingt gut, ist aber nicht ganz richtig. In OpenGL, den Puffer zu schaffen nicht jeden Raum reservieren. Wenn Sie Speicherplatz reservieren möchten, ohne Daten zu übergeben, können Sie glBufferData aufrufen und einfach null übergeben. (Siehe den Abschnitt Notizen hier .)

Aktualisierung der Pufferdaten

Ich vermute, Sie meinen glBufferData oder andere Funktionen wie diese, oder? Hier erfolgt die eigentliche Datenübertragung. (Es sei denn, Sie übergeben Null, wie ich gerade im letzten Absatz gesagt habe.)

Binden des Puffers als aktiv (ist dies nur eine Möglichkeit, der API mitzuteilen, dass dieser Puffer beim nächsten Aufruf von draw gerendert werden soll, und dass er selbst nichts tut?)

Ja, aber es kann noch ein bisschen mehr. Wenn Sie beispielsweise ein VAO (Vertex-Array-Objekt) binden und dann ein VBO binden, wird dieses VBO an das VAO gebunden. Wenn Sie das VAO später erneut binden und glDrawArrays aufrufen, weiß es, welches VBO zu zeichnen ist.

Beachten Sie, dass Sie in vielen Tutorials für jeden VBO einen VAO erstellen müssen. Mir wurde jedoch mitgeteilt, dass dies nicht die beabsichtigte Verwendung ist. Angeblich solltest du eine VAO erstellen und diese mit jedem VBO verwenden, der die gleichen Attribute hat. Ich habe das noch nicht ausprobiert, daher kann ich nicht sicher sagen, ob es besser oder schlechter ist.

API-Draw-Aufruf

Was hier passiert, ist aus unserer Sicht ziemlich einfach. Angenommen, Sie binden eine VAO und rufen dann glDrawArrays auf. Sie geben einen Startpunkt und eine Anzahl an, und der Scheitelpunkt-Shader wird für jeden Scheitelpunkt in diesem Bereich ausgeführt, der seine Ausgaben entlang der Linie weitergibt. Dieser ganze Prozess ist jedoch ein weiterer eigener Aufsatz.

Icy Defiance
quelle
"dann ist das problem gelöst" Ja, das instanziieren würde viel helfen, aber ohne es müsste ich immer noch für jedes objekt einen draw call machen. In beiden Fällen die gleiche Menge. Also frage ich mich, ob die Reihenfolge wichtig ist.
NPS
@NPS - Es zählt einige . Wenn sie bestellt sind, müssen Sie Ihre Bindungen also nicht ständig wechseln, ja, das wird wahrscheinlich eine winzige Menge schneller sein. Aber wenn Sie sich viel Mühe geben müssen, um sie zu sortieren, wird das wahrscheinlich sehr viel teurer sein. Es gibt zu viele Variablen, die von Ihrer Implementierung abhängen, um viel mehr zu sagen.
Icy Defiance