¿Debería alguna vez usar un `vec3` dentro de un búfer uniforme o un objeto de búfer de almacenamiento de sombreador?

Resuelto Nicol Bolas asked hace 8 años • 1 respuestas

El vec3tipo 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?

Nicol Bolas avatar Jul 04 '16 00:07 Nicol Bolas
Aceptado

¡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.wpara obtener el otro valor. Tratar con él.

Si desea matrices de vec3s, conviértalas en matrices de vec4s. 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 std140el 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 std140el 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 vec3s.

Considere las siguientes definiciones de C++ para un vec3tipo:

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

Ambos son tipos perfectamente legítimos. El sizeofdiseño y diseño de estos tipos coincidirá con el tamaño y diseño que std140se requiera. Pero no coincide con el comportamiento de alineación que std140impone.

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++, sizeofpara ambos Block_ay Block_fserán 24. Lo que significa que offsetof bserán 12.

Sin embargo, en el diseño std140, vec3siempre está alineado con 4 palabras. Y por tanto, Block.btendrá un offset de 16.

Ahora, podrías intentar solucionarlo utilizando alignasla funcionalidad de C++11 (o _Alignasuna 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_ay 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 vec3debe comenzar en un límite de 16 bytes. Pero vec3no consume 16 bytes de almacenamiento; solo consume 12. Y como floatpuede comenzar en un límite de 4 bytes, a vec3seguido de a floatocupará 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 std140requiere 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 vec3s en tus sombreadores y evitar toda esta complejidad adicional.

Tenga en cuenta que un alignas(8)basado en vec2no 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 vec3las 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 vec3seguido de a floatfuncionará como vec4seguido 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 vec3por 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.

Nicol Bolas avatar Jul 03 '2016 17:07 Nicol Bolas