GL EXT geometry shader4

Aus DGL Wiki
Wechseln zu: Navigation, Suche

EXT_geometry_shader4

Info DGL.png Die Orginalspezifikation finden Sie unter "Ressourcen" am Ende des Artikels.

Am 11. Juli 2008 wurde diese Extension vom ARB anerkannt. Die neue Extension GL_ARB_geometry_shader4 ist bis auf die Namensänderung von EXT zu ARB identisch mit GL_EXT_geometry_shader4. Im Augenblick (Feb 2009) wird GL_ARB_geometry_shader4 aber noch nicht von den Treibern unterstützt.


Abfragestring

GL_EXT_geometry_shader4

Beschreibung

Die Extension EXT_geometry_shader4 definiert einen neuen Shader-Typ, Geometryshader genannt. Der Geometryshader wird nach dem Vertexshader jedoch vor dem Cliping ausgeführt. Ein Blick auf die Renderingpipeline sollte dies verdeutlichen.

Der Geometryshader funktioniert ähnlich wie der Vertexshader arbeitet jedoch auf Primitiven. Als Eingabe erhält er ein einzelnes Primitiv (Point, Line, Line-Adjacency, Triangle oder Triangle-Adjacency). Er kann die Attribute sämtlicher Vertices in diesem Primitiv lesen und verwenden um neue Primitive zu erzeugen. Der Primitiv-Typ den ein Geometryshader erzeugen soll (Point, Line-Strip oder Triangle-Strip) muss im voraus festgelegt werden. Um ein neues Primitiv zu erzeugen müssen nacheinander die einzelnen Vertices emittiert werden. Dabei können auch mehrere zusammenhanglose Primitive erzeugt werden. Die erzeugten Primitive werden geclippt und anschließend ganz normal verarbeitet wie ein äquivalentes, von der Anwendung definiertes Primitiv.

Die Extension EXT_geometry_shader4 stellt vier zusätzliche Primitiv-Typen bereit: GL_LINES_ADJACENCY_EXT, GL_LINE_STRIP_ADJACENCY_EXT, GL_TRIANGLES_ADJACENCY_EXT und GL_TRIANGLE_STRIP_ADJACENCY_EXT. Einige der Vertices in diesen neuen Typen sind nicht Teil des eigentlichen Primitives sondern repräsentieren angrenzende Nachbar-Vertices. Diese zusätzlichen Informationen können vom Geometryshader verwendet werden um die emittierten Vertices mit den Nachbar-Primitiven abzustimmen.

Ein Geometryshader erwartet einen bestimmten Primitiv-Typ als Eingabe welcher im voraus festgelegt werden muss. Liefert die Anwendung Primitive eines anderen Typs wird ein Fehler ausgelöst.

