OpenGL ultramoderne. Partie 1



Bonjour à tous. Tous ceux qui connaissent un peu le sujet OpenGL savent qu'il existe un grand nombre d'articles et de cours sur ce sujet, mais beaucoup n'affectent pas l'API moderne, et certains d'entre eux parlent généralement de glBegin et glEnd . J'essaierai de couvrir certaines des nuances de la nouvelle API à partir de la version 4. Lien vers la deuxième partie de l'article

Cette fois, j'essaierai d'écrire un article intéressant et informatif, et ce qui s'est passé, c'est aux bons habitants d'habra d'en décider. Veuillez me pardonner ma pauvre grammaire (je serai reconnaissant pour les corrections).

Si vous l'aimez, j'écrirai sur l'optimisation d'OpenGL et la réduction de DrawCalls.

Commençons!

Ce qui sera dans cet article - la fonctionnalité d'OpenGL moderne
Ce qui ne sera pas dans cet article - approches modernes du rendu sur OpenGL

Contenu:
  • Accès direct à l'État
  • Déboguer
  • Objets Shader séparés
  • Tableaux de texture
  • Vue de texture
  • Tampon unique pour l'index et le sommet
  • Pavage et ombrage de calcul
  • Rendu de chemin


DSA (Direct State Access)


Accès direct à l'État - Accès direct à l'État. Un outil pour modifier les objets OpenGL sans avoir à les accrocher au contexte. Cela vous permet de modifier l'état d'un objet dans un contexte local sans affecter l'état global partagé par toutes les parties de l'application. Cela rend également l'API un peu plus orientée objet, car les fonctions qui changent l'état des objets peuvent être clairement définies. C'est ce que le wiki OpenGL nous dit.

Comme nous le savons, OpenGL est une API avec de nombreux boutons radio - glActiveTexture , glBindTexture , etc.

De là, nous avons quelques problèmes :

  • Le sélecteur et les états actuels peuvent effectuer un changement d'état plus profond.
  • Il peut être nécessaire de lier / changer l'unité active pour définir un filtre pour les textures
  • La gestion des états devient problématique à la suite de laquelle la complexité de l'application augmente
  • Un état inconnu conduit à des paramètres supplémentaires
  • Les tentatives de sauvegarde / restauration de l'état peuvent être problématiques

Qu'est-ce que le groupe Khronos nous a offert et comment l'aide DSA?

  • Ajoute des fonctions qui fonctionnent directement avec l'objet / les objets
  • Définit le filtre de texture pour l'objet de texture spécifié, pas celui en cours.
  • Lie une texture à une unité spécifique, pas à l'actif
  • Ajoute un très grand nombre de nouvelles fonctionnalités
  • Couvre les choses jusqu'à OpenGL 1.x
  • Ajoute des fonctionnalités supplémentaires.

En théorie, un DSA peut aider à réduire à zéro le nombre d'opérations de non-rendu et de changement d'état ... Mais ce n'est pas exact.

Maintenant, je vais brièvement passer en revue certaines des nouvelles fonctionnalités, je ne m'attarderai pas sur les paramètres en détail, je laisserai des liens sur le wiki.

  • glCreateTextures remplace glGenTextures + glBindTexture (initialisation).
    C'était:
    glGenTextures(1, &name); glBindTexture(GL_TEXTURE_2D, name); 
    C'est devenu:
     glCreateTextures(GL_TEXTURE_2D, 1, &name); 
  • glTextureParameterX équivalent à 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); 
    Maintenant, nous allons l'écrire comme ceci:
     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 remplacer glActiveTexture + glBindTexture
    Voici comment nous l'avons fait:
     glActiveTexture(GL_TEXTURE0 + 3); glBindTexture(GL_TEXTURE_2D, name); 
    Maintenant:
     glBindTextureUnit(3, name); 

Les changements ont également affecté glTextureImage , il n'est plus utilisé, et voici pourquoi:

glTexImage est plutôt dangereux, il est très facile d'obtenir des textures invalides, car la fonction ne vérifie pas les valeurs lorsqu'elle est appelée, le pilote le fait pendant le dessin. GlTexStorage a été ajouté pour le remplacer .

glTexStorage fournit un moyen de créer des textures avec des vérifications effectuées pendant un appel, ce qui minimise les erreurs. Le référentiel de textures est résolu par la plupart, sinon la totalité, des problèmes causés par les textures mutables, bien que les textures immuables soient plus fiables.

Les modifications ont également affecté le tampon de trame:


Ce ne sont pas toutes les fonctionnalités modifiées. Les lignes suivantes sont les fonctions des tampons:


Voici une liste de ce qui est désormais inclus dans le support DSA:

  • Objets de tableau de sommets
  • Objets Framebuffer
  • Objets de programme
  • Objets tampons
  • Piles matricielles
  • Beaucoup de choses obsolètes

