Wie interagieren CPU und GPU bei der Anzeige von Computergrafiken?


58

Hier sehen Sie einen Screenshot eines kleinen C ++ - Programms namens Triangle.exe mit einem rotierenden Dreieck, das auf der OpenGL-API basiert.

Bildbeschreibung hier eingeben

Zugegebenermaßen ein sehr einfaches Beispiel, aber ich denke, es ist auf andere Grafikkartenoperationen anwendbar.

Ich war nur neugierig und wollte den gesamten Prozess vom Doppelklicken auf Triangle.exe unter Windows XP bis zum Drehen des Dreiecks auf dem Monitor kennen. Was passiert, wie interagieren CPU (die zuerst die EXE-Datei verarbeitet) und GPU (die schließlich das Dreieck auf dem Bildschirm ausgibt)?

Ich denke, an der Darstellung dieses rotierenden Dreiecks ist hauptsächlich die folgende Hardware / Software beteiligt:

Hardware

  • HDD
  • Systemspeicher (RAM)
  • Zentralprozessor
  • Videospeicher
  • GPU
  • LCD Bildschirm

Software

  • Betriebssystem
  • DirectX / OpenGL-API
  • Nvidia-Treiber

Kann jemand den Prozess erklären, vielleicht mit einer Art Flussdiagramm zur Veranschaulichung?

Es sollte keine komplexe Erklärung sein, die jeden einzelnen Schritt abdeckt (eine Vermutung, die über den Rahmen hinausgeht), sondern eine Erklärung, der ein IT-Intermediär folgen kann.

Ich bin mir ziemlich sicher, dass viele Leute, die sich selbst als IT-Experten bezeichnen würden, diesen Prozess nicht richtig beschreiben können.


Ihr Dilemma wäre vorbei, wenn Sie nur die GPU als Erweiterung der CPU betrachten könnten!
KawaiKx

Antworten:


55

Ich habe beschlossen, ein wenig über den Programmieraspekt und die Art und Weise, wie Komponenten miteinander kommunizieren, zu schreiben. Vielleicht bringt es etwas Licht in bestimmte Bereiche.

Die Präsentation

Was braucht es, um ein einzelnes Bild, das Sie in Ihrer Frage gepostet haben, auf dem Bildschirm zu zeichnen?

Es gibt viele Möglichkeiten, ein Dreieck auf dem Bildschirm zu zeichnen. Nehmen wir der Einfachheit halber an, dass keine Vertex-Puffer verwendet wurden. (Ein Scheitelpunktpuffer ist ein Speicherbereich, in dem Sie Koordinaten speichern.) Nehmen wir an, das Programm hat der Grafikverarbeitungs-Pipeline einfach jeden einzelnen Scheitelpunkt (ein Scheitelpunkt ist nur eine Koordinate im Raum) in einer Reihe mitgeteilt.

Aber bevor wir etwas zeichnen können, müssen wir zuerst ein Gerüst bauen. Wir werden später sehen, warum :

// Clear The Screen And The Depth Buffer
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 

// Reset The Current Modelview Matrix
glMatrixMode(GL_MODELVIEW); 
glLoadIdentity();

// Drawing Using Triangles
glBegin(GL_TRIANGLES);

  // Red
  glColor3f(1.0f,0.0f,0.0f);
  // Top Of Triangle (Front)
  glVertex3f( 0.0f, 1.0f, 0.0f);

  // Green
  glColor3f(0.0f,1.0f,0.0f);
  // Left Of Triangle (Front)
  glVertex3f(-1.0f,-1.0f, 1.0f);

  // Blue
  glColor3f(0.0f,0.0f,1.0f);
  // Right Of Triangle (Front)
  glVertex3f( 1.0f,-1.0f, 1.0f);

// Done Drawing
glEnd();

Also, was hat das getan?

Wenn Sie ein Programm schreiben, das die Grafikkarte verwenden möchte, wählen Sie normalerweise eine Schnittstelle zum Treiber. Einige bekannte Schnittstellen zum Treiber sind:

  • OpenGL
  • Direct3D
  • CUDA

