21

我已经看到在 OpenGL VBO 实现中使用了以下宏:

#define BUFFER_OFFSET(i) ((char *)NULL + (i))
//...
glNormalPointer(GL_FLOAT, 32, BUFFER_OFFSET(x));

你能提供一些关于这个宏是如何工作的细节吗?可以用函数代替吗?更准确地说,增加 NULL 指针的结果是什么?

4

4 回答 4

34

让我们回顾一下 OpenGL 的肮脏历史。曾几何时,有 OpenGL 1.0。你曾经glBeginglEnd做画,仅此而已。如果你想要快速绘图,你可以把东西放在显示列表中。

然后,有人有了一个聪明的主意,能够只使用对象数组进行渲染。于是诞生了 OpenGL 1.1,它为我们带来了glVertexPointer. 您可能会注意到此函数以单词“Pointer”结尾。glDraw*那是因为它需要指向实际内存的指针,当调用一组函数时将访问这些指针。

再快进几年。现在,显卡有能力自己执行顶点 T&L(到目前为止,固定功能的 T&L 是由 CPU 完成的)。最有效的方法是将顶点数据放在 GPU 内存中,但显示列表并不理想。那些太隐蔽了,没有办法知道你是否会得到好的表现。输入缓冲区对象。

然而,因为 ARB 的绝对政策是让一切尽可能向后兼容(无论它使 API 看起来多么愚蠢),他们决定实现这一点的最佳方法是再次使用相同的函数。只是现在,有一个全局开关将glVertexPointer' 的行为从“获取指针”更改为“从缓冲区对象获取字节偏移量”。该开关是缓冲区对象是否绑定到GL_ARRAY_BUFFER.

当然,就 C/C++ 而言,该函数仍然需要一个指针。而且 C/C++ 的规则不允许您将整数作为指针传递。不是没有演员表。这就是BUFFER_OBJECT存在宏的原因。这是将整数字节偏移量转换为指针的一种方法。

(char *)NULL部分简单地将 NULL 指针(在 C 中通常为 a void*,在 C++ 中为文字 0 )并将其转换为char*. + i只是对char*. 因为空指针通常有一个零地址,添加i它会增加字节偏移量i,从而生成一个指针,其值是您传入的字节偏移量。

当然,C++ 规范将 BUFFER_OBJECT 的结果列为未定义行为。通过使用它,您实际上是在依靠编译器来做一些合理的事情。毕竟,NULL不一定为零;所有规范都说它是一个实现定义的空指针常量。它根本不必具有零值。在大多数真实系统上,它会。但这不是必须的。

这就是为什么我只使用演员表。

glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 0, (void*)48);

无论哪种方式都不能保证行为(有条件地支持 int->ptr->int 转换,不是必需的)。但它也比键入“BUFFER_OFFSET”要短。GCC 和 Visual Studio 似乎认为它是合理的。而且它不依赖于 NULL 宏的值。

就个人而言,如果我更喜欢 C++ 迂腐,我会使用 a reinterpret_cast<void*>。但我不是。

或者你可以放弃旧的 API 并使用glVertexAttribFormatet。人。,这在各方面都更好。

于 2011-11-27T05:32:51.897 回答
28
#define BUFFER_OFFSET(i) ((char *)NULL + (i))

从技术上讲,此操作的结果是undefined,并且宏实际上是错误的。让我解释:

C 定义(并且 C++ 遵循它),指针可以转换为整数,即 type uintptr_t,并且如果以这种方式获得的整数,转换回它来自的原始指针类型,将产生原始指针。

然后是指针算术,这意味着如果我有两个指针指向同一个对象,我可以取它们的差,产生一个整数(类型为ptrdiff_t),并且该整数添加或减去任何一个原始指针,将产生其他。它还定义了,通过将 1 加到指针上,将产生指向索引对象的下一个元素的指针。此外,两个uintptr_t, 除以sizeof(type pointed to)同一对象的指针的差必须等于被减去的指针本身。最后但并非最不重要的一点是,这些uintptr_t值可能是任何东西。它们也可以是不透明的手柄。它们不需要是地址(尽管大多数实现都是这样做的,因为它是有意义的)。