Wissenswertes

  • Alle Primitive die den Geometryshader verlassen sollen muss man selbst erzeugen. Man kann also auch einfach keine Primitive erzeugen und so eine Art discard für den Vertexshader erhalten.
  • Will man die Nachbarschaftsinformationen von Dreiecken, z.B. mit GL_TRIANGLES_ADJACENCY_EXT als Input-Datentyp verwenden, müssen die Daten bereits im Vertexbuffer (etc.) so gegeben sein. Ein Dreieck besteht dann z.B. aus 6 statt 3 Vertices. Der Geometryshader kann die Nachbarschaftsinformationen nicht einfach so erraten, sondern ein Mesh muss entsprechend vorbereitet werden. Auf Grund der häufigen Wiederverwendung der gleichen Vertices, empfiehlt sich offensichtlich die Verwendung eines Indexbuffers, um die Pipeline zu entlasten.
  • Verwendet man einen Geometryshader, muss man auch einen Vertexshader definieren. Wenn möglich sollte man z.B. die Transformation der Vertices in den Vertexshader auslagern. Ansonsten müsste der gleiche Vertex mehrfach transformiert werden, wenn er zu mehreren Primitiven gehört.
  • Man muss die maximal mögliche Anzahl der im Geometryshader erzeugten Vertices im voraus festlegen. Für diese Vertices muss Speicher im Cache reserviert werden, der dann nicht von anderen parallel laufenden Shaderunits benutzt werden kann. Will man also viele Vertices oder sehr große Vertices erzeugen hat man möglicherweise das Problem, dass nicht alle Shaderunits arbeiten können und das ganze wird entsprechend langsam. Mit der Vertexgröße ist hier die Anzahl der varying-Variablen zwischen Geometry- und Fragmentshader gemeint.
  • Die Geschwindigkeit hängt direkt und nicht-linear von der Anzahl/Größe der erzeugten Vertices ab, da der Speicher reserviert werden muss bevor die Grafikkarte weiß wie viele Vertices tatsächlich erzeugt werden. Nicht-linear bedeutet hier, dass es eine gewisse Anzahl geschriebener Komponenten gibt, bei der die Verarbeitung dann plötzlich massiv langsamer wird. Dies dürfte natürlich auch von Grafikkarte zu Grafikkarte variieren. Wähle also die maximale Anzahl deiner Vertices sowie auch deren Größe so klein wie möglich. [1]
  • Aus diesem Grund eignet sich der Geometryshader leider im Augenblick eben nur für kleine Modifikationen und nicht für LOD-Stufen, Tessellation und ähnliche Heavy-Output-Algorithmen. [1]
  • Interessant ist die Verwendung des Geometryshaders in Kombination mit Transform-Feedback. Die Extension GL_EXT_tranform_feedback erlaubt es die verarbeiteten Vertices komplett oder auch nur einzelne Varyings direkt wieder in einen Buffer zu schreiben. Ein solcher Buffer kann anschließend beliebig als Vertexbuffer, Indexbuffer oder auch Texturbuffer eingesetzt werden.

Verwendung

Geometryshader im Programm

Einen Geometryshader lädt man im wesentlichen genauso, wie man auch einen Vertex- oder Fragmentshader lädt. Für Details sollte man sich das GLSL-Tutorial noch einmal genauer anschauen. Das folgende ist C++-Quellcode, den bei Gelegenheit mal jemand in Delphi übersetzen könnte.

Neu sind hier eigentlich nur die Aufrufe von glProgramParameteriEXT. Hier werden die maximale Anzahl erzeugte Vertices, sowie Input- bzw. Output-Primitiv-Typen festgelegt. Zu beachten ist, dass dies vor dem linken geschieht.

GLuint m_prgTest = glCreateProgram();
GLuint vsTest = loadShader(GL_VERTEX_SHADER, "data/shader/test.vs");
GLuint gsTest = loadShader(GL_GEOMETRY_SHADER_EXT, "data/shader/test.gs");
glAttachShader(m_prgTest, vsTest);
glAttachShader(m_prgTest, gsTest);
glProgramParameteriEXT(m_prgTest, GL_GEOMETRY_VERTICES_OUT_EXT, 6);
glProgramParameteriEXT(m_prgTest, GL_GEOMETRY_INPUT_TYPE_EXT, GL_TRIANGLES);
glProgramParameteriEXT(m_prgTest, GL_GEOMETRY_OUTPUT_TYPE_EXT, GL_TRIANGLE_STRIP);
glLinkProgram(m_prgTest);
checkLinkError("data/shader/test", m_prgTest);
glDeleteShader(vsTest);
glDeleteShader(gsTest);
glUseProgram(m_prgTest);

Die folgenden Hilfsfunktionen sollten relativ selbst erklärend sein.

