4

I have recently migrated from a 32bit environment to a 64bit one, and it has gone smoothly apart from one problem: glMultiDrawElements uses some arrays that do not work without some tweaking under a 64bit OS.

glMultiDrawElements( GL_LINE_LOOP, fCount_, GL_UNSIGNED_INT,
                     reinterpret_cast< const GLvoid** >( iOffset_ ),
                     mesh().faces().size() );

I am using VBOs for both the vertices and vertex indices. fCount_ and iOffset_ are arrays of GLsizei. Since a buffer is bound to GL_ELEMENT_ARRAY_BUFFER, iOffset_'s elements are used as byte offsets from the VBO beginning. This works perfectly under a 32bit OS.

If I change glMultiDrawElements to glDrawElements and put it into a loop, it works fine on both platforms:

int offset = 0;
for ( Sy_meshData::Faces::ConstIterator i  = mesh().faces().constBegin();
                                        i != mesh().faces().constEnd(); ++i ) {
    glDrawElements( GL_LINE_LOOP, i->vertexIndices.size(), GL_UNSIGNED_INT,
                    reinterpret_cast< const GLvoid* >( sizeof( GLsizei ) * offset ) );
    offset += i->vertexIndices.size();
 }

I think what I am seeing is OpenGL reading 64bit chunks of iOffset_ leading to massive numbers, but glMultiDrawElements does not support any type wider than 32bit (GL_UNSIGNED_INT), so I'm not sure how to correct it.

Has anyone else had this situation and solved it? Or am I handling this entirely wrong and was just lucky on a 32bit OS?

Update

Swapping out my existing code for:

typedef void ( *testPtr )( GLenum mode, const GLsizei* count, GLenum type,
                           const GLuint* indices, GLsizei primcount );
testPtr ptr = (testPtr)glMultiDrawElements;
ptr( GL_LINE_LOOP, fCount_, GL_UNSIGNED_INT, iOffset_, mesh().faces().size() );

Results in exactly the same result.

cmannett85
  • 21,725
  • 8
  • 76
  • 119

2 Answers2

5

The simple reason is, that glMultiDrawElements doesn't expect an array of integer offsets (32bit on your platform), but an array of pointers (64bit on your platform), interpreted as buffer offsets.

But you are just casting the array of (or pointer to) integers to an array of (or pointer to) pointers, which won't work, as the function now just reinterprets your n consecutive 32bit values as n consecutive 64bit values. Of course it works for glDrawElements because you're just casting a single integer into a single pointer, which essentially converts your 32bit value into a 64bit value.

What you need to do is not cast your pointer/array, but each individual value in this offset array:

std::vector<void*> pointers(mesh().faces().size());
for(size_t i=0; i<pointers.size(); ++i)
    pointers[i] = static_cast<void*>(iOffset_[i]);
glMultiDrawElements( GL_LINE_LOOP, fCount_, GL_UNSIGNED_INT, 
                     &pointers.front(), mesh().faces().size() );

Or better, just store your offsets as pointers instead of integers right from the start.

Christian Rau
  • 45,360
  • 10
  • 108
  • 185
  • Ah, the inverse of what I thought. I can actually get the best of both worlds by just maintaining a single array of `quintptr` (an integral type providing by `Qt` that is the width of the pointer types), and then keeping my `reinterpret_cast` - less loops, less casts, less arrays, and still platform-agnostic. – cmannett85 Jan 04 '12 at 18:15
  • @cbamber85 You could also use the standard C and C++ type `size_t` (which is IMHO also guaranteed to be of pointer size) to be even framework agnostic. – Christian Rau Jan 04 '12 at 18:39
  • @cbamber85: Why use a Qt defined type if there's a standard C type for this: uintptr_t – datenwolf Jan 04 '12 at 20:06
  • @ChristianRau: size_t is not guaranteed to be pointer sized. The type you're looking for it uintptr_t – datenwolf Jan 04 '12 at 20:07
  • @datenwolf: They're equivalent so why not use it? `quintptr` is one less character. – cmannett85 Jan 04 '12 at 20:38
  • @cbamber85: How shall I say this: Introducing and using types which are readily available by the language itself is poor style. There are some reasons why a library or API may decide to introduce self choosen names, but often those reasons are poor. For example Qt explicitly lives in C++ where this type is available. OpenGL however is more or less language agnostic (hence introduces it's own names for specific types, to be definable independent of the C language). – datenwolf Jan 04 '12 at 20:50
  • @datenwolf Wow, I always thought `size_t` is guaranteed to be of pointer width, but I guess this is something practically working but not by standard, like the conversion from uint to pointer. But isn't `uintptr_t` C99/C++11? This might also explain Qt's redefinition and my sticking to `size_t`. – Christian Rau Jan 04 '12 at 21:23
  • @datenwolf Well, I think the decision between `uintptr_t` and `quintptr` is a matter of taste (at least if the code is tightly coupled to Qt anyway), since I also prefer the Qt containers over the std ones for the sake of consistency. But you're right in that `quintptr` is maybe a wrong overabstraction from Qt's side in the first place. – Christian Rau Jan 04 '12 at 21:27
  • @ChristianRau: Yes, it's C99 (I was pretty sure C++ had it as well, but it seems I was wrong, hence Qt's one). size_t is defined as the integer large enough to hold the result of applying the sizeof operator to some type. Thought naively this should be enough to hold a pointer (think of 'sizeof(char [largestpossibleallocation]'); however with the introducion of recent architectures pointers may be even larger. Or you may need to operate in multiple memory segments, which require some higher bits in a pointer value to represent unambigously. On PCs size_t is safe for storing a pointer though. – datenwolf Jan 04 '12 at 21:33
