0

I was using an OpenGL buffer with a bunch of GLfloats as a vertex buffer and all was well. The format of the GLfloats being [x1, y1, z1, x2, y2, z2, ...].

But then, while following this tutorial, it tells me to use glm::vec3 instead:

glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(GLfloat), &vertices[0], GL_STATIC_DRAW);

glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(glm::vec3), &vertices[0], GL_STATIC_DRAW);

Now this code is valid, and I wonder how would OpenGL know how to fill in the buffer with glm::vec3 instead of GLfloats. Then I wonder, when I read the data back from the buffer, using:

std::vector<glm::vec3> data;
glGetBufferSubData(mTarget, offset, vertexCount * sizeof(glm::vec3), &data[0]);`

Will this make a bunch of glm::vec3? So the question is, how does OpenGL fill buffers with glm::vec3, and does (and if so, how does) it read it back?

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
fordcars
  • 457
  • 3
  • 14

2 Answers2

5

According to OpenGL's documentation, glBufferData() needs a pointer to the data (i.e. an array, i.e. the coordinates of the vertices).

Let's first have a look at glm::vec3's implementation.

If you check out glm's Github repo, you'll see that, depending on your compilation flags, glm::vec3 is a typedef of highp_vec3 which is a typedef of tvec3<float, highp>.

tvec3 is declared in type_vec3.hpp (included by vec3.hpp) and the class (template) methods are defined in type_vec3.inl.

In particular, operator[]'s definition is:

template <typename T, precision P>
GLM_FUNC_QUALIFIER T & tvec3<T, P>::operator[](typename tvec3<T, P>::length_type i)
{
    assert(i >= 0 && static_cast<detail::component_count_t>(i) < detail::component_count(*this));
    return (&x)[i];
}

Given that piece of code, one would assume that x is the first element of the "array" containing the coordinates of glm::vec3. However, when we go back to type_vec3.h, we find:

union { T x, r, s; };
union { T y, g, t; };
union { T z, b, p; };

So x, y and z are separate attributes. But thanks to how class/struct members are laid out, they can be viewed as a single array starting from &x.

We know now, that glm::vec3 (actually tvec3) stores the coordinates in a contiguous manner. But does it also store other attributes ?

Well, we can continue to dive into the code, or use a simple program to give us the answer:

#include <iostream>
#include <ios>

#include <glm/vec3.hpp>

int main()
{
    const glm::vec3 v;

    const size_t sizeof_v   = sizeof(v);
    const size_t sizeof_xyz = sizeof(v.x) + sizeof(v.y) + sizeof(v.z);

    std::cout << "sizeof(v)  : " << sizeof_v   << std::endl;
    std::cout << "sizeof(xyz): " << sizeof_xyz << std::endl;

    std::cout << "sizeof(v) == sizeof(xyz) : " << std::boolalpha << (sizeof_v == sizeof_xyz) << std::endl;
}

Which prints, on my machine:

sizeof(v)  : 12
sizeof(xyz): 12
sizeof(v) == sizeof(xyz) : true

Therefore, glm::vec3 stores only the (x, y, z) coordinates.

Now, if we create a std::vector<glm::vec3> vertices;, it is safe to say that the layout of the data pointed by &vertices[0] (which, in C++11 is vertices.data()) is:

vertices == [vertice1 vertice2 ...]
         == [vertice1.x vertice1.y vertice1.z vertice2.x vertice2.y vertice2.z ...]

Going back to the original issue -- glBufferData()'s requirements: when you pass &vertices[0] you are actually passing the address (i.e. pointer) of the data, just as expected by glBufferData(). The same logic applies to glGetBufferSubData().

Community
  • 1
  • 1
maddouri
  • 3,737
  • 5
  • 29
  • 51
  • Great! This is very helpful. Just as a bonus, could you pass "anything" to an OpenGL buffer? Say an array of instances of a class. I am guessing that when you would read the data back, it wound't quite work, or would it? – fordcars Oct 10 '15 at 22:52
  • It depends on what GL object you're _[binding](https://www.opengl.org/sdk/docs/man3/xhtml/glBindBuffer.xml)_ to. But in my experience, if you want to upload vertices, you're going to have to use the method mentioned in the tutorial (i.e. passing a raw array or glm::vec3 etc... basically, a contiguous structure of `x, y, z` coordinates. So if you creates a custom class that has **only** `x, y, z` defined like in `glm` (or an array of size 3), you should be fine passing a vector that contains a number of its instances. – maddouri Oct 10 '15 at 23:07
  • 1
    @fordcars: Well, a GL buffer object is just a continous region of memory with a specified size. So you can put everything you like there, and can also read it back. However, if you intend to use the buffer as input for OpenGL itself, you need to mathc some restrictions, depending on the buffer type you are using. Vertex attributes can be 1 to 4 dimensional vectors of certain data types, and vertex arrays need to be laid out with a constant offstet between consecutive elements. Putting instances of objects there might work, but you might waste some memory. – derhass Oct 11 '15 at 02:38
1

glm::vec3 is just three floats in a struct. So passing the adress of a glm::vec3 to gl function effectively does the same thing as passing the adress to the first element of a float array. GLfloat is just a typedef of float btw.

The same principle apply when reading data from gl. An array of x elements of glm::vec3 in memory is equivalent to an array of GLfloat (float) with 3x elements.

warsac
  • 251
  • 1
  • 11