Preview

Die Zukunft - Programmierbare Shader-Sprache

Was ist eine Shader-Sprache / Real-Time Shading Language (RTSL)?

Wie in meinem Artikel zum Thema OpenGL & DirectX beschrieben, gibt es seit DirectX 8 jeweils eine Sprache um Vertexdaten (3D-Koordinaten) und Pixeldaten zu manipulieren. Die Vertex Sprache ist recht einfach, da es hier nur um die Manipulation von Vektoren geht und kann, wenn nicht vom GPU beschleunigt, auch von der CPU übernommen werden.
Die weit größeren Möglichkeiten bietet aber die Shader Sprache. Mit ihrer Hilfe können theo. jedem Pixel eigene math. Operationen zugewiesen werden. Somit sind eine nahezu unendliche Menge an Operationen möglich und der Fantasie kaum Grenzen gesetzt.
Allerdings liegt hier auch wieder ein Problem, den die Shaderarbeiten können nicht oder nur sehr langsam vom CPU ausgeführt werden. Also definierte man in DX8 die Pixel Shader - Version 1.0. Schnell wurde aber klar, dass das nicht der Weisheit letzter Schluss war, den NVIDIA und ATI hatten verschiedene Ansätze in ihrer Hardware implementiert. So folgten Version 1.1 bis 1.4. Weiteres dazu
hier .
Da sich die Hardware immer unterscheiden wird, muß also eine einheitliche Sprache her.

Stand der Technik (05.09.2001)

Die heutige Hardware (DirectX 8.0+) ist zu folgendem fähig:
  • vektorbasierte Vertex Verarbeitung
  • mehrere Texturen pro Durchlauf aufbringen
  • mehrere Durchläufe für komplexere Effekte
  • Erweiterte Texturekombinierungsoperationen (Advanced texture combining operation)

Daraus ergeben sich folgende Nachteile:

  • die Hardware ist schwierig zu programmieren
  • Programmierung läuft auf Microcode/Assembler Ebene
  • Aufteilung der Operationen in mehrere Durchläufe ist zeitraubend
  • Funktionalität variiert zwischen den verschiedenen 3D-Chipsätzen
  • heutige API's benutzen ein 3D Pipeline-Modell, was zu hardwarenah aufgebaut ist
    • DirectX 8.0 arbeitet mit eigener Pixel Shader + Vertex Shader Sprache
    • OpenGL arbeitet mit Extensionen

Was würde eine Shader-Sprache bringen?

Vorteile:
  • Verwendung einer Shader Sprache um einen höheren Grad der Abstraktion zu erreichen
  • bietet ein einheitliches Interface für alle 3D-Chipsätze
  • Portabilität 3D-Chipsätze + Plattformen
  • vereinfacht die Komplexität der darunterliegenden Hardware
  • verbirgt Unterschiede der verschiedenen 3D-Chipsätze

mögliche Nachteile:
  • Hardwareabstraktion macht die Benutzung der Hardware einfacher oder schwieriger, je nach Implementation und Hardware
  • könnte etwas Performance kosten, wie bei modernen CPU's - je nach  Compiler bessere oder schlechtere Optimierung als handgetunter Assemblercode

Klingt recht komplex, gibt es da schon was?

Ja, natürlich. Quake 3 benutzt eine eigene Shader Scriptsprache, hier ein kleines Beispiel:
textures/testsurface
{
    {
        map textures/basecolor.tga   
1. Stage: Texture wird geholt
        rgbgen identity
    }
    {
        map $lightmap                 
2. Stage: Lightmap wird mit Texture kombiniert, per "filter" Blendfunktion
        rgbgen identity
        blendfunc filter
    }

}


Vertexdaten (Farbe, Position, Texturekoordinaten) können ebenfalls geändert und erzeugt werden. Die einzelnen Stages werden dann auf verschiedene (oder einen, je nach HW) Renderingdurchläufe umgelegt.
Das Stanford Real-Time Programmable Shading Project ist jedoch wesentlich besser als Beispiel geeignet, da es nicht speziell für ein Spiel o.ä. entwickelt wurde und somit wesentlich vielseitiger einsetzbar ist.

Das Stanford Real-Time Programmable Shading Project

