Tampons GPU : introduction aux « Shader Storage Buffers Objects » d'OpenGL 4.3

Les Shader Storage Buffers Objects sont des zones en mémoire vidéo accessibles que ce soit en lecture ou en écriture. Découvrez dans ce tutoriel comment les utiliser.

Commentez Donner une note à l'article (5)

Article lu   fois.

Les deux auteur et traducteur

Traducteur : Profil ProSite personnel

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

I. Introduction

Image non disponible

Dans ce tutoriel, nous avions découvert les Uniform Buffer Objects (UBO). Pour résumer, les UBO sont des zones mémoire accessibles par le GPU en lecture seule à partir d'un shader GLSL. La taille d'un UBO est quelque peu limitée : 64 ko pour les GPU AMD et NVIDIA et 16 ko du côté d'Intel.

Une taille limitée, un accès en lecture seule, hum… Avec toutes ces cartes graphiques modernes et leurs tonnes de gigaoctets de mémoire vidéo, nous pouvons faire mieux que 64 ko pour un tampon GPU.

Les Shader Storage Buffers Objects (SSBO) peuvent être vus comme des UBO débridés. Ils sont accessibles en lecture ET écriture dans le shader GLSL et leur taille semble être limitée par la quantité de mémoire GPU disponible. Sur une GeForce GTX 660, il est possible d'allouer 2 Go de VRAM à un SSBO. Super !

Le seul inconvénient des SSBO est… Mac OS X. Mavericks, la dernière version des OS X, supporte seulement OpenGL 4.1. Il n'y aura donc pas de SSBO sous OS X avant la prochaine décennie Image non disponible.

Sur Windows et Linux (avec les pilotes NVIDIA et AMD dont les codes source sont fermés), les SSBO sont disponibles pour tous les GPU compatibles avec OpenGL 4.

La bible des SSBO peut être trouvée ici : GL_ARB_shader_storage_buffer_object.

II. Code OpenGL

La gestion des SSBO est très proche de celle des UBO.

Reprenons notre structure de données C/C++ utilisée dans l'article sur les UBO :

 
Sélectionnez
struct shader_data_t 
{ 
  float camera_position[4]; 
  float light_position[4]; 
  float light_diffuse[4]; 
} shader_data;

La création et l'initialisation d'un SSBO :

 
Sélectionnez
GLuint ssbo = 0; 
glGenBuffers(1, &ssbo); 
glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo); 
glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(shader_data), &shader_data, GL_DYNAMIC_COPY); 
glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);

La mise à jour d'un SSBO ; nous obtenons un pointeur sur la mémoire GPU et nous copions nos données :

 
Sélectionnez
glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo); 
GLvoid* p = glMapBuffer(GL_SHADER_STORAGE_BUFFER, GL_WRITE_ONLY); 
memcpy(p, &shader_data, sizeof(shader_data)) 
glUnmapBuffer(GL_SHADER_STORAGE_BUFFER);

Comme pour les UBO, il existe un équivalent au bloc de variables uniformes dans les shaders GLSL. Pour les SSBO, nous avons un bloc de stockage dans le shader. Ce bloc décrit la structure des données à partir de laquelle le shader peut lire ou écrire :

 
Sélectionnez
#version 430 
... 
layout (std430, binding=2) buffer shader_data 
{ 
  vec4 camera_position; 
  vec4 light_position; 
  vec4 light_diffuse; 
}; 
... 
void main() 
{ 
  ... 
}

Le mot-clé uniform du bloc de variables uniformes est remplacé par le mot-clé buffer qui montre la capacité de lecture et d'écriture du tampon.

Comme avec les UBO, OpenGL maintient une table de points de liaison dans chaque contexte de rendu. Cette table stocke une sorte de référence sur chaque SSBO. Pour une GeForce GTX 660, cette table présente 96 entrées.

Image non disponible

Avec une GeForce GTX 660, chaque type de shader (vertex, fragment, geometry, tesselation et compute) peut contenir jusqu'à 16 blocs de stockage.

Pour être capable de lire ou écrire dans un SSBO, les étapes suivantes sont nécessaires :

  1. Trouver l'indice du bloc de stockage :

     
    Sélectionnez
    GLunit block_index = 0;
    block_index = glGetProgramResourceIndex(program, GL_SHADER_STORAGE_BLOCK, "shader_data");
    
  2. Connecter le bloc de stockage du shader au SSBO : nous indiquons au shader sur quel point de liaison il trouvera le SSBO. Dans notre cas, le SSBO peut être trouvé sur le point numéro 2 :
 