In diesem Beispiel bleiben wir bei OpenGL. Über die Schnittstelle zum Treiber verfügen Sie nun über alle Tools, die Sie zum Sprechen Ihres Programms mit der Grafikkarte (oder dem Treiber, der dann mit der Karte spricht ) benötigen .

Diese Schnittstelle wird Ihnen bestimmte Werkzeuge zur Verfügung stellen . Diese Tools haben die Form einer API, die Sie von Ihrem Programm aus aufrufen können.

Diese API wird im obigen Beispiel verwendet. Lass uns genauer hinschauen.

Das Gerüst

Bevor Sie wirklich zeichnen können, müssen Sie ein Setup durchführen . Sie müssen Ihr Ansichtsfenster (den Bereich, der tatsächlich gerendert wird), Ihre Perspektive (die Kamera in Ihre Welt) und das verwendete Anti-Aliasing definieren (um die Kanten Ihres Dreiecks zu glätten) ...

Aber das werden wir uns nicht ansehen. Wir werfen einen kurzen Blick auf die Dinge, die Sie für jeden Frame tun müssen . Mögen:

Bildschirm löschen

Die Grafik-Pipeline löscht nicht bei jedem Frame den Bildschirm. Du wirst es sagen müssen. Warum? Deshalb:

Bildbeschreibung hier eingeben

Wenn Sie den Bildschirm nicht löschen, werden Sie einfach über ziehen sie jeden Rahmen. Deshalb rufen wir glClearmit dem GL_COLOR_BUFFER_BITSet an. Das andere Bit ( GL_DEPTH_BUFFER_BIT) teilt OpenGL mit den löschen Tiefen Puffern. Dieser Puffer wird verwendet, um zu bestimmen, welche Pixel vor (oder hinter) anderen Pixeln liegen.

Transformation

Bildbeschreibung hier eingeben
Bildquelle

Die Transformation ist der Teil, in dem wir alle Eingabekoordinaten (die Eckpunkte unseres Dreiecks) nehmen und unsere ModelView-Matrix anwenden. Dies ist die Matrix, die erklärt, wie unser Modell (die Scheitelpunkte) gedreht, skaliert und verschoben wird.

Als nächstes wenden wir unsere Projektionsmatrix an. Dadurch werden alle Koordinaten so verschoben, dass sie korrekt auf unsere Kamera ausgerichtet sind.

Jetzt transformieren wir noch einmal mit unserer Viewport-Matrix. Wir tun dies, um unser Modell an die Größe unseres Monitors anzupassen. Jetzt haben wir eine Reihe von Scheitelpunkten, die gerendert werden können!

Wir werden etwas später auf die Transformation zurückkommen.

Zeichnung

Um ein Dreieck zu zeichnen, können wir OpenGL einfach anweisen, eine neue Liste von Dreiecken zu beginnen, indem wir glBeginmit der GL_TRIANGLESKonstante aufrufen .
Es gibt auch andere Formen, die Sie zeichnen können. Wie ein Dreiecksstreifen oder ein Dreiecksfächer . Dies sind in erster Linie Optimierungen, da sie weniger Kommunikation zwischen der CPU und der GPU erfordern, um die gleiche Anzahl von Dreiecken zu zeichnen.

Danach können wir eine Liste von Sätzen mit 3 Eckpunkten erstellen, die jedes Dreieck bilden sollen. Jedes Dreieck verwendet 3 Koordinaten (da wir uns im 3D-Raum befinden). Zusätzlich biete ich für jeden Scheitelpunkt eine Farbe an, indem ich glColor3f vor dem Aufruf aufrufe glVertex3f.

Der Schatten zwischen den drei Eckpunkten (den drei Ecken des Dreiecks) wird von OpenGL automatisch berechnet . Die Farbe wird über die gesamte Fläche des Polygons interpoliert.

Interaktion

Nun, wenn Sie auf das Fenster klicken. Die Anwendung muss nur die Fenstermeldung erfassen, die den Klick signalisiert. Anschließend können Sie eine beliebige Aktion in Ihrem Programm ausführen.

Dies wird sehr viel schwieriger, wenn Sie mit Ihrer 3D-Szene interagieren möchten.

