
Hallo an alle. Jeder, der ein wenig über das OpenGL-Thema weiß, weiß, dass es eine große Anzahl von Artikeln und Kursen zu diesem Thema gibt, aber viele haben keinen Einfluss auf die moderne API, und einige von ihnen sprechen im Allgemeinen über
glBegin und
glEnd . Ich werde versuchen, einige der Nuancen der neuen API ab Version 4 zu behandeln.
Link zum zweiten Teil des ArtikelsDieses Mal werde ich versuchen, einen interessanten und informativen Artikel zu schreiben, und was passiert ist, liegt bei den guten Habra-Bewohnern zu entscheiden. Bitte verzeihen Sie mir meine schlechte Grammatik (ich werde für die Korrekturen dankbar sein).
Wenn es Ihnen gefällt, schreibe ich über die Optimierung von OpenGL und die Reduzierung von DrawCalls.
Fangen wir an!
Was wird in diesem Artikel sein - die Funktionalität des modernen OpenGLWas wird in diesem Artikel nicht sein - moderne Ansätze zum Rendern auf OpenGLInhalt:- Direkter Statuszugriff
- Debuggen
- Separate Shader-Objekte
- Textur-Arrays
- Texturansicht
- Einzelpuffer für Index und Vertex
- Tessellation und Berechnungsschattierung
- Pfadwiedergabe
DSA (Direct State Access)
Direkter Statuszugriff - Direkter Zugriff auf den Status. Ein Tool zum Ändern von OpenGL-Objekten, ohne sie im Kontext einrasten zu müssen. Auf diese Weise können Sie den Status eines Objekts in einem lokalen Kontext ändern, ohne den globalen Status zu beeinflussen, der von allen Teilen der Anwendung gemeinsam genutzt wird. Dadurch wird die API auch etwas objektorientierter, da Funktionen, die den Status von Objekten ändern, klar definiert werden können. Dies sagt uns das
OpenGL-Wiki .
Wie wir wissen, ist OpenGL eine API mit vielen Optionsfeldern -
glActiveTexture ,
glBindTexture usw.
Von hier aus haben wir einige
Probleme :
- Der Selektor und der aktuelle Zustand können eine tiefere Änderung des Zustands bewirken.
- Möglicherweise muss die aktive Einheit gebunden / geändert werden, um einen Filter für Texturen festzulegen
- Das Zustandsmanagement wird problematisch, wodurch die Komplexität der Anwendung zunimmt
- Unbekannter Zustand führt zu zusätzlichen Einstellungen
- Versuche, den Status zu speichern / wiederherzustellen, können problematisch sein
Was hat uns die Khronos-Gruppe angeboten und wie hilft die DSA?
- Fügt Funktionen hinzu, die direkt mit dem Objekt / den Objekten arbeiten
- Legt den Texturfilter für das angegebene Texturobjekt fest, nicht für das aktuelle.
- Bindet eine Textur an eine bestimmte Einheit, nicht an die aktive
- Fügt eine sehr große Anzahl neuer Funktionen hinzu
- Deckt Dinge bis zu OpenGL 1.x ab
- Fügt zusätzliche Funktionen hinzu.
Theoretisch kann ein DSA dazu beitragen, die Anzahl der nicht rendernden und sich ändernden Operationen auf Null zu reduzieren ... Aber das ist nicht korrekt.
Jetzt werde ich kurz auf einige der neuen Funktionen eingehen. Ich werde nicht näher auf die Parameter eingehen, sondern Links im Wiki hinterlassen.
- glCreateTextures ersetzt glGenTextures + glBindTexture (Initialisierung).
Es war:
glGenTextures(1, &name); glBindTexture(GL_TEXTURE_2D, name);
Es wurde:
glCreateTextures(GL_TEXTURE_2D, 1, &name);
- glTextureParameterX entspricht glTexParameterX
glGenTextures(1, &name); glBindTexture(GL_TEXTURE_2D, name); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, width, height); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
Jetzt werden wir es so schreiben:
glCreateTextures(GL_TEXTURE_2D, 1, &name); glTextureParameteri(name, GL_TEXTURE_WRAP_S, GL_CLAMP); glTextureParameteri(name, GL_TEXTURE_WRAP_T, GL_CLAMP); glTextureParameteri(name, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTextureParameteri(name, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTextureStorage2D(name, 1, GL_RGBA8, width, height); glTextureSubImage2D(name, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
- glBindTextureUnit ersetzt glActiveTexture + glBindTexture
So haben wir es gemacht:
glActiveTexture(GL_TEXTURE0 + 3); glBindTexture(GL_TEXTURE_2D, name);
Jetzt:
glBindTextureUnit(3, name);
Die Änderungen betrafen auch
glTextureImage , es wird nicht mehr verwendet und hier ist der Grund:
glTexImage ist ziemlich unsicher, es ist sehr einfach, ungültige Texturen zu erhalten, da die Funktion die Werte beim Aufruf nicht überprüft, der Treiber dies beim Zeichnen tut.
GlTexStorage wurde hinzugefügt,
um es zu
ersetzen .
glTexStorage bietet eine Möglichkeit, Texturen mit Überprüfungen zu erstellen, die während eines Aufrufs durchgeführt werden, wodurch Fehler minimiert werden. Das Textur-Repository wird durch die meisten, wenn nicht alle Probleme gelöst, die durch veränderbare Texturen verursacht werden, obwohl unveränderliche Texturen zuverlässiger sind.
Die Änderungen wirkten sich auch auf den Bildspeicher aus:
Dies sind nicht alle geänderten Funktionen. Als nächstes folgen die Funktionen für die Puffer:
Hier ist eine Liste dessen, was jetzt in der DSA-Unterstützung enthalten ist:
- Vertex-Array-Objekte
- Framebuffer-Objekte
- Objekte programmieren
- Pufferobjekte
- Matrixstapel
- Viele veraltete Dinge
Debuggen
Seit Version 4.3 wurden neue Funktionen zum Debuggen hinzugefügt, die meiner Meinung nach sehr nützlich und praktisch sind. Jetzt ruft OpenGL unseren Rückruf für Fehler und Debug-Meldungen auf, deren Stufe wir anpassen können.

Wir müssen nur zwei Funktionen aufrufen, um zu aktivieren:
glEnable &
glDebugMessageCallback , nirgendwo ist es einfacher.
glEnable(GL_DEBUG_OUTPUT); glDebugMessageCallback(message_callback, nullptr);
Jetzt schreiben wir eine Rückruffunktion, um die Nachricht zu erhalten:
void callback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, GLchar const* message, void const* user_param) { auto source_str = [source]() -> std::string { switch (source) { case GL_DEBUG_SOURCE_API: return "API"; case GL_DEBUG_SOURCE_WINDOW_SYSTEM: return "WINDOW SYSTEM"; case GL_DEBUG_SOURCE_SHADER_COMPILER: return "SHADER COMPILER"; case GL_DEBUG_SOURCE_THIRD_PARTY: return "THIRD PARTY"; case GL_DEBUG_SOURCE_APPLICATION: return "APPLICATION"; case GL_DEBUG_SOURCE_OTHER: return "OTHER"; default: return "UNKNOWN"; } }(); auto type_str = [type]() { switch (type) { case GL_DEBUG_TYPE_ERROR: return "ERROR"; case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: return "DEPRECATED_BEHAVIOR"; case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: return "UNDEFINED_BEHAVIOR"; case GL_DEBUG_TYPE_PORTABILITY: return "PORTABILITY"; case GL_DEBUG_TYPE_PERFORMANCE: return "PERFORMANCE"; case GL_DEBUG_TYPE_MARKER: return "MARKER"; case GL_DEBUG_TYPE_OTHER: return "OTHER"; default: return "UNKNOWN"; } }(); auto severity_str = [severity]() { switch (severity) { case GL_DEBUG_SEVERITY_NOTIFICATION: return "NOTIFICATION"; case GL_DEBUG_SEVERITY_LOW: return "LOW"; case GL_DEBUG_SEVERITY_MEDIUM: return "MEDIUM"; case GL_DEBUG_SEVERITY_HIGH: return "HIGH"; default: return "UNKNOWN"; } }(); std::cout << source_str << ", " << type_str << ", " << severity_str << ", " << id << ": " << message << std::endl; }
Wir können den Filter auch mit
glDebugMessageControl konfigurieren . Der Filter kann im Filtermodus nach Quelle / Typ / Wichtigkeit oder einer Reihe von Nachrichten unter Verwendung ihrer Bezeichner arbeiten.
Filtern Sie Nachrichten in einem bestimmten Bereich:
glPushDebugGroup( GL_DEBUG_SOURCE_APPLICATION, DEPTH_FILL_ID, 11, “Depth Fill”);
Es ist sehr nützlich, den asynchronen Aufruf zu deaktivieren, damit wir die Reihenfolge der Funktionsaufrufe bestimmen und beim Debuggen die Stelle des Fehlers auf dem Stapel finden können. Dies geschieht ganz einfach:
glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
Es ist zu beachten, dass es nicht sicher ist, OpenGL- oder Fensterfunktionen in einer Wrapped-Call-Funktion aufzurufen. Rückrufe sind nicht kostenlos und sollten nicht im Release-Build enthalten bleiben.
Weitere Details zu diesen und anderen kleinen Dingen - Gimmicks, können Sie
hier lesen.
SSO (Separate Shader-Objekte)
Nachdem OpenGL als „feste Pipeline“ gearbeitet hatte, bedeutete dies, dass die vorprogrammierte Verarbeitung auf alle zur Visualisierung übertragenen Daten angewendet wurde. Der nächste Schritt war die „programmierbare Pipeline“ - wo der programmierbare Teil in GLSL geschriebene Shader implementiert, bestand das klassische GLSL-Programm aus Vertex- und Fragment-Shadern, aber in modernem OpenGL wurden einige neue Arten von Shadern hinzugefügt, nämlich die Shader für Geometrie, Gewichtung und Berechnungen (über sie I. Ich werde im nächsten Teil erzählen).
Mit SSOs können wir Shader-Schritte im
laufenden Betrieb ändern, ohne sie erneut zu verknüpfen. Das Erstellen und Einrichten einer einfachen Software-Pipeline ohne Debugging lautet wie folgt:
GLuint pipe = GL_NONE;
Wie wir sehen, generiert
glCreateProgramPipelines einen Deskriptor und initialisiert das Objekt.
GlCreateShaderProgramv generiert, initialisiert, kompiliert und verknüpft das Shader-Programm unter Verwendung der angegebenen Quellen und
glUseProgramStages hängt die Programmschritte an das Pipeline-Objekt an.
glBindProgramPipeline -
Ordnet eine Pipeline einem Kontext zu.
Es gibt jedoch eine Einschränkung: Jetzt müssen die Eingabe- und Ausgabeparameter der Shader übereinstimmen. Wir können die Eingabe- / Ausgabeparameter in derselben Reihenfolge und mit denselben Namen deklarieren oder mithilfe von Qualifizierern festlegen, dass ihre Position eindeutig übereinstimmt.
Ich empfehle die letztere Option, damit wir eine klar definierte Schnittstelle konfigurieren und hinsichtlich Namen und Reihenfolge flexibel sein können.
Um eine strengere Schnittstelle bereitzustellen, müssen wir auch die integrierten Eingabe- und Ausgabeblöcke deklarieren, die wir für jede Stufe verwenden möchten.
Die integrierten Blockschnittstellen sind definiert als (
aus einem Wiki ):
Scheitelpunkt:
out gl_PerVertex { vec4 gl_Position; float gl_PointSize; float gl_ClipDistance[]; };
Tesselationskontrolle:
out gl_PerVertex { vec4 gl_Position; float gl_PointSize; float gl_ClipDistance[]; } gl_out[];
Tesselationsbewertung:
out gl_PerVertex { vec4 gl_Position; float gl_PointSize; float gl_ClipDistance[]; };
Geometrie:
out gl_PerVertex { vec4 gl_Position; float gl_PointSize; float gl_ClipDistance[]; };
Ein Beispiel für die erneute Deklaration eines integrierten Moduls und die Verwendung der Attributposition in einem regulären Vertex-Shader:
#version 450 out gl_PerVertex { vec4 gl_Position; }; layout (location = 0) in vec3 position; layout (location = 1) in vec3 color; layout (location = 0) out v_out { vec3 color; } v_out; void main() { v_out.color = color; gl_Position = vec4(position, 1.0); }