C ++ und OpenGL ES: glDrawArrays-Aufrufe sind zeitaufwändig

7

Derzeit profiliere ich mein iOS. Alle Aufrufe von glDrawArrays sind teuer. Hier ist zum Beispiel ein Screenshot des Rendering-Teils eines Partikelsystems:

Zeitprofiler

Wie Sie sehen können, nimmt der glDrawArraysAufruf von dieser Methode 39% der CPU-Zeit in Anspruch. Während andere Anrufe mögen glUniform1iund glBindVertexArrayOESsehr schnell sind. Warum ist nicht glDrawArraysso schnell wie die anderen? Beginnen nicht alle diese Methoden mit gl***kurzen Funktionen, die der Treiberwarteschlange einen Befehl hinzufügen? Warum dauert es glDrawArraysim Vergleich zu allen anderen gl-Anrufen länger, bis ein Anruf zur Warteschlange hinzugefügt wird?

Ist das normal oder bedeutet das, dass ich etwas falsch mache?


Als Antwort auf Doug : Nein, das hat nicht funktioniert. Der Aufruf von glFinish () hat tatsächlich eine Weile gedauert, aber der glDrawArraysAufruf nimmt im Vergleich zu den Statusänderungen (wie) immer noch viel Zeit in Anspruch glBindVertexArrayOES.

Mit glFinish

Martijn Courteaux
quelle
glUniform1i und glBindVertexArrayOES machen wirklich nichts, sie aktualisieren mehr oder weniger nur die Zustandsmaschine. Nur tatsächliche Draw-Aufrufe werden im Status ausgeführt.
Doug65536
Wie oft wird glDrawArray in einem Frame aufgerufen?
5ound
Ich habe die glDrawArray-Aufrufe mit einem Xcode OpenGL ES Frame Capture gezählt. In diesem Rahmen hatte ich 47 glDrawArray-Aufrufe.
Martijn Courteaux

Antworten:

8

Die meisten Fahrer arbeiten mit einem Modell für "Lazy State Changes". Dies bedeutet, dass die überwiegende Mehrheit Ihrer gl * -Aufrufe nicht viel mehr tut , als einen Status aufzuzeichnen, einige Parameter zu speichern und dann sofort zurückzukehren. Dies funktioniert einwandfrei, bis ein gl * -Anruf getätigt wird, der tatsächlich etwas mit all diesem Status tun muss (oder der vom Ergebnis eines anderen zuvor getätigten Anrufs abhängt). Zu diesem Zeitpunkt muss der gesamte zuvor gepufferte Status gesammelt und geleert werden, bevor der Anruf getätigt werden kann.

Was Sie also sehen, ist ein völlig normales, wenn auch leicht irreführendes Verhalten: Die für glDrawArrays aufgezeichnete Zeit ist nicht die Zeit, die glDrawArrays allein benötigt , sondern umfasst auch die Zeit für das tatsächliche Festschreiben einer ganzen Reihe früherer GL-Aufrufe.

Es gibt eine Diskussion darüber für D3D , aber das gleiche Grundprinzip gilt für jede moderne Implementierung einer der beiden APIs. Der wichtigste Punkt, den Sie wegnehmen sollten, ist: "Im Allgemeinen wird der Versuch, Ihre GPU durch Timing der CPU zu profilieren, verwirrend und irreführend sein."

Maximus Minimus
quelle
1

Möglicherweise sehen Sie die Auswirkungen der Warteschlange. Höchstwahrscheinlich blockiert die Befehlswarteschlange bei diesem Aufruf, weil beispielsweise die Warteschlange zu voll ist oder Sie vsync voraus sind. Es ist wahrscheinlich eine Menge Arbeit vor dem Aufruf von glDrawArrays.

Sie können versuchen, ein glFinish direkt vor glDrawArrays zu setzen, um festzustellen, ob es sich um die Befehle in der Warteschlange handelt, die die Zeit in Anspruch nehmen, oder um den Aufruf von glDrawArrays. Wenn die Warteschlangeneffekte Ihr tatsächlicher Leistungsengpass sind, wird glFinish anstelle des unschuldigen Aufrufs von glDrawArrays dafür verantwortlich gemacht.

Andererseits verbraucht glDrawArrays möglicherweise viel CPU. Ich würde das oben Genannte versuchen, um sicherzugehen, dass Sie das Richtige suchen.

EDIT: glFinish, NICHT glFlush

Ich sage nicht, lassen Sie den glFlush CALL dort. Komm schon Leute, glaubst du wirklich, ich sage, lass den glFlush-Anruf dort? Es ist eine Leistungsuntersuchung .