Sie müssen zunächst genau wissen, bei welchem ​​Pixel der Benutzer auf das Fenster geklickt hat. Unter Berücksichtigung Ihrer Perspektive können Sie dann die Richtung eines Strahls vom Mausklickpunkt in Ihre Szene berechnen. Sie können dann berechnen, ob ein Objekt in Ihrer Szene diesen Strahl schneidet . Jetzt wissen Sie, ob der Benutzer auf ein Objekt geklickt hat.

Also, wie lässt du es drehen?

Transformation

Mir sind zwei Arten von Transformationen bekannt, die im Allgemeinen angewendet werden:

  • Matrixbasierte Transformation
  • Knochenbasierte Transformation

Der Unterschied besteht darin, dass Knochen einzelne Scheitelpunkte betreffen . Matrizen wirken sich immer auf alle gezeichneten Scheitelpunkte gleich aus. Schauen wir uns ein Beispiel an.

Beispiel

Zuvor haben wir unsere Identitätsmatrix geladen , bevor wir unser Dreieck gezeichnet haben. Die Identitätsmatrix bietet einfach überhaupt keine Transformation . Was auch immer ich zeichne, wird nur von meiner Perspektive beeinflusst. Das Dreieck wird also überhaupt nicht gedreht.

Wenn ich es jetzt drehen möchte, könnte ich entweder selbst rechnen (auf der CPU) und einfach glVertex3fmit anderen Koordinaten aufrufen (die gedreht werden). Oder ich lasse die GPU die ganze Arbeit machen, indem ich glRotatefvor dem Zeichnen anrufe:

// Rotate The Triangle On The Y axis
glRotatef(amount,0.0f,1.0f,0.0f);               

amountist natürlich nur ein fester Wert. Wenn Sie animieren möchten , müssen amountSie jedes Bild im Auge behalten und vergrößern.

Also, warte, was ist mit all dem Matrix-Gerede früher passiert?

In diesem einfachen Beispiel müssen wir uns nicht um Matrizen kümmern. Wir rufen einfach an glRotatefund das alles erledigt es für uns.

glRotateerzeugt eine Drehung von angleGrad um den Vektor xyz. Die aktuelle Matrix (siehe glMatrixMode ) wird mit einer Rotationsmatrix multipliziert, wobei das Produkt die aktuelle Matrix ersetzt, als würde glMultMatrix mit der folgenden Matrix als Argument aufgerufen:

x 2 ≤ 1 - c + cx ≤ y ≤ 1 - c - z ≤ sx ≤ z ≤ 1 - c + y ≤ s 0 y ≤ x ≤ 1 - c + z ≤ sy 2 ≤ 1 - c + cy ≤ z ≤ 1 - c - x ⁢ s 0 x ⁢ z ⁡ 1 - c - y ⁢ sy ⁢ z ⁡ 1 - c + x ⁢ sz 2 ⁡ 1 - c + c 0 0 0 1

Danke dafür!

Fazit

Was offensichtlich wird , ist es viel Gerede ist zu OpenGL. Aber es sagt uns nichts. Wo ist die Kommunikation?

Das einzige, was uns OpenGL in diesem Beispiel sagt, ist, wenn es fertig ist . Jede Operation benötigt eine gewisse Zeit. Einige Operationen dauern unglaublich lange, andere sind unglaublich schnell.

Das Senden eines Scheitelpunkts an die GPU geht so schnell, dass ich nicht einmal weiß, wie ich ihn ausdrücken soll. Das Senden von Tausenden von Eckpunkten von der CPU an die GPU in jedem einzelnen Frame ist höchstwahrscheinlich überhaupt kein Problem.

Das Löschen des Bildschirms kann eine Millisekunde oder noch schlimmer dauern (denken Sie daran, dass Sie normalerweise nur etwa 16 Millisekunden Zeit haben, um jeden Frame zu zeichnen), je nachdem, wie groß Ihr Ansichtsfenster ist. Um es zu löschen, muss OpenGL jedes einzelne Pixel in der Farbe zeichnen, die Sie löschen möchten, das können Millionen Pixel sein.

