c++ - 当c ++未指定结构布局时,为什么glBufferData可以为UBO和SSBO缓冲结构

2021-12-19 00:00:00 opengl struct c++

我正在浏览 这个关于如何在openGL中使用统一缓冲区对象的页面,看到了以下结构:

I was browsing this page on how to use Uniform Buffer Objects in openGL and saw the following struct:

struct shader_data_t
{
    float camera_position[4];
    float light_position[4];
    float light_diffuse[4];
} shader_data;

使用

GLuint ubo = 0;
glGenBuffers(1, &ubo);
glBindBuffer(GL_UNIFORM_BUFFER, ubo);
glBufferData(GL_UNIFORM_BUFFER, sizeof(shader_data), &shader_data, GL_DYNAMIC_DRAW);
glBindBuffer(GL_UNIFORM_BUFFER, 0);

并在着色器中用作

...
layout (std140) uniform shader_data
{ 
  vec4 camera_position;
  vec4 light_position;
  vec4 light_diffuse;
};
...

但是,我不明白openGl如何在glBufferData转换结构指针和void指针时知道如何将结构中的数据映射到uniforms,这应该使openGL不知道结构的内存布局.c++ 结构布局是实现定义的,虽然作者和 UBO 的其他用户手动用虚拟变量填充他们的 c++ 结构以匹配着色器中的标准化 std140 布局,但什么阻止了 c++ 编译器添加更多填充和破坏布局?C++ 标准中是否有强有力的保证,即不会插入更多填充,或者这是实际上可移植"的交易吗?

However, I don't understand how openGl knows how to map the data in the struct to the uniforms when glBufferData converts the struct pointer and a void pointer, which should make openGL unaware of the memory layout of the struct. c++ struct layout is implementation defined, and while the author and other users of UBOs manually pad their c++ structures with dummy variables to match the standardized std140 layout in the shader, what prevents the c++ compiler from adding more padding and breaking the layout? is there a strong guarantee in the c++ standard that more padding will not be inserted or is this a "practically portable" deal?

推荐答案

OpenGL 非常清楚地定义了 std140 接口块的字节布局是什么.您在 C++ 端要做的就是提供符合该布局的数据.如果您可以定义一个编译器将与 std140 匹配的结构,那么您就可以了.你是怎么做到的?

OpenGL defines, very clearly, what the byte layout of a std140 interface block is. All you have to do on the C++ side is provide data in accord with that layout. If you can define a struct that your compiler will match with std140, then you're fine. How do you do that?

您必须了解您的编译器用于布局类型的规则.

You have to know the rules that your compiler uses to lay out types.

C++11 定义了一个名为标准布局类型的概念".如果您遵循某些规则,则您的类型是标准布局.现在,这对于确切了解它们在内存中的布局并没有多大意义.C++ 告诉您关于布局的标准布局类型的唯一事情是忽略空基类(只要它保持标准布局)并且第一个 NSDM 将在类的最开始.也就是说,前面永远不会有填充.

C++11 defines a concept called "standard layout types". If you follow certain rules, your types are standard layout. Now, this doesn't really mean much for knowing exactly how they are laid out in memory. The only things C++ tells you about standard layout types with regard to layout is that empty base classes are ignored (so long as it remains standard layout) and that the first NSDM will be at the very beginning of the class. That is, there will never be padding at the front.

标准说的另一件事是,相同访问类别的 NSDM 将按顺序分配,后面的比前面的有更大的偏移量.由于不允许在标准布局类型中拥有不同访问类的不同 NSDM,因此您可以依赖它们按指定的顺序进行布局.

The other thing the standard says is that NSDMs of the same access class will be allocated in order, with later ones having larger offsets than earlier ones. And since you're not allowed to have different NSDMs of different access classes in standard layout types, you can rely on them being laid out in the order specified.

但就 C++ 标准而言,仅此而已.[class.mem]/13 指出实现可以出于各种原因在成员之间添加填充.

But that's it, as far as the C++ standard is concerned. [class.mem]/13 states that implementations can add padding between members for various reasons.

然而,非正式地,标准布局类型"的规则很重要.给你一个很好的指南,让你知道什么时候不会添加这样的填充.遵循标准布局的规则,您可以假设,对于大多数系统,您的类的布局将在所用类型的大小和对齐方式允许的情况下尽可能紧凑.

Informally however, the rules of "standard layout types" give you a good guideline to know when such padding won't be added. Follow the rules of standard layout, and you can assume, for most systems, that your class's layout will be as tightly packed as the sizes and alignments of the types being used permit.

实现必须一起玩吗?不.但真的没有理由假设他们不会.最坏的情况是最坏的,您可以随时检查实现如何布局类型.

Do implementations have to play along? No. But there's really no reason to assume that they won't. And worst comes to worst, you can always check to see how implementations lay types out.

相关文章