Déboguer


Depuis la version 4.3, de nouvelles fonctionnalités ont été ajoutées pour le débogage, à mon avis très utiles et pratiques. Maintenant OpenGL appellera notre rappel pour les erreurs et les messages de débogage, dont nous pouvons ajuster le niveau.



Nous devons appeler seulement deux fonctions pour activer: glEnable et glDebugMessageCallback , nulle part plus facile.

 glEnable(GL_DEBUG_OUTPUT); glDebugMessageCallback(message_callback, nullptr); 

Nous allons maintenant écrire une fonction de rappel pour obtenir le message:

 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; } 

Nous pouvons également configurer le filtre à l'aide de glDebugMessageControl . Le filtre peut fonctionner en mode de filtrage par source / type / importance ou un ensemble de messages en utilisant leurs identifiants.

Filtrer les messages dans une portée spécifique:

 glPushDebugGroup( GL_DEBUG_SOURCE_APPLICATION, DEPTH_FILL_ID, 11, “Depth Fill”); //  Render_Depth_Only_Pass(); //  glPopDebugGroup(); //  

Il sera très utile de désactiver l'appel asynchrone afin de pouvoir déterminer l'ordre des appels de fonction, ainsi que de trouver la place de l'erreur sur la pile lors du débogage. Cela se fait tout simplement:

 glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); 

Il convient de rappeler qu'il n'est pas sûr d'appeler OpenGL ou des fonctions de fenêtre dans une fonction d'appel encapsulée, ainsi que les rappels ne sont pas gratuits et vous ne devez pas les laisser inclus dans la version.
Plus de détails sur ces petites choses et d'autres - des gadgets, vous pouvez lire ici .

SSO (objets de shader séparés)


Une fois OpenGL fonctionnait comme un «pipeline fixe» - cela signifiait qu'un traitement préprogrammé était appliqué à toutes les données transférées pour la visualisation. L'étape suivante était un «pipeline programmable» - où la partie programmable implémente des shaders, écrits en GLSL, le programme GLSL classique consistait en vertex et fragment shaders, mais dans OpenGL moderne de nouveaux types de shaders ont été ajoutés, à savoir les shaders de géométrie, de pondération et de calculs (à leur sujet, je Je le dirai dans la partie suivante).


Les SSO nous permettent de modifier les étapes du shader à la volée sans les reconnecter . La création et la configuration d'un pipeline logiciel simple sans débogage sont les suivantes:

 GLuint pipe = GL_NONE; // Create shaders GLuint fprog = glCreateShaderProgramv( GL_FRAGMENT_SHADER, 1, &text); GLuint vprog = glCreateShaderProgramv( GL_VERTEX_SHADER, 1, &text); // Bind pipeline glGenProgramPipelines( 1, &pipe); glBindProgramPipelines( pipe); // Bind shaders glUseProgramStages( pipe, GL_FRAGMENT_SHADER_BIT, fprog); glUseProgramStages( pipe, GL_VERTEX_SHADER_BIT, vprog); 

Comme nous le voyons, glCreateProgramPipelines génère un descripteur et initialise l'objet, glCreateShaderProgramv génère, initialise, compile et lie le programme de shader en utilisant les sources spécifiées, et glUseProgramStages attache les étapes du programme à l'objet pipeline. glBindProgramPipeline - Associe un pipeline à un contexte.

Mais il y a une mise en garde, maintenant les paramètres d'entrée et de sortie des shaders doivent correspondre. Nous pouvons déclarer les paramètres d'entrée / sortie dans le même ordre, avec les mêmes noms, ou nous faisons clairement coïncider leur emplacement à l'aide de qualificatifs.
Je recommande cette dernière option, cela nous permettra de configurer une interface clairement définie, ainsi que d'être flexible en ce qui concerne les noms et l'ordre.

Pour fournir une interface plus rigoureuse, nous devons également déclarer les blocs d'entrée et de sortie intégrés que nous voulons utiliser pour chaque étape.

Les interfaces de blocs intégrées sont définies comme (à partir d'un wiki ):
Sommet:

 out gl_PerVertex { vec4 gl_Position; float gl_PointSize; float gl_ClipDistance[]; }; 

Contrôle de la tesselation:
 out gl_PerVertex { vec4 gl_Position; float gl_PointSize; float gl_ClipDistance[]; } gl_out[]; 

Évaluation de la tesselation:
 out gl_PerVertex { vec4 gl_Position; float gl_PointSize; float gl_ClipDistance[]; }; 

Géométrie:
 out gl_PerVertex { vec4 gl_Position; float gl_PointSize; float gl_ClipDistance[]; }; 

Un exemple de re-déclaration d'un module intégré et d'utilisation de l'emplacement d'attribut dans un vertex shader régulier:

 #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); } 

Source: https://habr.com/ru/post/fr456932/


All Articles