0

You're running into the problems I thoroughly dissected in https://stackoverflow.com/a/8284829/524368

I suggest you follow my suggestion at the very end of the answer, and don't try to cast your numbers into something the compiler thinks is a pointer, but cast the function signature into something that takes a number.

Note that in the case of glMultiDrawElements the first indirection does not go into a VBO, but into client memory. So the signature to cast to are eg.

void myglMultiDrawElementsOffset(GLenum mode,
    const GLsizei * count,
    GLenum type,
    const uintptr_t * indices,
    GLsizei  primcount);
Community
  • 1
  • 1
datenwolf
  • 159,371
  • 13
  • 185
  • 298
  • @cbamber85: You must perform the assignment to signature adjusted function pointer *after* calling `glewInit()` Note that you can cast the function as well: `((TFPTR_VertexOffset)&glVertexPointer)(…)` – datenwolf Jan 04 '12 at 15:42
  • Thanks, I should have noticed that. See my update, it must be my syntax if it has worked on your projects, but I can't get the function pointer to behave any differently to my existing faulty code. – cmannett85 Jan 04 '12 at 16:20
  • 1
    This won't work, as the function (no matter which signature) still expects an array of 64bit values (pointers) and he gives it an array of 32bit values (integers). – Christian Rau Jan 04 '12 at 17:50
  • -1 for casting function pointers. The only time a C++ program ever should cast a function pointer is to assign the correct type to the results of `GetProcAddress` / `dlsym`. – Ben Voigt Jan 04 '12 at 18:19
  • @BenVoigt: Did you read my other answer, why that function pointer is casted? The problem is, that there is no way to use the VBO conforming to the C standard. In either case it's invoking undefined behaviour. – datenwolf Jan 04 '12 at 20:05
  • @BenVoigt: On a second note: If you use those casted function pointer variantes, the compiler will actually complain if you pass an array of mismatching element size (something obscured by the cast to void** if you cast the indices instead). – datenwolf Jan 04 '12 at 20:12
  • @ChristianRau: You're right of course. Silly me I didn't notice that. I edited my answer accordingly. Yet I still think, that creating a signature alias is the sane thing, as this allows the compiler to issue warnings. If you used the signature like I propose, passing an uncasted, mismatched array, the compiler will issue a warning. – datenwolf Jan 04 '12 at 20:15
  • @datenwolf: The only thing that should be cast here, is from `vbo_offset_t` to `void*`, for each offset. Then you're actually passing a `void*[]` to the function, which matches the signature. – Ben Voigt Jan 04 '12 at 20:17
  • 1
    @BenVoigt: I ask you again: Did you read my other answer? Are you aware that casting numbers that are not the result of casting a pointer to ([u]intptr_t) back to a pointer invokes undefined behaviour? If you cast an arbitrary number to a pointer the compiler may as well say "that's not a pointer value, I'm not going to cast this". For example you may be on a implementation, where all pointer values (except null pointer) are >0x1000, thus easy for a compiler to decide for a number <0x1000 that this is not a pointer, which may be silently coereced to 0. – datenwolf Jan 04 '12 at 20:30
  • 1
    @BenVoigt: Just to make this clear: The C99 standard says in section 6.3.2.3.5: "An integer may be converted to any pointer type. Except as previously specified, the result is implementation-defined, might not be correctly aligned, might not point to an entity of the referenced type, and **might be a trap representation.**". In addition 6.3.2.3.8 says: "A pointer to a function of one type may be converted to a pointer to a function of another type and back again; **the result shall compare equal to the original pointer.**" – so there you have it. – datenwolf Jan 04 '12 at 20:55
  • @BenVoigt: According to the C language standard, casting the integer to a pointer is the unsafe way of doing it, and casting the function signature the safe way. – datenwolf Jan 04 '12 at 20:57
  • @datenwolf: Absolutely not! Read that more carefully. The only thing you can safely do with a function pointer after casting is cast it back to the original type, any other use is undefined behavior. And specifically calling through the result of the cast is undefined behavior. – Ben Voigt Jan 05 '12 at 03:49
  • "A function pointer can be explicitly converted to a function pointer of a different type. **The effect of calling a function through a pointer to a function type (8.3.5) that is not the same as the type used in the definition of the function is undefined.**" Section 5.2.10 of the C++ standard (this question is tagged `c++`). – Ben Voigt Jan 05 '12 at 03:53
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/6412/discussion-between-ben-voigt-and-datenwolf) – Ben Voigt Jan 05 '12 at 03:53