Warum ist Thread-Sicherheit für Grafik-APIs so wichtig?

21

Sowohl Vulkan als auch DirectX12 sollen threadsicher verwendet werden können. Die Leute scheinen darüber aufgeregt zu sein.

Warum ist dies ein so großes Feature? Die "echte" Verarbeitung wird auf jeden Fall auf einer separaten Verarbeitungseinheit über die Speicherbrücke geworfen.

Auch wenn es so groß ist, warum wurde bis jetzt keine threadsichere Grafik-API veröffentlicht?

Ratschenfreak
quelle
Dieser Artikel ist viel "gamer-fokussierter", aber es könnte Ihnen einige Einblicke geben ... pcgamer.com/what-directx-12-meanss-for-gamers-and-developers
glampert

Antworten:

13

Der Hauptgewinn wäre, dass es einfacher wäre, CPU-Aufgaben in mehrere Threads zu unterteilen, ohne alle schwierigen Probleme beim Zugriff auf die Grafik-API lösen zu müssen. Normalerweise müssten Sie entweder den Kontext aktuell machen (was sich negativ auf die Leistung auswirkt) oder eine Warteschlange bereitstellen und die Grafik-API in einem einzelnen Thread aufrufen. Ich glaube nicht, dass auf diese Weise Leistung gewonnen wird, da die GPU sie sowieso sequentiell verarbeitet, aber dies erleichtert die Arbeit der Entwickler erheblich.

Der Grund, warum dies bisher wahrscheinlich nicht gemacht wurde, ist, dass DirectX und OpenGL in einer Zeit erstellt wurden, in der Multithreading nicht wirklich offensichtlich war. Auch das Khronos-Board ist sehr konservativ bei der Änderung der API. Ihre Meinung zu Vulkan ist auch, dass es neben OpenGL existieren wird, da beide unterschiedliche Zwecke erfüllen. Es war wahrscheinlich nicht bis vor kurzem, dass Paralismus so wichtig wurde, als Verbraucher Zugang zu immer mehr Prozessoren erhalten.

BEARBEITEN: Ich meine nicht, dass durch die Arbeit mit mehreren CPUs keine Leistung erzielt wird. Es ist nicht sinnvoll, Ihre Aufrufe in mehrere Threads aufzuteilen, um Texturen / Shader schneller zu erstellen. Die Leistung wird eher dadurch verbessert, dass mehr Prozessoren beschäftigt sind und die GPU mit den zu erledigenden Aufgaben beschäftigt ist.

Maurice Laveaux
quelle
1
Als zusätzliche Anmerkung funktioniert OpenGL im Allgemeinen nur auf einem Thread, sodass eine grafikintensive App einen Kern maximal ausnutzen kann. So etwas wie Vulkan ermöglicht es mehreren Threads, Befehle an eine Warteschlange zu senden, was bedeutet, dass viele Grafikaufrufe von mehreren Threads durchgeführt werden können.
Soapy
9

Es ist viel Arbeit auf der CPU erforderlich, um einen Frame für die GPU einzurichten, und ein guter Teil dieser Arbeit befindet sich im Grafiktreiber. Vor DX12 / Vulkan war diese Grafiktreiber-Arbeit durch das Design der API im Wesentlichen gezwungen, Single-Threading-Operationen durchzuführen.

Die Hoffnung ist, dass DX12 / Vulkan diese Einschränkung aufhebt und die Treiberarbeit auf mehreren CPU-Threads innerhalb eines Frames parallel ausgeführt werden kann. Dies ermöglicht eine effizientere Nutzung von Multicore-CPUs, sodass Game-Engines komplexere Szenen verschieben können, ohne an die CPU gebunden zu sein. Das ist die Hoffnung - ob dies in der Praxis umgesetzt wird, müssen wir in den nächsten Jahren abwarten.

Um es ein wenig zu erläutern: Die Ausgabe eines Game-Engine-Renderers ist ein Stream von DX / GL-API-Aufrufen, die die Abfolge der Operationen zum Rendern eines Frames beschreiben. Es gibt jedoch eine große Distanz zwischen dem Strom von API-Aufrufen und den tatsächlichen Binärbefehlspuffern, die die GPU-Hardware verbraucht. Der Treiber muss die API-Aufrufe sozusagen in die Maschinensprache der GPU "kompilieren". Dies ist kein trivialer Prozess - es umfasst eine große Anzahl von Übersetzungen von API-Konzepten in Hardware-Realitäten auf niedriger Ebene, die Überprüfung, um sicherzustellen, dass die GPU niemals in einen ungültigen Zustand versetzt wird, das Verwirren von Speicherzuordnungen und Daten sowie das Nachverfolgen von Statusänderungen, um das Problem zu lösen Korrigieren Sie Befehle auf niedriger Ebene usw. Der Grafiktreiber ist für all diese Dinge verantwortlich.

