I. Introduction▲
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 .
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 :
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 :
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 :
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 :
#
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.
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 :
-
Trouver l'indice du bloc de stockage :
SélectionnezGLunit block_index
=
0
; block_index=
glGetProgramResourceIndex(program, GL_SHADER_STORAGE_BLOCK,"shader_data"
); - 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 :
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 :
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 :
glShaderStorageBlockBinding(program, block_index, 80
);
Comme pour les UBO, l'association d'un SSBO à un point de liaison particulier se fait avec :
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() :
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
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.
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.
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.