Выравнивание структур по стандарту std140, сторона процессора

Я полагаю, что это что-то среднее между вопросом на чистом C++ и вопросом OpenGL. У меня есть юниформ-буфер, и я выделяю в нем место размером sizeof(ShaderData) байт. Я использую макет std140 на стороне графического процессора в шейдере.

Согласно правилам std140, мне нужно добавить отступы в разных местах моей структуры, чтобы убедиться, что такие вещи, как векторы, выровнены правильно. Структура ниже является примером (для моего источника света):

struct ShaderData {

    float Light_Intensity;  
    float _pad1[3];    // align following vec3 on 4N boundary
    Math::Vec3f Light_Position;
    float _pad2;       // align following vec4 on 4N boundary
    Math::Colour4f Light_Ambient;
    Math::Colour4f Light_Diffuse;
    Math::Colour4f Light_Specular;    
    float Light_AttenuationMin;
    float Light_AttenuationMax;

} MyShaderData;

Это то, как люди обычно делают что-то в C++, или есть специальные ключевые слова или прагмы для выравнивания отдельных элементов структуры на стороне процессора, которые немного аккуратнее?


person Robinson    schedule 13.11.2013    source источник


Ответы (1)


Нет, таким образом вы просто теряете место. Вы должны найти оптимизированный макет в соответствии с правилами std140.

  • float требуется 4 байта, и он выровнен по 4 байтам
  • vec3 требуется 12 байтов, и он выровнен по 16 байтам
  • vec4 требуется 16 байт, и он выровнен по 16 байтам

Это означает, что вы можете найти лучший макет для своей структуры:

float Light_Intensity;          X
float _pad1[3];                  XXX
Math::Vec3f Light_Position;         XXX
float _pad2;                           X

Как видите, вы тратите впустую 4 байта, и что еще хуже, вы можете просто сделать что-то вроде:

Math::Vec3f Light_Position      XXX
float Light_Intensity;             X

Чтобы выровнять его и не тратить ни байта. Это работает, потому что vec3 будет выровнено по границам 16 байт, а float будет по-прежнему выровнено по границам 4 байт.

person Jack    schedule 13.11.2013
comment
Хорошо, правильно, я видел это минуту назад. Но в общем случае (а не в моем конкретном случае), как лучше всего добавить отступы? Существует ли всегда оптимизированный порядок, который не требует этого? Кажется маловероятным. - person Robinson; 13.11.2013
comment
AFAIK не существует стандартизированного способа определения заполнения обычным способом, который отличается от того, что вы делаете (фиктивные члены структуры). Есть несколько директив #pragma, связанных с выравниванием, но я не уверен, насколько они стандартны. gcc.gnu.org/onlinedocs/gcc/Structure_002dPacking-Pragmas.html - person Jack; 13.11.2013
comment
Я думаю, вы должны пойти что-то вроде #pragma pack(push,16), чтобы установить выравнивание, а затем #pragma pack(pop), чтобы восстановить его после. - person Jack; 13.11.2013
comment
Хорошо, это выглядело бы довольно ужасно. Наверное, лучше просто подсунуть какой-нибудь float _pad и добавить комментарий о намерении. - person Robinson; 13.11.2013
comment
Вы можете использовать свой собственный макрос, например, PADDING(4) PADDING(16), если вам так удобнее, уж точно лучше, чем #pragma pack(push,16) - person Jack; 13.11.2013
comment
@Robinson Помимо вещей, специфичных для компилятора (например, __declspec(align(16)) в MSVC, __attribute__((aligned(16))) для gcc), C++11 имеет переносимый alignas(16) (хотя и не поддерживается всеми компиляторами), просто добавьте их перед соответствующими объявлениями членов: alignas(16) Math::Vec3f Light_Position. Изменение упаковки структур на 16 было бы слишком строгим для float, которым не требуется выравнивание по 16. - person Christian Rau; 13.11.2013
comment
Хорошо понял. Спасибо, Кристиан. - person Robinson; 13.11.2013