在64位操作系统中使用glMultiDrawElements

4

我最近从32位环境迁移到了64位环境,一切顺利,除了一个问题:glMultiDrawElements使用了一些数组,在64位操作系统下需要进行一些调整才能正常工作。

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

我正在使用VBO来存储顶点和顶点索引。fCount_和iOffset_是GLsizei类型的数组。由于一个缓冲区被绑定到GL_ELEMENT_ARRAY_BUFFER,iOffset_的元素被用作相对于VBO开头的字节偏移量。在32位操作系统下,这个方法运行得非常完美。
如果我将glMultiDrawElements更改为glDrawElements并将其放入循环中,在两个平台上都可以正常工作。
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();
 }

我认为我看到的是OpenGL读取iOffset_的64位块,导致数字巨大,但glMultiDrawElements不支持任何宽度大于32位的类型(GL_UNSIGNED_INT),所以我不确定如何纠正它。
有其他人遇到过这种情况并解决了吗?还是我处理方式完全错误,在32位操作系统上只是碰巧成功了?
更新
将我的现有代码替换为:
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() );

结果完全相同。


fCount_iOffset_是什么类型? - datenwolf
2个回答

5
简单的原因是,glMultiDrawElements 不需要一个整数偏移量数组(在您的平台上为32位),而是需要一个指针数组(在您的平台上为64位),并且将其解释为缓冲区偏移量。
但是你只是将整数数组或指针转换为指针数组或指针,这样不起作用,因为函数现在只将你的 n 个连续的32位值重新解释为n个连续的64位值。当然,对于glDrawElements它是有效的,因为您只是将单个整数强制转换为单个指针,这本质上将您的32位值转换为64位值。
您需要做的是不要将指针/数组强制转换,而是将此偏移量数组中的每个单独的值进行转换:
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() );

或者更好的方法是,从一开始就将您的偏移量存储为指针,而不是整数。

啊,与我想的相反。实际上,我可以通过只维护一个 quintptr 数组(由 Qt 提供的整数类型,宽度为指针类型),从而获得最佳效果 - 减少循环、减少转换、减少数组,同时仍然具有跨平台性。 - cmannett85
@cbamber85 你也可以使用标准的 C 和 C++ 类型 size_t(在我看来,它也保证了指针大小),使其更加跨框架通用。 - Christian Rau
@cbamber85:如果有一个标准的C类型,为什么要使用Qt定义的类型:uintptr_t? - datenwolf
@ChristianRau:size_t不能保证与指针大小相同。你要找的类型是uintptr_t。 - datenwolf
@datenwolf,嗯,我认为在 uintptr_tquintptr 之间做出决策是一个品味问题(至少如果代码与Qt紧密耦合的话),因为为了一致性,我也更喜欢Qt容器而不是标准库容器。但你说得对,quintptr 可能是Qt方面一开始过度抽象的错误。 - Christian Rau
显示剩余4条评论

0

你遇到了我在https://dev59.com/Smsy5IYBdhLWcg3w1xiU#8284829中彻底剖析的问题。

我建议你按照答案最后的建议去做,不要试图将你的数字转换为编译器认为是指针的东西,而是将函数签名转换为接受数字的东西。

请注意,在glMultiDrawElements的情况下,第一个间接寻址不会进入VBO,而是进入客户端内存。因此,要转换的签名是例如:

void myglMultiDrawElementsOffset(GLenum mode,
    const GLsizei * count,
    GLenum type,
    const uintptr_t * indices,
    GLsizei  primcount);

1
这样是行不通的,因为无论函数的签名如何,它仍然期望一个64位值(指针)的数组,而他给出的是一个32位值(整数)的数组。 - Christian Rau
将函数指针强制转换为-1的唯一时刻是为了将正确的类型分配给 GetProcAddress / dlsym 的结果。 - Ben Voigt
@BenVoigt:另外一点:如果您使用这些转换过的函数指针变量,如果传递大小不匹配的数组(如果您转换索引,则通过将其强制转换为void **来隐藏该问题),编译器实际上会发出警告。 - datenwolf
1
@BenVoigt:我再问你一遍:你有看过我的另一个答案吗?你知道将不是通过将指针转换为([u]intptr_t)的结果而得到的数字强制转换为指针会引发未定义行为吗?如果你将任意数字转换为指针,编译器可能会说“这不是指针值,我不会进行转换”。例如,你可能在一个实现中,所有指针值(除了空指针)都大于0x1000,因此编译器可以轻松地决定对于小于0x1000的数字,这不是指针,可能会被静默地强制转换为0。 - datenwolf
1
@BenVoigt:为了明确这一点:C99标准在第6.3.2.3.5节中说:“整数可以转换为任何指针类型。除非另有规定,否则结果是实现定义的,可能不正确对齐,可能不指向所引用类型的实体,并且可能是陷阱表示。”此外,6.3.2.3.8还说:“一个类型的函数指针可以转换为另一个类型的函数指针,然后再转换回来;结果应与原始指针相等。”- 就是这样。 - datenwolf
显示剩余9条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接