GLuint loadShader(GLenum type, const char* filename) {
    // Quelltext aus Datei laden
    FILE* file = fopen(filename, "rb");
    if (file == 0) { throw Exception(std::string("error loading '") + filename + "'"); }
    fseek(file, 0, SEEK_END);
    int size = ftell(file);
    fseek(file, 0, SEEK_SET);
    char* data = new char[size+1];
    int read = fread(data,1,size,file);
    if (read != size) { throw Exception(std::string("error while loading '") + filename + "'"); }
    fclose(file);
    data[size]=0;
    const char* data_const = data;

    // Shader-Objekt erzeugen, Quelltext übergeben und compilieren
    GLuint shader = glCreateShader(type);
    glShaderSource(shader, 1, &data_const, NULL);
    glCompileShader(shader);

    // Quelltext wieder freigeben
    delete[] data;

    // Infolog auslesen und eventuelle Fehler oder Warnungen ausgeben
    int len;
    glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &len);
    if (len > 1) {
        char buffer[len];
        glGetShaderInfoLog(shader, len, NULL, buffer);
        std::cout << "file: " << filename << "\n" << buffer << "\n";
    }     
    int status = 0;
    glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
    if (status) { 
        return shader; 
    }
    else {
        throw Exception("shader compilation failed.");
    }
}
void checkLinkError(const std::string name, GLuint program) {
    // Link-Status und ggf. Infolog auslesen
    int status = 0;
    glGetProgramiv(program, GL_LINK_STATUS, &status);
    if (!status) { 
        int len;
        glGetProgramiv(program, GL_INFO_LOG_LENGTH, &len);
        if (len > 1) {
            char buffer[len];
            glGetProgramInfoLog(program, len, NULL, buffer);
            std::cout << buffer << "\n";
        }
        throw Exception(name + ": program linking failed.");
    }
}

Geometryshader in GLSL

Zunächst definieren wir einen trivialen Vertexshader. Die Modelview-Transformation und die Projektion wollen wir im Geometryshader vornehmen. Die Varying-Variable foo dient hier nur der Anschauung wie Varyings an den Geometryshader übergeben werden und erfüllt keinen weiteren Nutzen.

varying float foo;

void main() {
    gl_Position = gl_Vertex;
    foo = 42.0;
}

Nun der Geometryshader. Dieser zugegeben etwas sinnlose Shader soll einfach das aktuelle Dreieck ausgeben, sowie zusätzlich eine um den Vektor (1,0,0) verschobene Kopie erzeugen. Zunächst müssen wir sicherstellen, dass die Extension EXT_geometry_shader4 in GLSL verfügbar ist, ansonsten würde der Compiler den Dienst verweigern. Dies geht wie bei anderen Shadererweiterungen (z.B. GL_EXT_gpu_shader4) über das Präprozessor-Statement #extension. Neue Primitive werden, wie im Beispiel zu sehen, mit den neuen Funktionen EmitVertex() und EndPrimitive() erzeugt. Da es im Geometryshader sowohl Input- als auch Output-Varyings gibt, wurde das Schlüsselwort varying um die Schlüssel in und out erweitert. Der Rest des Shaders sollte selbst erklärend sein.

#extension GL_EXT_geometry_shader4: enable

varying in float foo[3]; // beachte: ein Array der Größe 3, für die drei Vertices eines Dreiecks!

varying out float bar; // varying für den Fragmentshader, kein Array!
                       // Da wir keinen Fragmentshader definieren, wird diese varying ignoriert.

void main() {
    for (int i=0; i<3; ++i) {
        gl_Position = gl_PositionIn[i];
        gl_Position = gl_ModelViewProjectionMatrix * gl_Position;
        bar = (foo[0] + foo[1] + foo[2]) / 3.0;
        EmitVertex(); // Vertex geschrieben, nächsten Vertex anfangen
    }
    EndPrimitive(); // erstes Dreieck wurde geschrieben, TriangleStrip beenden

    for (int i=0; i<3; ++i) {
        gl_Position = gl_PositionIn[i] + vec4(1,0,0,0);
        gl_Position = gl_ModelViewProjectionMatrix * gl_Position;
        bar = (foo[0] + foo[1] + foo[2]) / 3.0;
        EmitVertex();
    }

    // EndPrimitive() hier unnötig, wird am Ende automatisch aufgerufen.
}

Primitiv-Typen

Im folgenden werden die neu hinzugekommenen Primitiv-Typen vorgestellt, sowie erläutert wann wo welche Primitiv-Typen verwendet werden dürfen.

