Passing Data through OpenGL API

Well I’ve just been thinking about some implementation details lately and how data is passed through the OpenGL API. Well most math API and such define a vector (including GLM), at least a simplified version of such, like so:

struct Vector3
{
    float x, y, z;
};

// to pass to OpenGL

Vector3 v;

glSomeFunction( &v );
glSomeFunction( &v.x );

// basically how it's done in glm


Which, I guess depending on OpenGL implementation, though I think I’ve read in some documentation that the API expects an array. Passing a structure as an array is undefined behavior in C and C++. I mean GLM works on several compilers and the behavior seems to be similar in each case. Not sure if there is any that can shed some light on this situation, it seems to be 2 or more specification of C/C++ simply imply this to happen even if the specification doesn’t explicitly define it as a rule.

Passing a structure as an array is undefined behavior in C and C++.

Before going any further, where’d you get this? Where in the C or C++ standard is that actually written (not that rules in both languages are actually equal when it come to structs, but hey)?

EDIT: Just to give you a hint why your wording is flawed, consider your example: in both cases you pass a pointer, most likely implicitly converted by the compiler to a GLvoid*. In some cases strictly specified in the C++ standard (C++03 is more strict than C++11, actually), this is not only legal but equally safe or unsafe (as working with pointers can be dangerous if you’re not using them correctly) as well. In your case it certainly is.

It’s the case that the standard doesn’t state that the two must the same. An implementation is free to insert whatever padding it wishes between elements of a structure, and at the end, while arrays are required to be packed (i.e. given “type x[n]”, sizeof(x) == n*sizeof(x[0])).

So on a 64-bit platform with a 32-bit “float” type, the structure could (theoretically) have 32 bits of padding after each element, while the array can’t (i.e. the structure could be 48 bytes while the array is 24 bytes).

But you’re not going to encounter such behaviour on any platform which supports OpenGL.

@thokra

Arrays were existent in the C89 standard which C++ is built on and supports so they should be equivalent in this regard.
An array is not a pointer though, an array can be converted to a pointer but using a pointer to an arbitrary variable as an array converted to a pointer is not the same. Thinking a pointer is an array is what’s flawed (the standard makes a clear distinction between the two as well as the conversion from an array to a pointer).

@GClements

So if for any reason the implementation details of the compilers were to change OpenGL (potentially Directx as well ?) would not be supported any more. That’s a bit worrying.

The OpenGL API doesn’t use structures, so how they’re implemented isn’t an issue. It may be an issue for libraries which try to build a higher-level interface on top of OpenGL, e.g. defining a vector type using a structure.

Also, structure layout isn’t generally the choice of a particular compiler. Operating systems normally define an ABI, which compilers have to follow (otherwise, you wouldn’t be able to write programs which used the system’s libraries).

True. But in the case of

But you’re not going to encounter such behaviour on any platform which supports OpenGL.

But you’re not going to encounter such behaviour on any platform which supports OpenGL.

Still, my bad. I foolishly assumed that the OP is on an x86/AMD64 platform where float alignment is the same accross most of the major compilers. One cannot safely make that assumption.

Which OpenGL functions take arguments of array type?

Where did I say that “an array is a pointer”? I said that you “pass a pointer” to a GL function. It’s you who stated something completely bogus:

You cannot pass an argument of structure type to a function expecting an array argument, even if the array parameter is an array of said structure type. Furthermore, array arguments simply decay to a pointer the the first element in C/C++, unless your parameter is a reference to an array in C++ like so:

void f(T (&array)[N]);

Now, what GLM leverages when returning a pointer to the first member of its data types, which are, in C++11 terms, all standard-layout structs, are the following facts:

[ul]
[li]a pointer to an object of structure type which has standard-layout will point to the memory location of its first member, if reinterpreted suitably (i.e. there’s no padding at the beginning of the struct) [/li][li]a standard-layout container has to have only non-static data members with the same access control, i.e. consecutive members will be at higher, consecutive memory locations [/li][li]if the compiler on a specific platform does not enforce a certain alignment, memory occupied by members of a standard-layout struct will be contiguous [/li][/ul]

