web-dev-qa-db-ja.com

ユニフォームバッファーまたはシェーダーストレージバッファーオブジェクト内で `vec3`を使用する必要がありますか?

vec3タイプは非常にいいタイプです。 3つの浮動小数点数のみを使用し、3つの浮動小数点数のみを必要とするデータがあります。そして、私はUBOやSSBOの構造でそれを使いたいです:

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

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

次に、CまたはC++コードで、これを実行して一致するデータ構造を作成できます。

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

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

これは良い考えですか?

30
Nicol Bolas

NO!絶対にしないでください!

UBO/SSBOを宣言するときは、すべての3要素ベクトルおよび行列タイプが存在しないと仮定します。唯一のタイプがスカラー、2、および4要素ベクトル(および行列)であると仮定します。そうすれば、あなたは自分自身を非常に多くの悲しみから救うでしょう。

Vec3 + floatの効果が必要な場合は、手動でパックする必要があります手動で

_layout(std140) uniform UBO
{
  vec4 data1;
  vec4 data2and3;
};
_

はい、他の値を取得するには_data2and3.w_を使用する必要があります。それに対処します。

_vec3_ sの配列が必要な場合は、それらを_vec4_ sの配列にします。 3要素のベクトルを使用する行列についても同様です。 SSBO/UBOからの3要素ベクトルの概念全体を追放するだけです。あなたは長期的にははるかに良くなるでしょう。

_vec3_を避けるべき理由は2つあります。

C/C++は何もしない

_std140_レイアウトを使用する場合は、GLSLの定義と一致するCまたはC++のデータ構造を定義する必要があります。これにより、2つを簡単に組み合わせることができます。また、_std140_レイアウトを使用すると、ほとんどの場合にこれを実行できます。ただし、_vec3_ sに関しては、そのレイアウト規則はCおよびC++コンパイラの通常のレイアウト規則と一致しません。

_vec3_タイプの次のC++定義を検討してください。

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

これらは両方とも完全に正当なタイプです。これらのタイプのsizeofとレイアウトは、_std140_に必要なサイズとレイアウトに一致します。しかし、それは_std140_が課す整列動作と一致しません。

このことを考慮:

_//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;
};
_

ほとんどのC++コンパイラでは、_Block_a_と_Block_f_の両方のsizeofは24になります。つまり、offsetofbは12になります。

ただし、std140レイアウトでは、_vec3_は常に4ワードに揃えられます。したがって、_Block.b_のオフセットは16になります。

ここで、C++ 11のalignas機能(またはC11の同様の__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;
};
_

コンパイラーが16バイト境界整列をサポートしている場合、これは機能します。または、少なくとも、_Block_a_および_Block_f_の場合に機能します。

しかし、この場合機能しません

_//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;
};
_

_std140_の規則により、各_vec3_は16バイト境界でstartでなければなりません。ただし、_vec3_は16バイトのストレージをconsumeしません。消費するのは12だけです。また、floatは4バイト境界で開始できるため、_vec3_の後にfloatが続く場合は、16バイトを使用します。

しかし、C++アラインメントのルールでは、そのようなことは許可されていません。型がXバイト境界に揃えられている場合、その型を使用するとXバイトの倍数が消費されます。

したがって、_std140_のレイアウトを一致させるには、それが使用される場所に基づいてタイプを選択する必要があります。 floatが後に続く場合は、_vec3a_を使用する必要があります。 4バイト以上の境界で整列されている型が後に続く場合は、_vec3a_16_を使用する必要があります。

または、シェーダーで_vec3_ sを使用せず、この複雑さの追加をすべて回避することもできます。

alignas(8)ベースの_vec2_ではこの問題は発生しないことに注意してください。また、適切なアライメント指定子を使用したC/C++のstructs&arraysも使用しません(小さいタイプの配列には独自の問題があります)。この問題onlyは、ネイキッド_vec3_を使用しているときに発生します。

実装サポートはあいまいです

すべてを正しく実行しても、実装は_vec3_の奇数レイアウトルールを誤って実装することが知られています。一部の実装では、GLSLにC++の配置規則を効果的に課しています。したがって、_vec3_を使用する場合は、C++が16バイト境界で整列された型を扱うように扱います。これらの実装では、_vec3_に続くfloatは、_vec4_に続くfloatのように機能します。

はい、それは実装者の責任です。しかし、実装をfixできないため、回避する必要があります。そして、それを行う最も合理的な方法は、_vec3_を完全に回避することです。

Vulkan(およびSPIR-Vを使用するOpenGL)の場合、SDKのGLSLコンパイラがこれを正しく行うため、そのために心配する必要はありません。

37
Nicol Bolas