Hier sehen Sie einen Screenshot eines kleinen C ++ - Programms namens Triangle.exe mit einem rotierenden Dreieck, das auf der OpenGL-API basiert.
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.
quelle
Antworten:
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 :
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:
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:
Wenn Sie den Bildschirm nicht löschen, werden Sie einfach über ziehen sie jeden Rahmen. Deshalb rufen wir
glClear
mit demGL_COLOR_BUFFER_BIT
Set 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
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
glBegin
mit derGL_TRIANGLES
Konstante 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 aufrufeglVertex3f
.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:
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
glVertex3f
mit anderen Koordinaten aufrufen (die gedreht werden). Oder ich lasse die GPU die ganze Arbeit machen, indem ichglRotatef
vor dem Zeichnen anrufe:amount
ist natürlich nur ein fester Wert. Wenn Sie animieren möchten , müssenamount
Sie 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
glRotatef
und das alles erledigt es für uns.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.
Bildquelle
quelle
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.
quelle
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.
quelle
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.
quelle
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.
quelle
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.
quelle
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.
quelle