Microsoft's Shading Modell in DirectX 8 ersetzt Teile der traditionellen nicht-programmierbaren Rendering Pipeline mit einer programmierbaren Einheit, welche über Register gesteuert wird. Anstatt die Programmierbarkeit durch mehrere Durchläufe zu erweitern, wird die Programmierbarkeit auf innerhalb eines Durchlaufes beschränkt. Als Resultat behandelt dieses Modell einen Durchlauf als eine Serie von mehreren einfachen Instruktionen, ganz im Gegensatz zum SIMD Prozessor Modell des Stanford Real-Time Programmable Shading Projektes, welches einen Durchlauf als eine komplexe Instruktion betrachtet.
In DirectX 8 werden Vertex- und Pixelmanipulationen getrennt betrachtet. Für beide gibt es getrennte Sprachen. Für die Pixelmanipulation verwendet man standard Texturekombinationsbefehle, welche auf 8 (< PS1.4) pro Durchlauf beschränkt sind, während NVIDIA's OpenGL Extensions wesentlich mehr Möglichkeiten bieten.

Es gibt 2 Hauptunterschiede zwischen dem programmierbaren Vertex-/Pixel-Verarbeitungs Modell von DirectX 8 und der SIMD Prozessor basierten Abstraktion.
  • Die separaten programmierbaren Vertexfunktionen sind im SIMD Modell nicht extra enthalten, weil die Macher davon ausgehen, dass diese Funktionalität sich bald im Pixel/Fragment basierten Teil der Hardware wiederfinden wird.
  • Der zweite Hauptunterschied besteht im Grade der Abstraktion von neuer Hardware.
    Da die Bandbreite immer kritischer wird, gerade zwischen Hostsystem und Grafikeinheit, aber auch zwischen GPU und Framebuffer, ist es von entscheidender Bedeutung, die Anzahl der Renderingdurchläufe zu minimieren. Dies kann beim SIMD Modell wunderbar vereinbart werden, da die VLSI (Very Large Scale Integration - Hochintegration) Technologie immer häufiger eingesetzt wird. Je mehr Operationen pro Durchlauf erledigt werden desto besser.

Diese zwei Vorteile haben die Macher dazu bewogen, sich dieses Modell näher anzusehen.

Weiterhin haben sie Microsoft's und NVIDIA's Ansatz in dreierlei Hinsicht erweitert:
  • Sie verschmelzen die Vertex-/Pixelverarbeitung mit dem Konzept der Berechnungshäufigkeit, was Operationen an verschiedenen "Häufigkeiten" zulässt, anstelle von nur-Vertex oder nur-Pixel.
  • Berechnungshäufigkeit
  • Sie stellen ein Interface bereit "die programmierbare Pipeline", welche alle Teile der verschiedenen Berechnungshäufigkeiten miteinander verknüpft und sie abstrahiert.
  • Sie virtualisieren die bestehende hardwarenahe Pipeline um Ressourceprobleme auszuräumen. Der Programmierer soll sich nicht um die Fähigkeiten / Ressourcen (Register...) der Hardware kümmern müssen. Ein Weg sind z.B. mehrere Renderingdurchläufe, wenn die Hardware es nicht in einem Durchgang schafft.

Das
System setzt auf OpenGL auf und abstrahiert die Grafikpipeline als einen SIMD Prozessor. SIMD (single instruction mutliple data - eine Instruktion wird auf mehrere Daten angewendet -> komplexe Instruktion)
  • Grafikhardware = SIMD Prozessor
  • Ein Renderingdurchlauf = SIMD Instruktion

Features:
  • einfach C-ähnliche Sprache
  • Skalar-, Verktor- und Matrixoperationen
  • separate Opberflächen- und Lichtshader
  • einfache Spezifikation von Berechnungshäufigkeiten
  • erweiterbares Design
  • einfache Erweiterbarkeit durch Module


Dies wird dann, unter Verwendung einer abstrahierten OpenGL Pipeline, kompiliert.

OpenGL

Regeln

Variablen:
  • C - Farbe
  • T - Texture
  • fb - Framebuffer

Operationen:
  • + - Addition
  • x - Multiplikation
  • ov - blend -> Texture auf Oberfläche legen

Hier ein Beispiel:


Kegel

Konstruktion des Kegels (stimmen nicht mit den Renderingdurchläufen überein, da manche Schritte in einem Durchlauf erledigt werden!)
  • a) die Basis-Texture des Kegels
  • b) die Brunswick-Texture kommt auf den Framebuffer
  • c) die kleine rote Kreis-Texture (im unteren Teil des B) kommt auf den Framebuffer
  • d) die Flecken-Texture kommt auf den Framebuffer
  • e) Framebuffer wird mit Cd (diffuses Licht) multipliziert
  • f) Cs (speculares Licht) wird zum Framebuffer addiert