现在我们可以看看臭名昭著的空指针。C 将类型值 0 转换为 for 的指针定义uintptr_u为无效指针。请注意,这在您的源代码中始终为 0。在后端,在编译后的程序中,用于实际向机器表示它的二进制值可能完全不同!通常它不是,但它可能是。C++ 是一样的,但是 C++ 不允许像 C 一样多的隐式转换,所以必须将 0 显式转换为void*. 也因为空指针不引用对象,因此没有取消引用大小指针算术是未定义的空指针。没有引用对象的空指针也意味着,没有定义将其明智地转换为类型化指针。

所以如果这一切都是未定义的,为什么这个宏到底能工作?因为大多数实现(意味着编译器)都非常容易上当受骗,而编译器编码人员则非常懒惰。在大多数实现中,指针的整数值只是后端指针本身的值。所以空指针实际上是 0。尽管没有检查空指针的指针算术,但大多数编译器会默默地接受它,如果指针被分配了某种类型,即使它没有意义。char如果你想说的话,是 C 的“单位大小”类型。因此,演员表上的指针算术就像后端地址上的人工算术。

长话短说,尝试使用指针魔术来将预期结果作为 C 语言方面的偏移量是没有意义的,它只是不能那样工作。

让我们退后一步,记住我们实际上想要做的事情:最初的问题是,gl…Pointer函数将指针作为它们的数据参数,但是对于顶点缓冲区对象,我们实际上想要指定一个基于字节的偏移量到我们的数据,是一个数字。对于 C 编译器,该函数接受一个指针(我们了解到,这是一个不透明的东西)。正确的解决方案是引入新功能,特别是与gl…OffsetVBO 一起使用(比如——我想我会为他们的介绍而集会)。相反,OpenGL 定义的是对编译器工作方式的利用。大多数编译器将指针及其等效整数实现为相同的二进制表示。所以我们要做的就是让编译器用gl…Pointer我们的数字而不是指针来调用这些函数。

所以从技术上讲,我们唯一需要做的就是告诉编译器“是的,我知道你认为这个变量a是一个整数,你是对的,那个函数glVertexPointer只接受一个void*作为它的数据参数。但是你猜怎么着:那个整数是产生的从一个void*“,通过将其转换为(void*)然后按住拇指,编译器实际上是如此愚蠢地将整数值按原样传递给glVertexPointer.

所以这一切都归结为以某种方式规避旧的函数签名。投射指针是恕我直言的脏方法。我会做一些不同的事情:我会弄乱函数签名:

typedef void (*TFPTR_VertexOffset)(GLint, GLenum, GLsizei, uintptr_t);
TFPTR_VertexOffset myglVertexOffset = (TFPTR_VertexOffset)glVertexPointer;

现在您可以使用myglVertexOffset而无需进行任何愚蠢的强制转换,并且 offset 参数将被传递给函数,没有任何危险,编译器可能会弄乱它。这也是我在程序中使用的方法。

于 2011-11-27T10:12:59.137 回答
2

这不是“NULL + int”,而是“将NULL转换为类型'pointer to char'”,然后将该指针增加i。

是的,它可以被一个函数取代——但如果你不知道它的作用,那你为什么要关心它呢?首先了解它的作用,然后考虑它是否会更好作为一个功能。

于 2011-11-27T04:53:42.200 回答
2

openGL 顶点属性数据通过相同的函数 (glVertexAttribPointer) 分配为内存中的指针或位于顶点缓冲区对象中的偏移量,具体取决于上下文。

BUFFER_OFFSET() 宏似乎将整数字节偏移量转换为指针,只是为了允许编译器安全地将其作为指针参数传递。“(char*)NULL+i”通过指针算术表示这种转换;结果应该是相同的位模式,假设 sizeof(char)==1,没有它,这个宏会失败。

也可以通过简单的重新铸造,但宏可能会在风格上更清晰地传递正在传递的内容;对于 32/64 位安全/面向未来,它也是捕获溢出的一个方便的地方

struct MyVertex { float pos[3]; u8 color[4]; }
// general purpose Macro to find the byte offset of a structure member as an 'int'

#define OFFSET(TYPE, MEMBER) ( (int)&((TYPE*)0)->MEMBER)

// assuming a VBO holding an array of 'MyVertex', 
// specify that color data is held at an offset 12 bytes from the VBO start, for every vertex.
glVertexAttribPointer(
  colorIndex,4, GL_UNSIGNED_BYTE, GL_TRUE,
  sizeof(MyVertex), 
  (GLvoid*) OFFSET(MyVertex, color) // recast offset as pointer 
);
于 2011-11-27T04:54:22.420 回答