Sélectionnez
GLuint ssbo_binding_point_index = 2; 
glShaderStorageBlockBinding(program, block_index, ssbo_binding_point_index);

Cette dernière étape n'est pas nécessaire. Le point de liaison peut être encodé en dur dans le shader GLSL, dans la disposition du tampon :

 
Sélectionnez
layout (std430, binding=2) buffer shader_data 
{ 
  ... 
}

La fonction glShaderStorageBlockBinding() permet de connecter dynamiquement le bloc de stockage du shader au SSBO. La ligne de code suivante permet de connecter le bloc de stockage au point de liaison 80 du SSBO :

 
Sélectionnez
glShaderStorageBlockBinding(program, block_index, 80);

Comme pour les UBO, l'association d'un SSBO à un point de liaison particulier se fait avec :

 
Sélectionnez
GLuint binding_point_index = 80; 
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, binding_point_index, ssbo);

Pour conclure cet article, voici quelques limites que nous pouvons récupérer avec glGetIntegerv() :

 
Sélectionnez
Limites d'une NVIDIA GeForce GTX 660 (R337.50) : 
GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS = 96 
GL_MAX_SHADER_STORAGE_BLOCK_SIZE = 2147483647 
GL_MAX_VERTEX_SHADER_STORAGE_BLOCKS = 16 
GL_MAX_FRAGMENT_SHADER_STORAGE_BLOCKS = 16 
GL_MAX_GEOMETRY_SHADER_STORAGE_BLOCKS = 16 
GL_MAX_TESS_CONTROL_SHADER_STORAGE_BLOCKS = 16 
GL_MAX_TESS_EVALUATION_SHADER_STORAGE_BLOCKS = 16 
GL_MAX_COMPUTE_SHADER_STORAGE_BLOCKS = 16 
GL_MAX_COMBINED_SHADER_STORAGE_BLOCKS = 96
 
Sélectionnez
Limites d'une AMD Radeon HD 7970 (Catalyst 14.4) : 
GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS = 16 
GL_MAX_SHADER_STORAGE_BLOCK_SIZE = 16777216 
GL_MAX_VERTEX_SHADER_STORAGE_BLOCKS = 16 
GL_MAX_FRAGMENT_SHADER_STORAGE_BLOCKS = 16 
GL_MAX_GEOMETRY_SHADER_STORAGE_BLOCKS = 16 
GL_MAX_TESS_CONTROL_SHADER_STORAGE_BLOCKS = 16 
GL_MAX_TESS_EVALUATION_SHADER_STORAGE_BLOCKS = 16 
GL_MAX_COMPUTE_SHADER_STORAGE_BLOCKS = 16 
GL_MAX_COMBINED_SHADER_STORAGE_BLOCKS = 16

J'espère que je n'ai pas fait trop d'erreurs. J'ai rapidement exploré les SSBO, je vais essayer d'écrire un autre article sur ces étranges qualificateurs de disposition que nous venons de voir et que nous avions vus dans l'article sur les UBO : std140 et std430. Ils sont vraiment très importants si vous souhaitez mettre à jour certaines parties du tampon d'objets et non son intégralité.

III. Démonstrations

J'ai programmé deux démonstrations avec GLSL Hacker qui utilisent les SSBO. La première utilise les SSBO pour passer les matrices de la caméra au shader. Cette démonstration est disponible dans le dossier host_api/gl-430-arb-shader-storage-buffer-object/ du pack de démonstration.

Image non disponible

La deuxième démonstration montre un exemple du mode lecture/écriture des SSBO avec des particules. Un compute shader (fonctionnalité d'OpenGL 4.3) lit la position d'une particule à partir du SSBO, la met à jour et écrit sa nouvelle position dans le même SSBO. Ce dernier est ensuite utilisé comme donnée de sommet pour l'affichage des particules. La démonstration est disponible dans le dossier host_api/gl-430-arb-compute-shader_Particles_SSBO/ du pack de démonstration.

Ces deux démonstrations utilisent la nouvelle fonctionnalité de GLSL Hacker 0.7.0+ : gh_gpu_buffer, une nouvelle bibliothèque Lua/Python. Cette bibliothèque bas niveau permet de gérer tous les types de tampons GPU incluant les tampons de variables uniformes et ceux de stockage.

Image non disponible

IV. Remerciements

Cet article est une traduction autorisée de l'article paru sur Geeks3D.com.

Merci à Winjerome pour sa relecture attentive et milkoseck pour sa relecture orthographique.

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2014 JeGX. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.