In DX11 / GL4 und früheren APIs wird diese Arbeit normalerweise von einem einzelnen Treiber-Thread ausgeführt. Selbst wenn Sie die API von mehreren Threads aus aufrufen (was Sie zum Beispiel mit DX11-Listen verzögerter Befehle tun können), fügt sie einer Warteschlange nur etwas Arbeit hinzu, damit der Treiber-Thread später durchkaut. Ein wichtiger Grund dafür ist das bereits erwähnte State Tracking. Viele der GPU-Konfigurationsdetails auf Hardware-Ebene erfordern Kenntnisse über den aktuellen Status der Grafik-Pipeline. Daher gibt es keine gute Möglichkeit, die Befehlsliste in Blöcke aufzuteilen, die parallel verarbeitet werden können. Jeder Block muss genau wissen, welcher Status gestartet werden soll mit, obwohl der vorherige Block noch nicht verarbeitet wurde.

Das ist eines der großen Dinge, die sich in DX12 / Vulkan geändert haben. Für eine Sache, übernehmen sie fast die ganze Grafik - Pipeline Zustand in einem Objekt und zum anderen (zumindest in DX12) , wenn Sie eine Befehlsliste starten Erstellen Sie müssen eine anfängliche Pipeline Zustand bereitzustellen; Der Status wird nicht von einer Befehlsliste zur nächsten vererbt. Auf diese Weise muss der Treiber im Prinzip nichts über vorherige Befehlslisten wissen, bevor er mit dem Kompilieren beginnen kann. Dadurch kann die Anwendung ihr Rendering in parallelisierbare Blöcke aufteilen und vollständig kompilierte Befehlslisten erstellen, die dann kompiliert werden können zusammen verkettet und mit einem Minimum an Aufwand an die GPU gesendet.

Natürlich gibt es viele andere Änderungen in den neuen APIs, aber was Multithreading angeht, ist dies der wichtigste Teil.

Nathan Reed
quelle
5

Moderne GPUs verfügen in der Regel über einen einzelnen Frontend-Bereich, der einen vollständig linearen Befehlsstrom von der CPU verarbeitet. Ob dies ein natürliches Hardware-Design ist oder ob es sich einfach aus der Zeit heraus entwickelt hat, als es einen einzelnen CPU-Kern gab, der Befehle für die GPU generiert, ist umstritten, aber es ist vorerst die Realität. Wenn Sie also einen einzelnen linearen Strom von Zustandsbefehlen generieren, ist es natürlich sinnvoll, diesen Strom linear auf einem einzelnen Thread in der CPU zu generieren! Recht?

Nun, moderne GPUs haben im Allgemeinen auch ein sehr flexibles, einheitliches Backend, das viele verschiedene Dinge gleichzeitig bearbeiten kann. Im Allgemeinen arbeitet die GPU mit Scheitelpunkten und Pixeln mit ziemlich feiner Granularität. Es gibt keinen großen Unterschied zwischen einer GPU, die 1024 Eckpunkte in einer Zeichnung und 512 + 512 Eckpunkte in zwei verschiedenen Zeichnungen verarbeitet.

Dies ist ein ziemlich natürlicher Weg, um weniger Arbeit zu leisten: Anstatt in einem einzigen Aufruf eine große Anzahl von Eckpunkten auf die GPU zu werfen, teilen Sie Ihr Modell in Abschnitte auf, führen Sie in diesen Abschnitten ein billiges Grobculling durch und senden Sie jeden Abschnitt einzeln, wenn er den Test besteht Keulungstest. Wenn Sie es mit der richtigen Granularität tun, sollten Sie eine schöne Beschleunigung bekommen!

Leider sind in der aktuellen Realität der Grafik-API Zeichnungsaufrufe auf der CPU extrem teuer. Eine vereinfachte Erklärung, warum: Statusänderungen auf der GPU möglicherweise nicht direkt mit Grafik-API-Aufrufen korrespondieren. Viele Grafik-API-Aufrufe setzen einfach einen bestimmten Status im Treiber, und der Draw-Aufruf, der von diesem neuen Status abhängt, wird ausgeführt und überprüft Status, der als geändert seit dem letzten Zeichnen markiert ist, schreibt ihn in den Befehlsstrom für die GPU und leitet dann das Zeichnen ein. Dies ist alles Arbeit, die gemacht wird, um einen schlanken und mittleren Befehlsstrom für die GPU-Frontend-Einheit zu erhalten.

Daraus ergibt sich, dass Sie ein Budget für Draw Calls haben, das vollständig vom Overhead des Fahrers bestimmt wird . (Ich glaube, ich habe gehört, dass Sie heutzutage mit etwa 5.000 pro Frame für einen 60-fps-Titel davonkommen können.) Sie können dies um einen großen Prozentsatz erhöhen, indem Sie diesen Befehlsstrom in parallelen Blöcken erstellen.

Es gibt auch andere Gründe (zum Beispiel asynchrone Zeitverzögerung für VR-Latenzverbesserungen), aber dies ist ein wichtiger Grund für grafikgebundene Spiele und andere Drawcall-lastige Software (wie 3D-Modellierungspakete).

John Calsbeek
quelle