opengl opengl-es glsl vulkan

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.