Die neuen Typen: *_ADJACENCY_EXT

Lines-adjacency.png

Line-strip-adjacency.png

Triangles-adjacency.png

Triangle-strip-adjacency.png

Input-Typen

Eine Übersicht über die auf Seiten der Anwendung erlaubten Primitiv-Typen (links) und ihre Entsprechungen als Input-Geometrie im Geometryshader (rechts) [2].

GL_POINTSGL_POINTS
GL_LINESGL_LINES
GL_LINE_STRIP
GL_LINE_LOOP
GL_LINES_ADJACENCY_EXTGL_LINES_ADJACENCY_EXT
GL_LINE_STRIP_ADJACENCY_EXT
GL_TRIANGLESGL_TRIANGLES
GL_TRIANGLE_STRIP
GL_TRIANGLE_FAN
GL_TRIANGLES_ADJACENCY_EXTGL_TRIANGLES_ADJACENCY_EXT
GL_TRIANGLE_STRIP_ADJACENCY_EXT
GL_QUADSnicht definiert?
Möglicherweise GL_TRIANGLES?
GL_QUAD_STRIP
GL_POLYGON

Der Input-Geometrie-Typ wird beispielsweise wie folgt gesetzt:

glProgramParameteriEXT(m_prgTest, GL_GEOMETRY_INPUT_TYPE_EXT, GL_TRIANGLES);

Output-Typen

Erlaubte Output-Primitiv-Typen sind die folgenden.

GL_POINTS
GL_LINE_STRIP
GL_TRIANGLE_STRIP

Die scheinbare Beschränkung auf die obigen drei Typen ist eigentlich keine. Ein Triangle-Strip zählt beispielsweise als ein Primitiv und man darf durchaus mehrere Primitive, also mehrere Strips erzeugen. Will man also einzelne, unabhängige Dreiecke generieren, schreibt man einfach mehrere Strips mit jeweils einem Dreieck. Der Output-Geometrie-Typ wird beispielsweise wie folgt gesetzt:

glProgramParameteriEXT(m_prgTest, GL_GEOMETRY_OUTPUT_TYPE_EXT, GL_POINTS);

Neues in GLSL

Eingebaute Variablen

Hier finden wir die aus dem Vertexshader bekannten. Im Geometryshader sind aber auch einige zusätzliche Variablen definiert.

  • vec4 gl_Position
In diese Variable muss die transformierte Position des Vertex geschrieben werden. Diese Variable ist zunächst undefiniert. Der Wert der Variable gl_Position aus dem Vertexshader findet sich im Varying-Array gl_PositionIn[].
  • float gl_PointSize
  • vec4 gl_ClipVertex
  • int gl_Layer
Wird benötigt, wenn man in mehrere Layer eines Framebuffers gleichzeitig rendert. Für Details siehe den Abschnitt "Dependencies on EXT_framebuffer_object" in der Orginalspezifikation.
  • int gl_PrimitiveIDIn
Der Shader zählt die verarbeiteten Primitive. Der aktuelle Zählerstand kann über diese Variable abgefragt werden. Die Zahl entspricht der Position des Primitives im Vertex- bzw. Indexbuffer. Diese Variable kann nur gelesen werden.
  • int gl_PrimitiveID
Will man die PrimitiveID im Fragmentshader verwenden, kann man in diese Variable schreiben. Die Variable ist dann als gl_PrimitiveID im Fragmentshader verfügbar.


Eingebaute Konstanten

Im Geometryshader ist neben den aus dem Vertexshader bekannten Konstanten auch die folgende neue Konstante verfügbar:

  • int gl_VerticesIn
Diese Konstante gibt die Anzahl der Input-Vertices, also die Größe der Arrays an. Man kann also einen Shader theoretisch auch für verschiedene Input-Typen auslegen. Die neuen Input-Typen *_ADJACENCY_EXT wurden weiter oben erklärt.
Primitiv-TypWert von gl_VerticesIn
GL_POINTS1
GL_LINES2
GL_LINES_ADJACENCY_EXT4
GL_TRIANGLES3
GL_TRIANGLES_ADJACENCY_EXT6