Beispiel

OpenGL, welches keine Extensionen benutzt braucht also 5 Renderingdurchläufe, bis das Bild fertig ist.

Systemüberblick

Hier eine Übersicht des gesamten Systemes:

Systemübersicht

Wie man sieht, muss die Shader-Sprache einige Stufen durchlaufen. Der Aufbau gleicht einer "normalen" Programmiersprache, welche kompiliert wird und als Objektcode der CPU übergeben wird.


Intermediate representation:
  • spezifiziert Programme für die programmierbare Pipeline
  • benutzt die gleichen Operationen & Datentypen wie die Shader-Sprache
  • Oberflächen und Lichter werden hier kombiniert

Programmable Pipeline:

Pipeline

Hier wird die Hardware virtualisiert, das bedeutet, es gibt keinerlei Beschränkungen hinsichtlich der Anzahl der Operationen oder Variablen. (wie die Programmiersprache C im Vergleich zu x86 Assembler)
Weder OpenGL, noch DirectX 8 erfüllen diese Voraussetzungen.
Leider müssen zur Zeit noch ein paar Beschränkungen beachtet werden:
  • Schleifenbildung oder Sprünge sind noch nicht möglich
  • kein freier (zufällige Anordnung) Zugriff auf den Speicher
  • nicht jede HW unterstützt alle Operationen
  • keine durchgängige Verwendung von "floats", GPU's rechnen viel mit Festkommazahlen und beschränkten Wertebereichen (z.B. -1.0 - 1.0)

Front end compilation (drei Schritte):
  • 1. verarbeite Shader Input File
  • 2. führe Operflächen und Lichter zusammen -> ein Pipelineprogramm
  • 3. ermittle Häufigkeiten einzelner Verarbeitungsschritte und teile das Pipelineprogramm dementsprechend
  • erzeuge als Endergebnis ein dreiteiliges Pipelineprogramm, einen Teil für jeden Pipelineteil

Back end compilation:
  • erzeuge ein ausführbares Pipelineprogrammg
  • Hardwaremapping (3 Teile der Pipeline):
    • Primitive Group Processing -> CPU
    • Vertex Processing -> CPU
    • Fragment Processing -> GPU mittels mehrere Renderingdurchläufe
  • wenn die GPU Hardware leistungsfähiger wird, dann werden die CPU Aufgaben auf die GPU verschoben

So sieht der Kompilierungsvorgang aus:

Kompilierung

Es werden verschiedene Arten von Code erzeugt, je nachdem, zu welchem Teil er Pipeline er gehört und wo er ausgeführt wird.

  1. Host Prozessor (CPU)
    • C Code order x86 Code
    • wird benötigt, wenn keine Vertexhardware vorhanden ist
  2. Vertex Programme (Microsoft / NVIDIA Code)
    • Bsp: handgeschriebener Code 38 Instruktionen / generierter Code vom Compiler  44 Instruktionen
  3. Multi-Pass OpengGL 1.2
    • benutzt Mutlitexturing
    • sehr portabel
    • virtualisiert die Hardware
  4. Register Combiners (NVIDIA's OpenGL Extension)

Fragment Processing:

Register Combiner Pipeline

VLIW (Very Long Intruction Word - viele Befehle in einem 64/128/... Bit breitem Block)

Register Combiner

Hier wurd als Beispiel NVIDIA's GF3 OpenGL Extension (NV_register_combiner) benutzt. Man könnte dieses Back end aber auch ohne große Probleme so umschreiben, dass es die DirectX 8 Pixel Shader oder pures OpenGL 1.1 benutzt. Natürlich sind mit beschränktem Funktionsumfang auch nicht alle "Effekte" nutzbar.

Pipeline    

Hier sind sehr schön die austauschbaren Back ends der dreiteiligen Pipeline zu sehen.

Für jede Stufe (Group / Vertex / Fragment) wurden mehrere Module geschrieben. Somit ist es sehr einfach, neue Hardware zu implementieren. Man muss einfach nur ein entsprechendes Modul schreiben.
Die Zukunft hat also begonnen, eine funktionierende Version (Windows/Linux) kann man sich downloaden. Die Entwicklung dauerte 18 Monate und besteht aus etwas 45000 Codezeilen.
Hier noch schnell 2 Bilder, die auf einer GeForce 3 entstanden:

Bild 1

Bild 2