If all this holds, then you can simply reinterpret a pointer to a an object to a pointer to it’s first member and then apply pointer artihmetic to walk over contigous elements - just like you would for an array of members. Although alignment requirements aren’t standardized, you can still get the needed information for certain compilers and platforms, e.g. on x86/AMD64 platform with G++ and MSVC++, the following type

struct Vector3
{
  float x, y, z;
};

is a standard-layout class type which will contain no unnnamed members for alignment reasons, i.e.

sizeof(Vector3) == 3 * sizeof(float)

It therefore holds that for a

Vector3 v;

true == (reinterpret_cast<float*>(&v) == &v.x) == (&v == reinterpret_cast<Vector3*>(&v.x));

and


true == ((&v.x + 1) == &v.y))

If all that’s cool, for any GL function taking a pointer to a block of contiguous memory, like glBufferData(), or glUniform3fv(), pointers returned by GLM can be used without problems.

Hope I got that all right.

If you’re using a non-trivial OS, the compiler has to conform to the platform’s ABI, otherwise you can’t use libraries built with one compiler from an executable built with another. At least, that’s the situation for C; neither Linux nor Windows formally define an ABI for C++, although the parts inherited from C are consistent so that calling C functions from C++ works.

You’re making a distinction which isn’t meaningful. In C, arrays are passed by reference, i.e. as a pointer to their first element. The majority of OpenGL functions which accept a pointer argument expect it to point to the first element of an array, not to a single value.

C requires that a pointer to a structure can be converted to a pointer to the type of its first member. I.e. there is no padding at the beginning of the structure and the structure’s alignment is compatible with its first member. That part is portable.

Alignment isn’t actually the issue. The majority of platforms (including those for x86 and x86-64 architectures, which don’t have hard alignment constraints) impose alignment constraints on structure fields (e.g. aligning floats to a 4-byte boundary). However, for a structure whose fields are all of the same type, the alignment requirements won’t require the addition of padding unless the alignment is larger than the size (which is unlikely, to say the least).

The point at issue is that this is merely “typical”, not something which is required by the C or C++ standards. I.e. neither standard requires that “Vector3 x[n]” and “float x[n][3]” have the same layout.

You can avoid the issue of padding between fields by using an array within the struct, e.g.


typedef struct {
    float v[3];
} Vector3;

However: this may still result in padding being added at the end, so while a pointer to the above structure could be passed (via a C-style cast or reinterpret_cast) to e.g. glVertex3fv(), passing it to e.g. glVertexPointer(3,GL_FLOAT,…) isn’t strictly portable.

You’re making a distinction which isn’t meaningful. In C, arrays are passed by reference, i.e. as a pointer to their first element.

Did you overread my mentioning of arrays decaying to pointers in C++ and C? It’s simply not true that a function in C/C++ whose prototype declares a pointer parameter T*, takes an object of array type (T[N]) as this parameter’s argument. It’s only due to arrays decaying to pointers to their first member in the context of function calls, i.e. due to implicit conversion, that you can pass an object x (where x is of type T[N]) to a function taking a T* (or a parameter T[N] - which is just different syntax for the same thing which doesn’t even carry actual size information). That doesn’t change the fact that an array is not a pointer. Actually I wouldn’t have questioned the OPs statement if the OP themself hadn’t accused me of equating arrays to pointers earlier.

The point at issue is that this is merely “typical”, not something which is required by the C or C++ standards.

And I didn’t explicitly nor implicity state that it was. I explicitly assoiated that behavior with a specific compiler on a specific platform.

You can avoid the issue of padding between fields by using an array within the struct

True, but that’s simply not how GLM does it.

Alignment isn’t actually the issue.

I’m pretty sure that this is actually the whole problem here. Alignment of structure members is, if necessary, done by adding unnamed padding in between. For the above mentioned case, padding at the end isn’t an issue.