Die tatsächlichen Kosten einer bestimmten Zustandsänderung variieren mit so vielen Faktoren, dass eine allgemeine Antwort nahezu unmöglich ist.
Erstens kann jede Statusänderung möglicherweise sowohl CPU-seitige als auch GPU-seitige Kosten verursachen. Die CPU-Kosten können je nach Treiber und Grafik-API vollständig im Haupt-Thread oder teilweise in einem Hintergrund-Thread bezahlt werden.
Zweitens können die GPU-Kosten vom Arbeitsaufwand im Flug abhängen. Moderne GPUs sind sehr gut ausgelastet und lieben es, viel Arbeit auf einmal im Flug zu haben. Die größte Verlangsamung ist das Abwürgen der Pipeline, sodass alles, was sich gerade im Flug befindet, vor einer Änderung des Status in den Ruhestand gehen muss. Was kann einen Pipeline-Stillstand verursachen? Nun, es hängt von deiner GPU ab!
Das, was Sie tatsächlich wissen müssen, um die Leistung hier zu verstehen, ist: Was müssen der Treiber und die GPU tun, um Ihre Statusänderung zu verarbeiten? Dies hängt natürlich von Ihrer GPU und auch von Details ab, die ISVs häufig nicht öffentlich teilen. Es gibt jedoch einige allgemeine Grundsätze .
GPUs sind in der Regel in ein Frontend und ein Backend unterteilt. Das Frontend verarbeitet einen Strom von Befehlen, die vom Treiber generiert werden, während das Backend die gesamte eigentliche Arbeit erledigt. Wie ich schon sagte, das Backend liebt es, viel Arbeit im Flug zu haben, aber es braucht einige Informationen, um Informationen über diese Arbeit zu speichern (möglicherweise vom Frontend ausgefüllt). Wenn Sie genügend kleine Chargen kicken und das gesamte Silizium verbrauchen, um die Arbeit zu verfolgen, muss das Frontend blockieren, auch wenn viele ungenutzte Pferdestärken herumstehen. Ein Prinzip hier: Je mehr Statusänderungen (und kleine Draws), desto wahrscheinlicher ist es, dass Sie das GPU-Backend aushungern lassen .
Während eine Zeichnung tatsächlich verarbeitet wird, führen Sie im Grunde nur Shader-Programme aus, die Speicherzugriffe durchführen, um Ihre Uniformen, Ihre Vertex-Pufferdaten, Ihre Texturen abzurufen, aber auch die Kontrollstrukturen, die den Shader-Einheiten mitteilen, wo Ihre Vertex-Puffer und Deine Texturen sind. Und die GPU hat auch Caches vor diesen Speicherzugriffen. Wenn Sie also neue Uniformen oder neue Textur- / Pufferbindungen auf die GPU werfen, wird wahrscheinlich ein Cache-Fehler auftreten, wenn diese zum ersten Mal gelesen werden müssen. Ein weiteres Prinzip: Die meisten Statusänderungen verursachen einen GPU-Cache-Miss. (Dies ist am sinnvollsten, wenn Sie konstante Puffer selbst verwalten: Wenn Sie konstante Puffer zwischen den einzelnen Ziehungen beibehalten, bleiben diese mit größerer Wahrscheinlichkeit auf der GPU im Cache.)
Ein großer Teil der Kosten für Statusänderungen für Shader-Ressourcen ist die CPU-Seite. Wann immer Sie einen neuen konstanten Puffer festlegen, kopiert der Treiber höchstwahrscheinlich den Inhalt dieses konstanten Puffers in einen Befehlsstrom für die GPU. Wenn Sie eine einzelne Uniform festlegen, verwandelt der Fahrer diese sehr wahrscheinlich in einen großen konstanten Puffer hinter Ihrem Rücken. Deshalb muss er den Versatz für diese Uniform im konstanten Puffer nachschlagen, den Wert in den konstanten Puffer kopieren und dann den konstanten Puffer markieren so schmutzig, dass es vor dem nächsten Aufruf von draw in den Befehlsstrom kopiert werden kann. Wenn Sie eine neue Textur oder einen neuen Scheitelpunktpuffer binden, kopiert der Treiber wahrscheinlich eine Kontrollstruktur für diese Ressource. Wenn Sie eine diskrete GPU in einem Multitasking-Betriebssystem verwenden, muss der Treiber außerdem jede Ressource nachverfolgen, die Sie verwenden, und wenn Sie damit beginnen, damit der Kernel s Der GPU-Speichermanager kann sicherstellen, dass der Speicher für diese Ressource im VRAM der GPU gespeichert ist, wenn die Auslosung erfolgt. Prinzip:Statusänderungen veranlassen den Treiber, den Speicher zu verschieben, um einen minimalen Befehlsstrom für die GPU zu generieren.
Wenn Sie den aktuellen Shader ändern, verursachen Sie wahrscheinlich einen GPU-Cache-Fehler (sie haben auch einen Anweisungs-Cache!). Grundsätzlich sollte die CPU-Arbeit auf das Einfügen eines neuen Befehls in den Befehlsstrom beschränkt sein, der "use the shader" lautet. In Wirklichkeit gibt es jedoch eine ganze Reihe von Shader-Kompilierungen, mit denen man sich befassen muss. GPU-Treiber kompilieren Shader sehr häufig träge, selbst wenn Sie den Shader im Voraus erstellt haben. Diesbezüglich relevanter ist jedoch, dass einige Status nicht von Haus aus von der GPU-Hardware unterstützt werden und stattdessen im Shader-Programm kompiliert werden. Ein beliebtes Beispiel sind Scheitelpunktformate: Diese können in den Scheitelpunkt-Shader kompiliert werden, anstatt einen separaten Status auf dem Chip zu haben. Wenn Sie also Scheitelpunktformate verwenden, die Sie zuvor noch nicht mit einem bestimmten Scheitelpunkt-Shader verwendet haben, Möglicherweise zahlen Sie jetzt eine Menge CPU-Kosten, um den Shader zu patchen und das Shader-Programm auf die GPU zu kopieren. Darüber hinaus können der Treiber und der Shader-Compiler alle möglichen Aktionen ausführen, um die Ausführung des Shader-Programms zu optimieren. Dies kann bedeuten, dass Sie das Speicherlayout Ihrer Uniformen und Ressourcensteuerungsstrukturen so optimieren, dass sie gut in benachbarte Speicher- oder Shaderregister gepackt sind. Wenn Sie also den Shader wechseln, überprüft der Treiber möglicherweise alles, was Sie bereits an die Pipeline gebunden haben, und packt es in ein völlig anderes Format für den neuen Shader und kopiert es dann in den Befehlsstrom. Prinzip: Dies kann bedeuten, dass Sie das Speicherlayout Ihrer Uniformen und Ressourcensteuerungsstrukturen so optimieren, dass sie gut in benachbarte Speicher- oder Shaderregister gepackt sind. Wenn Sie also den Shader wechseln, überprüft der Treiber möglicherweise alles, was Sie bereits an die Pipeline gebunden haben, und packt es in ein völlig anderes Format für den neuen Shader und kopiert es dann in den Befehlsstrom. Prinzip: Dies kann bedeuten, dass Sie das Speicherlayout Ihrer Uniformen und Ressourcensteuerungsstrukturen so optimieren, dass sie gut in benachbarte Speicher- oder Shaderregister gepackt sind. Wenn Sie also den Shader wechseln, überprüft der Treiber möglicherweise alles, was Sie bereits an die Pipeline gebunden haben, und packt es in ein völlig anderes Format für den neuen Shader und kopiert es dann in den Befehlsstrom. Prinzip:Das Wechseln der Shader kann zu einem starken Mischen des CPU-Speichers führen.
Framebuffer-Änderungen sind wahrscheinlich am implementierungsabhängigsten, sind aber auf der GPU im Allgemeinen ziemlich teuer. Ihre GPU ist möglicherweise nicht in der Lage, mehrere Draw-Aufrufe an verschiedene Render-Ziele gleichzeitig zu verarbeiten. Daher muss möglicherweise die Pipeline zwischen diesen beiden Draw-Aufrufen unterbrochen werden. Möglicherweise müssen die Caches geleert werden, damit das Renderziel später gelesen werden kann. Möglicherweise muss die Arbeit, die während des Zeichnens verschoben wurde, aufgelöst werden. (Es ist sehr verbreitet, eine separate Datenstruktur zusammen mit Tiefenpuffern, MSAA-Renderzielen und vielem mehr zu akkumulieren. Dies muss möglicherweise abgeschlossen werden, wenn Sie von diesem Renderziel wegwechseln. Wenn Sie eine GPU verwenden, die auf Kacheln basiert Wie bei vielen mobilen GPUs muss möglicherweise ein ziemlich großer Teil der eigentlichen Shading-Arbeit gelöscht werden, wenn Sie von einem Frame-Puffer wegwechseln.) Prinzip:Das Ändern von Renderzielen ist auf der GPU teuer.
Ich bin sicher, dass das alles sehr verwirrend ist, und leider ist es schwierig, zu genau zu werden, da Details oft nicht öffentlich sind, aber ich hoffe, es ist ein halbwegs anständiger Überblick über einige der Dinge, die tatsächlich passieren, wenn Sie einen Staat anrufen Änderungsfunktion in Ihrer bevorzugten Grafik-API.