doug65536
quelle
Eine Antwort wurde in meinen Fragenkörper eingefügt.
Martijn Courteaux
Gut, es entlädt viel Zeit, die es anscheinend in glDrawArrays benötigt. Auf den zweiten Blick machen Sie etwas sehr Schlechtes: Sie laden Daten hoch und verwenden sie dann SOFORT. Tun Sie das niemals. Was wahrscheinlich passiert, ist das Blockieren des Wartens auf das Ende der Puffersubdaten. Machen Sie dies wie im vorherigen Frame im Voraus und verwenden Sie doppelte Puffer und wechseln Sie sich ab. Das Blockieren kann sehr wohl ein tatsächlicher Polling-Spinloop sein, da die Wartezeit sehr kurz wäre.
Doug65536
Denken Sie daran, nur weil glBufferSubData zurückgegeben wird, heißt das nicht, dass es sich im Videospeicher befindet. Es kann nur in einen DMA-fähigen Speicherbereich kopiert und asynchron hochgeladen werden, sodass der glBufferSubData-Aufruf frühzeitig zurückkehren kann. AFAIK-Fahrer machen das.
Doug65536
0

Die naive Implementierung, bei jedem gl-Aufruf nur in einen Befehlspuffer zu schreiben, macht wenig Sinn, wenn der Treiber möglicherweise Statusänderungen optimieren kann. Daher ist es sinnvoll, die Verarbeitung so spät wie möglich zu verschieben, insbesondere bei einer gekachelten / gruppierten Rendering-Architektur, wie es die meisten OpenGL ES-Implementierungen sind.

Auf einigen Architekturen ist es tatsächlich sinnvoll, eine Shader-Verarbeitung erst bei einem glDrawArrays-Aufruf durchzuführen. (Und ja, sogar Ihre ES1.x-Hardware hat wahrscheinlich irgendeine Form von Shadern unter der Haube).

Das Aufrufen von glFinish / glFlush wie doug65536 schlägt vor, dass dies bei gekachelten / gruppierten Architekturen nicht hilfreich ist, da dies zu einem vollständigen Pipeline-Flush führt, der einen sehr, sehr, sehr hohen Overhead auf solchen hat - was dazu führt, dass alles, was sich bisher in der Warteschlange befand, auf jede Kachel und jedes Forcen gerendert wird eine Auflösung beim nächsten Renderzyklus.

Jari Komppa
quelle
Es sieht so aus, als ob Sie wissen, wovon Sie sprechen. Aber ich vermisse eine Antwort auf meine Frage. Ist dieses Verhalten normal? Arbeiten GPU und CPU gleichzeitig auf einem iPod Touch 4. Generation / iPhone 4S ? Wenn sie gleichzeitig arbeiten (was ich vermute, wenn ich die Ergebnisse des Hinzufügens eines glFinish()Anrufs sehe ), was dauert dann so lange für den glDrawArraysAnruf?
Martijn Courteaux
Ich bin weder mit den Interna des powervr-Treibers vertraut, noch habe ich Benchmark-Daten von verschiedenen Geräten, um sie zu sichern, aber ich würde sagen, dass dies normal ist. Was Sie beim Aufruf von glFinish () sehen, ist viel mehr Arbeit als erwartet, wenn dies ein direkter Renderer wäre.
Jari Komppa
@MartijnCourteaux Bitte beachten Sie, dass ich NICHT gesagt habe, den glFinish-Anruf dort zu lassen. Ich bat ihn, das Profil mit dem glFinish-Aufruf dort zu wiederholen, um zu sehen, ob es sich um eine zuvor in die Warteschlange gestellte Arbeit handelte, die die CPU wirklich beanspruchte. Beachten Sie, dass glDrawArrays in seinem neuen Profil viel weniger Zeit benötigt. Das liegt hauptsächlich daran, dass der glFinish-Aufruf jetzt die gesamte Arbeit in der Warteschlange erledigt.
Doug65536
@JariKomppa Ich erinnere mich, dass sich ATI-Treiber in einigen Szenarien drehten, weil die Wartezeit sehr kurz und die Latenz eines IRQ und DPC und all das zu lang wäre. Es ist möglich, dass sich der Treiber auf einem "Ready" -Bit in einem Register tief in der glFinish-Implementierung dreht, selbst bei vollständig hardwarebeschleunigtem OpenGL.
Doug65536
@ doug65536 Denken Sie daran: Dies ist eine "Scene Capture" -Architektur. glFinish wird das Rendern aller erfassten Geometrien zusammen mit Tonnen anderer Dinge bewirken. Dies ist viel schwerer als nur auf einen Zeitstempel zu warten.
Jari Komppa