opengl - ¿Debería usar un ''vec3'' dentro de un búfer uniforme o un objeto de búfer de almacenamiento de sombreador?
opengl-es glsl (1)
El tipo
vec3
es un tipo muy agradable.
Solo ocupa 3 flotantes, y tengo datos que solo necesitan 3 flotantes.
Y quiero usar uno en una estructura en un 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 todos los tipos de vectores y matrices de 3 elementos no existen . Imagine que los únicos tipos son escalares, vectores de 2 y 4 elementos (y matrices). Si lo hace, se ahorrará mucho dolor.
Si desea el efecto de un vec3 + un flotador, debe empacarlo manualmente :
layout(std140) uniform UBO
{
vec4 data1;
vec4 data2and3;
};
Sí, tendrá que usar
data2and3.w
para obtener el otro valor.
Tratar con él.
Si quieres matrices de
vec3
s, entonces
vec4
matrices de
vec4
s.
Lo mismo ocurre con las matrices que usan vectores de 3 elementos.
Simplemente elimine todo el concepto de vectores de 3 elementos de sus SSBO / UBO;
estarás mucho mejor a la larga.
Hay dos razones por las que debes evitar
vec3
:
No hará lo que hace C / C ++
Si utiliza el diseño
std140
, probablemente desee 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 el diseño
std140
hace que al menos sea posible hacerlo 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 C y C ++ cuando se trata de
vec3
s.
Considere las siguientes definiciones de C ++ para un tipo
vec3
:
struct vec3a { float a[3]; };
struct vec3f { float x, y, z; };
Ambos son tipos perfectamente legítimos.
El tamaño y el diseño de estos tipos coincidirán con el tamaño y el diseño que requiere
std140
.
Pero no coincide con el comportamiento de alineación que impone
std140
.
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
Block_a
y
Block_f
será 24. Lo que significa que el
offsetof
b
será 12.
Sin embargo, en el diseño std140,
vec3
siempre está alineado con 4 palabras.
Y por lo tanto,
Block.b
tendrá un desplazamiento de 16.
Ahora, podría intentar solucionarlo utilizando la funcionalidad de
alignas
C ++ 11 (o la característica
_Alignas
similar de
_Alignas
):
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 la 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
vec3
debe
comenzar
en un límite de 16 bytes.
Pero
vec3
no
consume
16 bytes de almacenamiento;
solo consume 12. Y dado que
float
puede comenzar en un límite de 4 bytes, un
vec3
seguido de un
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 bytes X, el uso de ese tipo consumirá un múltiplo de X bytes.
Por lo tanto, hacer
std140
el diseño de
std140
requiere que elija un tipo basado exactamente en dónde se usa.
Si es seguido por un
float
, debe usar
vec3a
;
si es seguido por algún tipo que esté alineado más de 4 bytes, debe usar
vec3a_16
.
O simplemente no puede usar
vec3
s en sus sombreadores y evitar toda esta complejidad añadida.
Tenga en cuenta que un
alignas(8)
basado en
alignas(8)
no tendrá este problema.
Tampoco las estructuras y matrices C / C ++ utilizan el especificador de alineación adecuado (aunque las matrices de tipos más pequeños tienen sus propios problemas).
Este problema
solo
ocurre cuando se usa un
vec3
desnudo.
El soporte de implementación es difuso
Incluso si hace todo bien, se sabe que las implementaciones implementan incorrectamente las reglas de diseño de
vec3
.
Algunas implementaciones imponen efectivamente reglas de alineación de C ++ a GLSL.
Entonces, si usa un
vec3
, lo trata como si C ++ tratara un tipo alineado de 16 bytes.
En estas implementaciones, un
vec3
seguido de un
float
funcionará como un
vec4
seguido de un
float
.
Sí, es culpa de los implementadores.
Pero como no puede
solucionar
la implementación, debe
solucionarla
.
Y la forma más razonable de hacerlo es simplemente evitar
vec3
completo.
Tenga en cuenta que, para Vulkan (y OpenGL con SPIR-V), el compilador GLSL del SDK lo hace bien, por lo que no debe preocuparse por eso.