web-dev-qa-db-fra.com

Dois-je jamais utiliser un `vec3` à l'intérieur d'un tampon uniforme ou d'un objet tampon de stockage de shader?

Le vec3 type est un type très agréable. Il ne prend que 3 flotteurs, et j'ai des données qui n'ont besoin que de 3 flotteurs. Et je veux en utiliser un dans une structure dans un UBO et/ou SSBO:

layout(std140) uniform UBO
{
  vec4 data1;
  vec3 data2;
  float data3;
};

layout(std430) buffer SSBO
{
  vec4 data1;
  vec3 data2;
  float data3;
};

Ensuite, dans mon code C ou C++, je peux le faire pour créer des structures de données correspondantes:

struct UBO
{
  vector4 data1;
  vector3 data2;
  float data3;
};

struct SSBO
{
  vector4 data1;
  vector3 data2;
  float data3;
};

Est-ce une bonne idée?

30
Nicol Bolas

NON! Ne faites jamais ça!

Lorsque vous déclarez des UBO/SSBO, faites comme si tous les vecteurs et matrices à 3 éléments n'existaient pas . Imaginez que les seuls types sont des scalaires, des vecteurs à 2 et 4 éléments (et des matrices). Vous vous épargnerez beaucoup de chagrin si vous le faites.

Si vous voulez l'effet d'un vec3 + un flotteur, alors vous devez le compresser manuellement :

layout(std140) uniform UBO
{
  vec4 data1;
  vec4 data2and3;
};

Oui, vous devrez utiliser data2and3.w Pour obtenir l'autre valeur. Faites avec.

Si vous voulez des tableaux de vec3 S, faites-en des tableaux de vec4 S. Il en va de même pour les matrices qui utilisent des vecteurs à 3 éléments. Bannissez simplement le concept entier de vecteurs à 3 éléments de vos SSBO/UBO; vous serez beaucoup mieux à long terme.

Il y a deux raisons pour lesquelles vous devriez éviter vec3:

Il ne fera pas ce que fait C/C++

Si vous utilisez la disposition std140, Vous souhaiterez probablement définir des structures de données en C ou C++ qui correspondent à la définition de GLSL. Cela facilite le mix & match entre les deux. Et la mise en page std140 Permet au moins de le faire dans la plupart des cas. Mais ses règles de mise en page ne correspondent pas aux règles de mise en page habituelles pour les compilateurs C et C++ en ce qui concerne vec3 S.

Tenez compte des définitions C++ suivantes pour un type vec3:

struct vec3a { float a[3]; };
struct vec3f { float x, y, z; };

Ces deux types sont parfaitement légitimes. Le sizeof et la disposition de ces types correspondront à la taille et à la disposition requises par std140. Mais il ne correspond pas au comportement d'alignement imposé par std140.

Considère ceci:

//GLSL
layout(std140) uniform Block
{
    vec3 a;
    vec3 b;
} block;

//C++
struct Block_a
{
    vec3a a;
    vec3a b;
};

struct Block_f
{
    vec3f a;
    vec3f b;
};

Sur la plupart des compilateurs C++, sizeof pour Block_a Et Block_f Sera égal à 24. Ce qui signifie que offsetofb sera égal à 12.

Cependant, dans la présentation std140, vec3 Est toujours aligné sur 4 mots. Par conséquent, Block.b Aura un décalage de 16.

Maintenant, vous pouvez essayer de résoudre ce problème en utilisant la fonctionnalité alignas de C++ 11 (ou la fonctionnalité similaire _Alignas De C11):

struct alignas(16) vec3a_16 { float a[3]; };
struct alignas(16) vec3f_16 { float x, y, z; };

struct Block_a
{
    vec3a_16 a;
    vec3a_16 b;
};

struct Block_f
{
    vec3f_16 a;
    vec3f_16 b;
};

Si le compilateur prend en charge l'alignement sur 16 octets, cela fonctionnera. Ou du moins, cela fonctionnera dans le cas de Block_a Et Block_f.

Mais cela ne fonctionnera pas dans ce cas:

//GLSL
layout(std140) Block2
{
    vec3 a;
    float b;
} block2;

//C++
struct Block2_a
{
    vec3a_16 a;
    float b;
};

struct Block2_f
{
    vec3f_16 a;
    float b;
};

Selon les règles de std140, Chaque vec3 Doit commencer sur une limite de 16 octets. Mais vec3 Ne consomme pas 16 octets de stockage; il n'en consomme que 12. Et comme float peut démarrer sur une limite de 4 octets, un vec3 suivi d'un float prendra 16 octets.

Mais les règles d'alignement C++ ne permettent pas une telle chose. Si un type est aligné sur une limite de X octets, l'utilisation de ce type consommera un multiple de X octets.

Par conséquent, pour correspondre à la disposition de std140, Vous devez choisir un type en fonction de l'endroit exact où il est utilisé. S'il est suivi d'un float, vous devez utiliser vec3a; s'il est suivi par un type aligné sur plus de 4 octets, vous devez utiliser vec3a_16.

Ou vous ne pouvez tout simplement pas utiliser vec3 S dans vos shaders et éviter toute cette complexité supplémentaire.

Notez qu'une alignas(8) basée sur vec2 N'aura pas ce problème. Les structures et tableaux C/C++ n'utilisent pas non plus le spécificateur d'alignement approprié (bien que les tableaux de types plus petits aient leurs propres problèmes). Ce problème uniquement se produit lors de l'utilisation d'un vec3 Nu.

Le support d'implémentation est flou

Même si vous faites tout correctement, les implémentations sont connues pour implémenter incorrectement les règles de mise en page bizarres de vec3. Certaines implémentations imposent effectivement des règles d'alignement C++ à GLSL. Donc, si vous utilisez un vec3, Il le traite comme C++ traiterait un type aligné sur 16 octets. Sur ces implémentations, un vec3 Suivi d'un float fonctionnera comme un vec4 Suivi d'un float.

Oui, c'est la faute des implémenteurs. Mais comme vous ne pouvez pas corriger l'implémentation, vous devez contourner cela. Et la façon la plus raisonnable de le faire est d'éviter tout simplement vec3.

Notez que, pour Vulkan (et OpenGL utilisant SPIR-V), le compilateur GLSL du SDK obtient ce droit, vous n'avez donc pas besoin de vous en inquiéter pour cela.

37
Nicol Bolas