Informationen zum Rendern, Stapeln, zur Grafikkarte, zur Leistung usw. + XNA?

12

Ich weiß, der Titel ist ein bisschen vage, aber es ist schwer zu beschreiben, wonach ich wirklich suche, aber hier ist es.

Wenn es um CPU-Rendering geht, ist die Leistung meist leicht einzuschätzen und unkompliziert, aber wenn es um die GPU geht, weil ich keine technischen Hintergrundinformationen habe, bin ich ratlos. Ich benutze XNA, also wäre es schön, wenn die Theorie damit zusammenhängen könnte.

Also, was ich eigentlich wissen möchte, was passiert wann und wo (CPU / GPU), wenn Sie bestimmte Zeichenaktionen ausführen? Was ist eine Charge? Welchen Einfluss haben Effekte, Projektionen usw.? Bleiben die Daten auf der Grafikkarte erhalten oder werden sie bei jedem Schritt übertragen? Wenn es um Bandbreite geht, handelt es sich um eine interne Bandbreite der Grafikkarte oder um die Pipeline von der CPU zur GPU?
Hinweis: Ich suche nicht wirklich nach Informationen darüber, wie der Zeichenprozess abläuft. Das ist die Aufgabe der GPU. Ich bin an all dem Aufwand interessiert, der dem vorausgeht.

Ich würde gerne verstehen, was passiert, wenn ich Action X mache, um meine Architekturen und Praktiken darauf abzustimmen.

Alle Artikel (möglicherweise mit Codebeispielen), Informationen, Links und Tutorials, die einen besseren Einblick in das Schreiben besserer Spiele geben, werden sehr geschätzt. Vielen Dank :)

Aidiakapi
quelle
2
Obwohl dies ursprünglich XNA war, habe ich das DirectX-Tag hinzugefügt, da dies die zugrunde liegende Technologie ist - es kann Ihnen helfen, bessere Antworten zu erhalten. Schauen Sie sich auch diese Antwort an, die Ihnen einen guten Ausgangspunkt bieten könnte.
Andrew Russell
@ AndrewRussell Vielen Dank :). Ich habe bereits verschiedene Artikel zu diesem Thema gelesen, darunter auch diesen. Aber es hat nicht alles abgedeckt, was ich wissen möchte.
Aidiakapi,

Antworten:

20

Ich mag es, Leistung in Begriffen von " Grenzen " zu sehen. Es ist eine praktische Möglichkeit, ein ziemlich kompliziertes, miteinander verbundenes System zu konzipieren. Wenn Sie ein Leistungsproblem haben, stellen Sie die Frage: "Welche Grenzen habe ich?" (Oder: "Bin ich CPU / GPU gebunden?")

Sie können es in mehrere Ebenen aufteilen. Auf der höchsten Ebene haben Sie die CPU und die GPU. Möglicherweise sind Sie CPU-gebunden (GPU wartet im Leerlauf auf CPU) oder GPU-gebunden (CPU wartet auf GPU). Hier ist ein guter Blogbeitrag zum Thema.

Sie können es weiter aufschlüsseln. Auf der CPU- Seite verwenden Sie möglicherweise alle Zyklen für Daten, die sich bereits im CPU-Cache befinden. Möglicherweise ist der Arbeitsspeicher begrenzt , sodass die CPU im Leerlauf darauf wartet, dass Daten aus dem Hauptspeicher eingehen ( optimieren Sie also Ihr Datenlayout ). Sie könnten es noch weiter aufschlüsseln.

(Während ich einen umfassenden Überblick über die Leistung in Bezug auf XNA mache, möchte ich darauf hinweisen, dass eine Zuordnung eines Referenztyps ( classnicht struct), obwohl normalerweise billig, den Garbage Collector auslösen kann, der viele Zyklen verbrennt - insbesondere auf Xbox 360 . sehen Sie hier für weitere Details).

Auf der GPU- Seite werde ich zunächst auf diesen ausgezeichneten Blog-Beitrag verweisen, der viele Details enthält. Wenn Sie einen wahnsinnigen Detaillierungsgrad in der Pipeline wünschen , lesen Sie diese Reihe von Blog-Posts . ( Hier ist eine einfachere ).

Um es hier einfach auszudrücken, einige der großen sind die: " Füllgrenze " (wie viele Pixel können Sie in den Backbuffer schreiben - oft können Sie überzeichnen), " Shadergrenze " (wie kompliziert Ihre Shader sein können und Wie viele Daten können Sie durchschieben?), " Texturabruf / Texturbandbreitenbegrenzung " (Wie viele Texturdaten können Sie zugreifen?).