Ansonsten können wir OpenGL so ziemlich nur nach den Fähigkeiten unserer Grafikkarte fragen (maximale Auflösung, maximales Anti-Aliasing, maximale Farbtiefe, ...).

Wir können eine Textur aber auch mit Pixeln füllen, die jeweils eine bestimmte Farbe haben. Jedes Pixel enthält somit einen Wert und die Textur ist eine riesige "Datei", die mit Daten gefüllt ist. Wir können das in die Grafikkarte laden (indem wir einen Texturpuffer erstellen), dann einen Shader laden , diesen Shader anweisen, unsere Textur als Eingabe zu verwenden, und einige extrem schwere Berechnungen für unsere "Datei" ausführen.

Wir können dann das Ergebnis unserer Berechnung (in Form neuer Farben) in eine neue Textur "rendern".

Auf diese Weise können Sie die GPU auf andere Weise für Sie arbeiten lassen. Ich gehe davon aus, dass CUDA ähnlich abschneidet, aber ich hatte nie die Gelegenheit, damit zu arbeiten.

Wir haben das ganze Thema wirklich nur wenig angerührt. Die Programmierung von 3D-Grafiken ist eine Hölle für sich.

Bildbeschreibung hier eingeben
Bildquelle


38

Es ist schwer genau zu verstehen, was du nicht verstehst.

Die GPU verfügt über eine Reihe von Registern, die vom BIOS zugeordnet werden. Dadurch kann die CPU auf den Speicher der GPU zugreifen und die GPU anweisen, Vorgänge auszuführen. Die CPU steckt Werte in diese Register, um einen Teil des GPU-Speichers abzubilden, damit die CPU darauf zugreifen kann. Dann lädt es Anweisungen in diesen Speicher. Anschließend schreibt er einen Wert in ein Register, das die GPU anweist, die Anweisungen auszuführen, die die CPU in ihren Speicher geladen hat.

Die Informationen bestehen aus der Software, die die GPU ausführen muss. Diese Software wird mit dem Treiber gebündelt und der Treiber übernimmt dann die Aufteilung der Verantwortung zwischen CPU und GPU (indem Teile seines Codes auf beiden Geräten ausgeführt werden).

Der Treiber verwaltet dann eine Reihe von "Fenstern" im GPU-Speicher, aus denen die CPU lesen und in die sie schreiben kann. Im Allgemeinen beinhaltet das Zugriffsmuster, dass die CPU Anweisungen oder Informationen in den abgebildeten GPU-Speicher schreibt und dann die GPU über ein Register anweist, diese Anweisungen auszuführen oder diese Informationen zu verarbeiten. Die Informationen umfassen Shader-Logik, Texturen usw.


1
Danke für Ihre Erklärung. Grundsätzlich habe ich nicht verstanden, wie der CPU-Befehlssatz mit dem GPU-Befehlssatz kommuniziert, aber offensichtlich ist es der Treiber, der diesen Teil ausführt. Das habe ich mit Abstraktionsebenen gemeint.
JohnnyFromBF

2
Es ist kein CPU-Befehlssatz beteiligt. Der Treiber und die Laufzeit kompilieren Ihre CUDA, OpenGL, Direct3D usw. in native GPU-Programme / -Kerne, die dann ebenfalls in den Gerätespeicher hochgeladen werden. Der Befehlspuffer verweist dann wie jede andere Ressource auf diese.
Axel Gneiting

2
Ich bin nicht sicher, auf welche Programme Sie sich beziehen (die auf der GPU laufen und in den Treibern enthalten sind). Die GPU ist weitgehend Hardware mit festen Funktionen, und die einzigen Programme, die ausgeführt werden, sind Shader, die von der Anwendung bereitgestellt werden, nicht vom Treiber. Der Treiber kompiliert nur diese Programme und lädt sie dann in den Speicher des GPU.
Ben Richards

1
@ sidran32: In der Kepler-Architektur von nVidia werden beispielsweise Kernel, Streams und Ereignisse von Software erstellt, die auf der GPU ausgeführt wird, nicht (normalerweise) von der CPU. Die GPU-seitige Software verwaltet auch RDMA. Die gesamte Software wird vom Treiber in den GPU-Speicher geladen und als "Mini-OS" auf der GPU ausgeführt, die die GPU-Seite des kooperierenden CPU / GPU-Paares verwaltet.
David Schwartz