Eingebaute Uniforms

Im Geometryshader sind die gleichen eingebauten Uniform-Variablen verfügbar wie im Vertexshader.


Eingebaute Input-Varyings

Im Geometryshader sind vom Prinzip die gleichen Varyings verfügbar wie im Vertexshader. Nur handelt es sich hier Arrays und zur besseren Unterscheidung zwischen Input und Output des Geometryshaders wurde jeweils das Suffix "In" angehängt.

  • varying in vec4 gl_FrontColorIn[gl_VerticesIn];
  • varying in vec4 gl_BackColorIn[gl_VerticesIn];
  • varying in vec4 gl_FrontSecondaryColorIn[gl_VerticesIn];
  • varying in vec4 gl_BackSecondaryColorIn[gl_VerticesIn];
  • varying in vec4 gl_TexCoordIn[gl_VerticesIn][]; // gl_MaxTextureCoords beachten
  • varying in float gl_FogFragCoordIn[gl_VerticesIn];
  • varying in vec4 gl_PositionIn[gl_VerticesIn];
  • varying in float gl_PointSizeIn[gl_VerticesIn];
  • varying in vec4 gl_ClipVertexIn[gl_VerticesIn];


Eingebaute Output-Varyings

Die eingebauten Output-Varyings für Geometryshader sind die gleichen wie im Vertexshader.

  • varying out vec4 gl_FrontColor;
  • varying out vec4 gl_BackColor;
  • varying out vec4 gl_FrontSecondaryColor;
  • varying out vec4 gl_BackSecondaryColor;
  • varying out vec4 gl_TexCoord[]; // gl_MaxTextureCoords beachten
  • varying out float gl_FogFragCoord;


Eingebaute Funktionen

Ein Geometryshader kann alle Funktionen verwenden, die auch im Vertexshader verfügbar sind. Also Mathe-Funktionen, Texturzugriffe, und was es alles so gibt. Zusätzlich wurden die folgenden beiden Funktionen für den Geometryshader eingeführt:

  • void EmitVertex();
Ein Aufruf dieser Funktion erzeugt einen neuen Vertex im aktuellen Primitiv. Intern wird dabei ein Arrayindex inkrementiert. Daher sind nach dem Aufruf alle Output-Variablen und -Varyings undefiniert. Für das nächste Vertex müssen sie also erneut geschrieben werden. Erzeugt man mehr Vertices als in GL_GEOMETRY_VERTICES_OUT_EXT festgelegt, haben weitere Aufrufe von EmitVertex() keine Wirkung. Der Compiler kann nicht sicher feststellen, ob zuviele Vertices erzeugt werden und gibt daher keine Warnung aus.
  • void EndPrimitive();
Ein Aufruf dieser Funktion beendet das aktuelle Primitiv und beginnt ein neues. Nach Beendigung des Geometryshaders wird diese Funktion automatisch aufgerufen. Die Funktion wird also nur dann benötigt, wenn man mehrere Line- bzw. Triangle-Strips erzeugen möchte. Erzeugt man Points ist der Aufruf optional.


Neue Schlüsselworte

  • varying in
Wird verwendet für Input-Varyings vom Vertexshader. Aus einer varying-Variable im Vertexshader muss im Geometryshader ein Array der Größe gl_VerticesIn werden. Die varying-Variable muss im Vertex- und Geometryshader jeweils den selben Namen haben.
Beispiele:
VertexshaderGeometryshader
varying float beispiel;varying in float beispiel[gl_VerticesIn];
varying vec4 foobar[4];varying in vec4 foobar[gl_VerticesIn][4];
  • varying out
Wird verwendet für Output-Varyings zum Fragmentshader. Diese funktionieren wie vom Vertexshader gewohnt. Für jeden Vertex den man im Geometryshader erzeugt, weißt man der Variable erneut einen Wert zu. Die varying-Variable muss im Geometry- und Fragmentshader jeweils den selben Namen haben.

