¿Debería alguna vez usar un `vec3` dentro de un búfer uniforme o un objeto de búfer de almacenamiento de sombreador?
El vec3
tipo es un tipo muy agradable. Solo requiere 3 flotadores y tengo datos que solo necesitan 3 flotadores. Y quiero usar uno en una estructura en una UBO y/o SSBO:
layout(std140) uniform UBO
{
vec4 data1;
vec3 data2;
float data3;
};
layout(std430) buffer SSBO
{
vec4 data1;
vec3 data2;
float data3;
};
Luego, en mi código C o C++, puedo hacer esto para crear estructuras de datos coincidentes:
struct UBO
{
vector4 data1;
vector3 data2;
float data3;
};
struct SSBO
{
vector4 data1;
vector3 data2;
float data3;
};
¿Es esta una buena idea?
¡NO! ¡Nunca hagas esto!
Al declarar UBO/SSBO, pretenda que no existen todos los tipos de vectores de 3 elementos . Esto incluye matrices de columnas principales con 3 filas o matrices de filas principales con 3 columnas. Suponga que los únicos tipos son escalares, vectores (y matrices) de 2 y 4 elementos. Si lo hace, se ahorrará muchos dolores de cabeza.
Si desea el efecto de un vec3 + un flotador, entonces debe empaquetarlo manualmente :
layout(std140) uniform UBO
{
vec4 data1;
vec4 data2and3;
};
Sí, tendrás que usarlo data2and3.w
para obtener el otro valor. Tratar con él.
Si desea matrices de vec3
s, conviértalas en matrices de vec4
s. Lo mismo ocurre con las matrices que utilizan vectores de 3 elementos. Simplemente elimine todo el concepto de vectores de 3 elementos de sus SSBO/UBO; estarás mucho mejor a largo plazo.
Hay dos razones por las que debes evitar vec3
:
No hará lo que hace C/C++
Si utiliza std140
el diseño, probablemente querrá definir estructuras de datos en C o C++ que coincidan con la definición en GLSL. Eso hace que sea fácil mezclar y combinar entre los dos. Y std140
el diseño al menos permite hacer esto en la mayoría de los casos. Pero sus reglas de diseño no coinciden con las reglas de diseño habituales para los compiladores de C y C++ cuando se trata de vec3
s.
Considere las siguientes definiciones de C++ para un vec3
tipo:
struct vec3a { float a[3]; };
struct vec3f { float x, y, z; };
Ambos son tipos perfectamente legítimos. El sizeof
diseño y diseño de estos tipos coincidirá con el tamaño y diseño que std140
se requiera. Pero no coincide con el comportamiento de alineación que std140
impone.
Considera esto:
//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;
};
En la mayoría de los compiladores de C++, sizeof
para ambos Block_a
y Block_f
serán 24. Lo que significa que offsetof
b
serán 12.
Sin embargo, en el diseño std140, vec3
siempre está alineado con 4 palabras. Y por tanto, Block.b
tendrá un offset de 16.
Ahora, podrías intentar solucionarlo utilizando alignas
la funcionalidad de C++11 (o _Alignas
una característica similar 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 el compilador admite una alineación de 16 bytes, esto funcionará. O al menos funcionará en el caso de Block_a
y Block_f
.
Pero no funcionará en este caso:
//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;
};
Según las reglas de std140
, cada uno vec3
debe comenzar en un límite de 16 bytes. Pero vec3
no consume 16 bytes de almacenamiento; solo consume 12. Y como float
puede comenzar en un límite de 4 bytes, a vec3
seguido de a float
ocupará 16 bytes.
Pero las reglas de alineación de C++ no permiten tal cosa. Si un tipo está alineado con un límite de X bytes, el uso de ese tipo consumirá un múltiplo de X bytes.
Por lo tanto, el diseño de coincidencia std140
requiere que elija un tipo basado exactamente en dónde se usa. Si va seguido de float
, debes usar vec3a
; Si va seguido de algún tipo que tenga más de 4 bytes alineados, deberá utilizar vec3a_16
.
O simplemente no puedes usar vec3
s en tus sombreadores y evitar toda esta complejidad adicional.
Tenga en cuenta que un alignas(8)
basado en vec2
no tendrá este problema. Las estructuras y matrices C/C++ tampoco utilizarán el especificador de alineación adecuado (aunque las matrices de tipos más pequeños tienen sus propios problemas). Este problema sólo ocurre cuando se usa un modelo desnudo vec3
.
El apoyo a la implementación es confuso
Incluso si hace todo bien, se sabe que las implementaciones implementan incorrectamente vec3
las extrañas reglas de diseño de. Algunas implementaciones imponen efectivamente reglas de alineación de C++ a GLSL. Entonces, si usa a vec3
, lo trata como C++ trataría un tipo alineado de 16 bytes. En estas implementaciones, a vec3
seguido de a float
funcionará como vec4
seguido de a float
.
Sí, es culpa de los implementadores. Pero como no se puede arreglar la implementación, hay que solucionarlo. Y la forma más razonable de hacerlo es evitarlo vec3
por completo.
Tenga en cuenta que, para Vulkan (y OpenGL que usa SPIR-V), el compilador GLSL del SDK hace esto bien, por lo que no necesita preocuparse por eso.