@DavidSchwartz Ich habe GPU-Rechenaufgaben vergessen. Sie verhalten sich jedoch in der Implementierung trotzdem ähnlich wie Shader. Ich würde es jedoch nicht als "Mini-OS" bezeichnen, da es nicht die gleiche Funktionalität aufweist, die normalerweise mit Betriebssystemen verbunden ist. Es ist immer noch eine sehr spezielle Software, da die GPU nicht wie eine CPU konstruiert ist (aus gutem Grund).
Ben Richards

13

Ich war nur neugierig und wollte den gesamten Prozess vom Doppelklicken auf Triangle.exe unter Windows XP bis zum Drehen des Dreiecks auf dem Monitor kennen. Was passiert, wie interagieren CPU (die zuerst die EXE-Datei verarbeitet) und GPU (die schließlich das Dreieck auf dem Bildschirm ausgibt)?

Nehmen wir an, Sie wissen tatsächlich, wie eine ausführbare Datei auf einem Betriebssystem ausgeführt wird und wie diese ausführbare Datei von Ihrer GPU an den Monitor gesendet wird, wissen aber nicht, was dazwischen passiert. Schauen wir uns also einen Hardwareaspekt an und gehen wir weiter auf die Antwort auf den Programmiereraspekt ein ...

Was ist die Schnittstelle zwischen CPU und GPU?

Mithilfe eines Treibers kann die CPU über Motherboard- Funktionen wie PCI mit der Grafikkarte kommunizieren und Befehle an diese senden, um einige GPU-Anweisungen auszuführen, auf den GPU-Speicher zuzugreifen / diesen zu aktualisieren , einen auf der GPU auszuführenden Code zu laden und vieles mehr ...

Sie können jedoch nicht direkt über Code mit der Hardware oder dem Treiber kommunizieren. Dies muss also über APIs wie OpenGL, Direct3D, CUDA, HLSL, Cg geschehen. Während die ersteren GPU-Anweisungen ausführen und / oder den GPU-Speicher aktualisieren, führt die letztere tatsächlich Code auf der GPU aus, da sie Physik- / Shader-Sprachen sind.

Warum Code auf der GPU und nicht auf der CPU ausführen?

Während die CPU unsere täglichen Workstation- und Serverprogramme gut ausführen kann, wurde nicht viel über all die glänzenden Grafiken nachgedacht, die Sie in den Spielen dieser Tage sehen. Früher gab es Software-Renderer, die den Trick aus 2D- und 3D-Dingen machten, aber sie waren sehr einschränkend. Hier kam also die GPU ins Spiel.

Die GPU ist optimiert für eine der wichtigsten Berechnungen in der Grafik, die Matrix-Manipulation . Während die CPU jede Multiplikation in einer Matrix-Manipulation einzeln berechnen muss (später haben Dinge wie 3DNow! Und SSE aufgeholt), kann die GPU alle diese Multiplikationen auf einmal ausführen! Parallelität.

Parallele Berechnungen sind jedoch nicht der einzige Grund. Ein weiterer Grund ist, dass die GPU viel näher am Videospeicher ist, wodurch sie viel schneller ist als Umläufe durch die CPU usw.

Wie zeigen diese GPU-Anweisungen / Speicher / Code Grafiken?

Damit dies alles funktioniert, fehlt ein Teil. Wir brauchen etwas, auf das wir schreiben können, das wir dann lesen und an den Bildschirm senden können. Wir können dies tun, indem wir einen Framebuffer erstellen . Was auch immer Sie tun, Sie werden schließlich die Pixel im Framebuffer aktualisieren. die neben der Position auch Informationen über Farbe und Tiefe enthalten.

Lassen Sie uns ein Beispiel geben, in dem Sie irgendwo ein Blutsprite (ein Bild) zeichnen wollten. Zunächst wird die Baumstruktur selbst in den GPU-Speicher geladen, wodurch es einfach ist, sie nach Wunsch neu zu zeichnen. Um das Sprite tatsächlich irgendwo zu zeichnen, können wir das Sprite mithilfe von Scheitelpunkten übersetzen (in die richtige Position bringen), es rastern (von einem 3D-Objekt in Pixel umwandeln) und den Framebuffer aktualisieren. Um eine bessere Vorstellung zu bekommen, ist hier ein OpenGL-Pipeline-Flussdiagramm von Wikipedia:

Dies ist der Kern der gesamten Grafikidee, mehr Forschung ist eine Hausaufgabe für den Leser.


7

Um die Dinge einfach zu halten, können wir es so beschreiben. Einige Speicheradressen sind (vom BIOS und / oder Betriebssystem) nicht für RAM, sondern für die Grafikkarte reserviert. Alle Daten, die mit diesen Werten (Zeigern) geschrieben wurden, werden auf die Karte geschrieben. Theoretisch kann also jedes Programm direkt auf die Videokarte schreiben, wenn es nur den Adressbereich kennt, und genau so wurde es früher gemacht. In der Praxis mit modernen Betriebssystemen wird dies über den Grafiktreiber und / oder die Grafikbibliothek (DirectX, OpenGL usw.) verwaltet.


1
-1 fragt er, wie ein DirectX-API-Aufruf von der CPU mit der GPU kommunizieren kann, und Ihre Antwort lautet "es wird vom Treiber und / oder DirectX verwaltet" ? Dies erklärt auch nicht, wie benutzerdefinierter Code (ala CUDA) ausgeführt werden könnte.
BlueRaja - Danny Pflughoeft

3
Bitte lerne zu lesen. Ich sagte durch Schreiben auf bestimmte Speicheradressen, die für die GPU anstelle von RAM reserviert sind. Und das erklärt, wie Sie alles ausführen können. Ein Speicherbereich ist für eine Karte registriert. Alles, was Sie in diesem Bereich schreiben, geht an die GPU mit Vertex-Verarbeitung, CUDA, was auch immer.
AZ.

5

GPUs werden normalerweise von DMA-Puffern gesteuert. Das heißt, der Treiber kompiliert die Befehle, die er vom User Space-Programm empfängt, in einen Befehlsstrom (Schaltzustand, dies auf diese Weise zeichnen, Kontexte wechseln usw.), der dann in den Gerätespeicher kopiert wird. Anschließend wird die GPU angewiesen, diesen Befehlspuffer über ein PCI-Register oder ähnliche Methoden auszuführen.

Bei jedem Draw-Aufruf usw. kompiliert der User-Space-Treiber den Befehl, der dann den Kernel-Space-Treiber über einen Interrupt aufruft. Anschließend sendet man den Befehlspuffer an den Gerätespeicher und weist die GPU an, mit dem Rendern zu beginnen.

Auf Konsolen kannst du sogar den Spaß haben, das alles selbst zu machen, besonders auf PS3.


0

Ich denke, die CPU sendet Videodaten über den Bus an die GPU und zeigt sie dann an. Eine schnellere GPU kann also mehr Daten von der CPU verarbeiten. Auf diese Weise wird ein Teil der Verarbeitung von CPU-Auslastung zur GPU. Daher kommt man in Spielen schneller voran.

Es ist wie der RAM, in dem die CPU Sachen speichert, damit sie schnell geladen und verarbeitet werden kann. Beides macht Spiele schneller.

Oder Soundkarte oder Netzkarte funktionieren nach dem gleichen Prinzip, dh Daten abrufen und einige Arbeiten der CPU auslagern.


Dies dupliziert eine andere Antwort und fügt keinen neuen Inhalt hinzu. Bitte posten Sie keine Antwort, es sei denn, Sie haben tatsächlich etwas Neues beizutragen.
DavidPostill

0

Ich denke, op ist nicht sicher, was genau die CPU der Grafikkarte vorschreibt und warum die grafikbezogenen Befehle (wie opengl- oder direct3d-Befehle) nicht direkt an die GPU gesendet werden.

Die CPU teilt der GPU lediglich mit, was gerendert werden soll. Alle Anweisungen gehen zuerst durch die CPU, wo sie eingerichtet / initialisiert werden, damit die GPU tatsächlich rendern kann.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.