Neue OpenGL-Funktionen

Neue OpenGL-Tokens

Akzeptiert als Parameter type bei glCreateShader und als Rückgabewert für den Parameter params von glGetShaderiv:

GL_GEOMETRY_SHADER_EXT0x8DD9


Akzeptiert als Parameter pname bei glProgramParameteriEXT und glGetProgramiv:

GL_GEOMETRY_VERTICES_OUT_EXT0x8DDA
GL_GEOMETRY_INPUT_TYPE_EXT0x8DDB
GL_GEOMETRY_OUTPUT_TYPE_EXT0x8DDC


Akzeptiert als Parameter pname bei glGetBooleanv, glGetIntegerv, glGetFloatv und glGetDoublev:

GL_MAX_GEOMETRY_TEXTURE_IMAGE_UNITS_EXT0x8C29
GL_MAX_GEOMETRY_VARYING_COMPONENTS_EXT0x8DDD
GL_MAX_VERTEX_VARYING_COMPONENTS_EXT0x8DDE
GL_MAX_VARYING_COMPONENTS_EXT0x8B4B
GL_MAX_GEOMETRY_UNIFORM_COMPONENTS_EXT0x8DDF
GL_MAX_GEOMETRY_OUTPUT_VERTICES_EXT0x8DE0
GL_MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS_EXT0x8DE1


Akzeptiert als Parameter mode bei glBegin, glDrawArrays, glMultiDrawArrays, glDrawElements, glMultiDrawElements und glDrawRangeElements:

GL_LINES_ADJACENCY_EXT0xA
GL_LINE_STRIP_ADJACENCY_EXT0xB
GL_TRIANGLES_ADJACENCY_EXT0xC
GL_TRIANGLE_STRIP_ADJACENCY_EXT0xD


Als Rückgabewert von glCheckFramebufferStatusEXT:

GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS_EXT0x8DA8
GL_FRAMEBUFFER_INCOMPLETE_LAYER_COUNT_EXT0x8DA9


Akzeptiert als Parameter pname bei glGetFramebufferAttachmentParameterivEXT:

GL_FRAMEBUFFER_ATTACHMENT_LAYERED_EXT0x8DA7
GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER_EXT0x8CD4

Hinweis
GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER_EXT ist ein Alias für GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_3D_ZOFFSET_EXT, welches von von der Extension GL_EXT_framebuffer_object bereitgestellt wird. Diese Extension dehnt die Bedeutung von zoffset aus um auch die Layer einer Array-Textur einzuschließen.


Akzeptiert als Parameter cap bei glEnable, glDisable und glIsEnabled, sowie als Parameter pname bei glGetIntegerv, glGetFloatv, glGetDoublev und glGetBooleanv:

GL_PROGRAM_POINT_SIZE_EXT0x8642

Hinweis
GL_PROGRAM_POINT_SIZE_EXT ist ein Alias für GL_VERTEX_PROGRAM_POINT_SIZE, welches von OpenGL 2.0 definiert wird. Letzteres ist selbst ein Alias für GL_VERTEX_PROGRAM_POINT_SIZE_ARB, welches von der Extension GL_ARB_vertex_program bereitsgestellt wird. "Program-computed point sizes" können aktiviert werden, wenn Geometryshader aktiv sind.


Abhängigkeiten

Es wird OpenGL 1.1 benötigt. Die Extension wurde anhand der OpenGL 2.0 Spezifikation geschrieben.

GL_EXT_geometry_shader4 beeinflusst die folgenden Extensions:

GL_EXT_geometry_shader4 beeinflusst die folgenden Extensions auf triviale Weise:

Die folgenden anderen Extensions interagieren mit GL_EXT_geometry_shader4:


Ressourcen

Quellen

[1] NVIDIA GeForce 8 and 9 Series GPU Programming Guide, insbesondere Abschnitt 4.6
[2] GLSL Geometry Shaders, Mike Bailey, Oregon State University