Pixelshader: Reicht "Partial Precision" aus?
29. November 2004 / von aths / Seite 1 von 2
ATI teilt ganz gerne kleine Hiebe gegen Nvidia aus, wenn es um die Frage der Rechengenauigkeit geht. Ebenso brüstet sich Nvidia mit FP32-Support, den ATI derzeit nicht durchgehend bieten kann – das nach der DX9-Spezifikation aber natürlich auch gar nicht muss. Doch was ist mit noch kleineren Datenformaten, FP16, oder gar FX12 – kann man diese wirklich verwenden, ohne Bildqualität zu opfern? Auf diese Frage gibt es kein "ja" oder "nein". Deshalb bieten wir heute einige Sätze zu diesem Thema an. Dabei geht es nicht nur um die Bildqualität, sondern auch um die Frage der Leistung – und darum, welche Optimierungen zulässig sind und welche nicht.
Intern vs. extern
Es ist wünschenswert, dass die interne Rechengenauigkeit möglichst hoch ist, und zwar auf jeden Fall höher als die Ausgabe-Genauigkeit auf dem Monitor. So gibt es Fälle, in denen sich 32-Bit-Rendering auch dann schon lohnt, wenn nur 16-Bit-Texturen zum Einsatz kommen. Allerdings wird selbst beim "16-Bit-Rendering" intern ohnehin mit 32 Bit (RGBA 8888) gerendert, lediglich der Framebuffer ist 16-bittig (RGBA 5650). Der 32-Bit-Framebuffer mit 24 Bit Farbauflösung ist bis heute Standard, obwohl einige Grafikkarten auch schon mehr unterstützen.
DirectX8 empfiehlt bereits 48 Bit interne Genauigkeit (4x 12 Bit Fixpoint), aber schreibt als Mindestanforderung nur 36 Bit vor. Dies sind 4x 9 Bit, wobei es sich um Vorzeichen-Bit + die üblichen 8 Bit pro Kanal Wortbreite handelt. Bestimmte Berechnungen laufen auf der GeForce 1/2/3/4 in 10-Bit-Genauigkeit, nur kann man diese Werte zu keinem Zeitpunkt "abgreifen", da die Register nur 9 Bit speichern. Direct9 verlangt pro Komponente 24 Bit Floating Point, erlaubt sind – sofern das "_PP-Flag" gesetzt ist, was für "Partial Precision" steht – auch 16 Bit Floating Point.
DirectX 7/8 Grafikkarten beweisen, dass man auch mit sehr kleinen Formaten arbeiten kann. Doch Pixelshader werden neuerdings nicht mehr nur dazu eingesetzt, möglichst effizient Multitexturing zu rendern. Der erweiterte Einsatz (wofür Pixelshader spätestens ab DirectX9 auch gedacht sind) erfordern präzisere und dynamischere Formate.
Dabei geht es uns vor allem um Farbverknüpfungsoperationen. Texturoperationen sollten immer mit FP32 ausgeführt werden, das macht sogar die Radeon 9700. Sofern allerdings eine Texturkoordinate im Pixelshader arithmetisch verrechnet wird, sinkt bei der Radeon 9700 bis X800 die Genauigkeit auf FP24, da der Wert beim "Eintreten" in den arithmetischen Pixelshader-Teil auf FP24 gerundet wird. Eine GeForce 3/4 berechnet Texturoperationen übrigens auch schon im FP32-Format.
Die Mantisse von FP16 ist 10 Bit breit. Damit ist das FP16-Format immer mindestens so gut wie ein Fixpointformat von 11 Bit Breite, denn FP-Formate arbeiten üblicherweise mit einer "packed Mantissa", wo das erste Bit nicht gespeichert wird, und 10 + 1 = 11. Inklusive dem Vorzeichenbit wäre man bei dem, was FX12 (Fixpoint mit 12 Bit) bietet. Allerdings bringt FP16 bei kleinen Zahlen eine deutlich höhere Auflösung, wenn man mit FX12 vergleicht. Außerdem sind auch sehr große Werte möglich, was zum Beispiel für die arithmetische Normalisierung erforderlich ist. In DirectX8 ist Normalisierung auch schon möglich, aber etwas umständlich und erfordert einen Texturzugriff, arithmetische Normalisierung ist erst ab DirectX9 sinnvoll.
Heutige Hardware
DirectX9 und ARB2 erlauben den Einsatz von FP16, sofern im Shader-Code bestimmte Hinweise gesetzt wurden. NV35 und NV40 profitieren davon, weil sie zusätzliche FP16-Recheneinheiten anbieten, außerdem nimmt ein FP16-Register nur halb so viel Speicherplatz weg wie ein FP32-Register. Bei den Radeon-Karten sind die temporären Register kein Problem. Allerdings gibt es hier ein "dependent read limit" von 4, und die Pipeline ist weniger tief als bei den GeForce-Karten. Somit lässt sich sicherstellen, dass normalerweise für alle Pixel, die in Bearbeitung sind, der Speicherplatz für temporäre Register nicht ausgeht.
Gesampelte Texturen und Zwischenergebnisse von Shader-Rechnungen werden meist in diesen temporären Registern gespeichert, und natürlich gibt es nicht beliebig viele davon. Die älteren Radeons der DirectX9-Klasse bieten 12, die GeForceFX 22, die Radeon X700 und X800 wie die GeForce6-Serie 32 Stück davon. Das heißt aber nicht, dass für jeden Pixel ,der in Bearbeitung ist, tatsächlich 32 Register in Hardware geboten werden. Zur Not müssen neue Pixel warten, ehe sie in die Pipeline kommen, damit in der Hardware wieder freie temporäre Register verfügbar sind.
Der Treiber meldet für die Karte soundso viele temporäre Register. Tatsächlich stehen diese aber nur virtuell zur Verfügung. FP16-Rechnungen bringen bei den GeForce-Karten oft Geschwindigkeitsvorteile, auch wenn die eigentliche Rechnung nicht schneller ist. Aber da im File für Temps weniger Platz beansprucht wird, lassen sich mehr Pixel gleichzeitig in die Pipeline schieben, was heißt, dass die Zahl der Wartetakte verringert wird.
ATIs Radeon-Karten haben dagegen aufgrund der einfacheren Pixelshader-Architektur keine ernsthaften Probleme mit den Temps. Im Gegenteil, hier gewinnt man an Leistung, wenn man erst einmal in einem Block schön viele Texturen in entsprechend viele Temps sampelt (im Hintergrund steht hierbei die Parallelverarbeitung des Phasenkonzeptes aus Radeon-8500-Zeiten).
FX12: Ein Stiefkind?
Jede GeForceFX bietet noch FX12-Rechenwerke, damit wird also der alten Microsoft-Empfehlung für DirectX8 Folge geleistet. Die Pixelshaderhardware gestattet, FX12, FP16 und FP32 anzuwenden. Allerdings gestattet weder DirectX9 Pixelshader 2.0 und höher, noch OpenGL ARB2, das FX12-Format zu nutzen. FP16 hingegen ist erlaubt, sofern bestimmte Flags gesetzt wurden.
Wir können nicht nachvollziehen, warum FX12 verboten ist, wenn man noch FP16 erlaubt. Zwar löst FX12 gerade die kleinen Werte, die es besonders nötig haben, schlechter auf als FP16. Doch um einzelne Lichtquellen zu berechnen, ist das Format eigentlich gut genug. Man bedenke, dass das Renderziel ja ein 8-Bit-Format ist – was zu Zeiten der Riva TNT als modern galt, und hier hat sich bis heute nichts grundlegendes getan. Radeon-Karten mit 10-Bit-Farbkanalauflösung bietet in dem Modus kein Antialiasing, die GeForce6-Serie mit 16-Bit-Floatingpoint-Farbkanalauflösung gestattet in diesem Modus ebenfalls kein Antialiasing. Es ist nicht abzusehen, dass sich ein besserer Framebuffer als RGBA8888 in ein oder zwei Jahren durchsetzt.
Genau deshalb reicht für kurze Berechnungspassagen das FX12-Format eigentlich aus. Auch beim Addieren von Texturen, in einigen Fällen auch beim Multiplizieren, wäre man mit FX12 noch im sicheren Bereich. Bei längeren abhängigen Shaderteilen muss man diese dann halt in hoher Präzision rechnen lassen. Es wäre kein Nachteil, wenn z. B. die Normalisierung beim Bumpmapping in FP16 (oder besser) ausgeführt wird, man aber den Wert aus der Basistextur und der Lichtintensität vom Dot3-Bumpmapping auch mit FX12 verrechnen dürfte.
Sofern man Effekte programmiert, die Medium Dynamic Range bzw. High Dynamic Range benötigen, ist der von FX12 darstellbare Bereich (von -2 bis +1,999) nutzlos. FP16 erlaubt Werte von rund -65000 bis +65000, FP24 löst dagegen 64mal feiner auf als FP16 und erlaubt außerdem extrem kleine oder große Werte. Halten wir soweit fest: FP24 ist bei aktuell üblichen Shaderlängen ein Allround-taugliches Format. Für besonders hochwertige Effekte braucht man FP32. Auch moderne Renderfarmen arbeiten meistens mit diesem Format – und berechnen Effekte in echter Kinoqualität. So lässt sich vermuten, dass FP32 längerfristig aktuell bleibt, während FP24 eher als Zwischenschritt anzusehen wäre.
Für viele der heute üblichen Echtzeit-Effekte reicht auch FP16, selbst dann, wenn "HDR-Lighting" zum Einsatz kommt. Für etliche heutige Grafikeffekte (die kein HDR brauchen) reicht sogar FX12 noch locker aus. Die GeForceFX darf ihre FX12-Rechenwerke beim Pixelshader 2.0 und höher, sowie bei ARB2-Shadern aber nicht nutzen. Damit wird sie aus unserer Sicht unsinnigerweise behindert.
Sicher, für einfache Effekte könnte man der Leistung wegen ja einen Pixelshader 1.1 verwenden (den Radeon-Karten ist es egal, ob ein Pixelshader als 1.1 oder 2.0 ausgeführt wird, sie arbeitet immer mit voller Geschwindigkeit. Doch oft setzen sich Effekte aus verschiedenen Teilen zusammen. Eigentlich wäre es sinnvoll, wenn die einfachen Teile, wie z. B. simple Arithmetik mit Textur-Farben, mit FX12 ausgeführt werden dürften. Nur die schwierigeren Parts müssten dann mit einem FP-Format berechnet werden. Jedenfalls wurde die GeForceFX für diese Arbeitsweise entworfen.
Natürlich stellt Nvidia für OpenGL die entsprechenden Extensions zur Verfügung, welche die "Mischung" von allen drei Formaten erlauben. Dementsprechend arbeitete John Carmack auch an einem NV30-Pfad für Doom 3. Dass später die Performance des ARB2-Pfades von Doom 3 mit der Performance des NV30-Pfades gleichzog, wird vermutlich auf Shaderreplacement zurückzuführen sein – denn auf einer GeForceFX sind Rechnungen im FP-Format immer deutlich langsamer, als die gleiche Rechnung im Fixpoint-Format.
Die DirectX9-Radeons haben im Pixelshader gar keine Fixpoint-Rechenwerken mehr (in den ROPs werden aber noch bestimmte Operationen mit Fixpoint-Formaten ausgeführt). Egal ob DirectX6 oder DirectX9, die Radeon rechnet immer mit FP24. Bis einschließlich DirectX7 werden aber pro Pass meistens nur zwei Texturen verrechnet, so dass man aus FP24 keine Qualitätsvorteile ziehen kann.
Doch zurück zu Doom 3: Wenn John Carmack sich entschließt (warum auch immer), den NV30-Pfad zu entfernen, "darf" Nvidia eigenmächtig entscheiden, weiterhin FX12-Rechnungen zu verwenden? Wenn es sich um eine zuschaltbare Option handeln würde, die den Fragmentshader vorher testet und nach gewissen Kriterien entscheidet nur FX12 zu verwenden (weil es ausreichen würde), hätte man eine sinnvolle Optimierungsstrategie. Wir gehen allerdings davon aus, dass einfach Shader mit handoptimierten Versionen ersetzt werden, was der Autor prinzipiell ablehnt.