Und jetzt kommen wir zu dem großen Punkt - was Sie wirklich fragen - wo CPU und GPU interagieren müssen (über die verschiedenen APIs und Treiber). Lose gibt es die " Batch-Limit " und " Bandbreite ". (Beachten Sie, dass der erste Teil der Serie, die ich bereits erwähnt habe, ausführliche Details enthält.)

Grundsätzlich geschieht ein Batch ( wie Sie bereits wissen ), wenn Sie eine der GraphicsDevice.Draw*Funktionen aufrufen (oder wenn ein Teil von XNA SpriteBatchdies für Sie erledigt). Wie Sie zweifellos bereits gelesen haben, erhalten Sie einige Tausend * davon pro Frame. Dies ist ein CPU-Limit - es konkurriert also mit Ihrer anderen CPU-Auslastung. Es ist im Grunde genommen der Fahrer, der alles zusammenpackt, was Sie ihm gesagt haben, um es zu zeichnen und an die GPU zu senden.

Und dann ist da noch die Bandbreite zur GPU. So viele Rohdaten können Sie dorthin übertragen. Dies umfasst alle Statusinformationen, die mit Batches verbunden sind - von der Einstellung des Rendering-Status und der Shader-Konstanten / -Parameter (einschließlich Matrizen für Welt / Ansicht / Projekt) bis hin zu Scheitelpunkten bei der Verwendung der DrawUser*Funktionen. Es enthält auch Aufrufe von SetDataund GetDataan Texturen, Vertex-Puffern usw.

An dieser Stelle sollte ich sagen, dass alles, was Sie aufrufen können SetData(Texturen, Vertex- und Indexpuffer usw.) sowie Effects - im GPU-Speicher verbleiben. Es wird nicht ständig an die GPU gesendet. Ein Zeichenbefehl, der auf diese Daten verweist, wird einfach mit einem Zeiger auf diese Daten gesendet.

(Außerdem: Sie können nur Zeichenbefehle vom Haupt-Thread senden, aber Sie können SetDataauf jedem Thread.)

XNA verkompliziert die Dinge etwas mit seinem Zustand Klassen machen ( BlendState, DepthStencilStateusw.). Diese Zustandsdaten werden pro Zeichenaufruf (in jedem Charge) gesendet. Ich bin nicht 100% sicher, aber ich habe den Eindruck, dass es faul gesendet wird (es wird nur der Status gesendet, der sich ändert). In beiden Fällen sind Zustandsänderungen im Verhältnis zu den Kosten eines Stapels kostengünstig.

Zuletzt ist noch die interne GPU-Pipeline zu erwähnen . Sie möchten das Löschen nicht erzwingen, indem Sie in Daten schreiben, die noch gelesen werden müssen, oder Daten lesen, die noch geschrieben werden müssen. Ein Pipeline-Flush bedeutet, dass auf den Abschluss der Operationen gewartet wird, sodass sich beim Zugriff auf Daten alles in einem konsistenten Zustand befindet.

Die zwei besonderen Fälle, auf die Sie achten müssen, sind: Aufruf GetDatavon etwas Dynamischem - insbesondere von einem RenderTarget2D, auf das die GPU möglicherweise schreibt. Dies ist extrem schlecht für die Leistung - tun Sie es nicht.

Der andere Fall ruft SetDataVertex / Index-Puffer auf. Wenn Sie dies häufig tun müssen, verwenden Sie a DynamicVertexBuffer(auch DynamicIndexBuffer). Auf diese Weise kann die GPU erkennen, dass sie sich häufig ändert, und kann intern Pufferung durchführen, um ein Leeren der Pipeline zu vermeiden.

(Beachten Sie auch, dass dynamische Puffer schneller sind als DrawUser*Methoden - sie müssen jedoch mit der maximal erforderlichen Größe vorab zugewiesen werden.)

... und das ist so ziemlich alles, was ich über die XNA-Leistung weiß :)

Andrew Russell
quelle
Vielen Dank! Das ist genau das, was ich suchte und in der Hoffnung für :).
Aidiakapi,
1
Ein paar hundert Batches pro Frame klingen zu pessimistisch. Die Faustregel, die ich immer gehört habe, ist 2K bis 3K Stapel pro Frame. Es ist bekannt, dass einige Spiele auf dem PC bis zu 10K erreichen, aber ich denke, das erfordert große Sorgfalt.
Nathan Reed
Ganz recht. Die Zahl "ein paar hundert" stammt aus dem Papier "Batch Batch Batch", in dem "25.000 Batches / s bei 100% einer 1-GHz-CPU" aufgeführt sind. Aber dieses Papier ist jetzt ein Jahrzehnt alt, und Treiber und CPUs haben sich seitdem erheblich verbessert. Ich werde dies (und meine anderen) aktualisieren, um "ein paar tausend" zu lesen.
Andrew Russell