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 ( class
nicht 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 SpriteBatch
dies 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 SetData
und GetData
an Texturen, Vertex-Puffern usw.
An dieser Stelle sollte ich sagen, dass alles, was Sie aufrufen können SetData
(Texturen, Vertex- und Indexpuffer usw.) sowie Effect
s - 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 SetData
auf jedem Thread.)
XNA verkompliziert die Dinge etwas mit seinem Zustand Klassen machen ( BlendState
, DepthStencilState
usw.). 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 GetData
von 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 SetData
Vertex / 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ß :)