Optimization 什么';考虑到各种优化,更快的glUniform4f/glUniform4fv是什么?

Optimization 什么';考虑到各种优化,更快的glUniform4f/glUniform4fv是什么?,optimization,Optimization,这是签名 glUniform4f(GLint location, GLfloat x, GLfloat y, GLfloat z, GLfloat w); glUniform4fv(GLint location, GLsizei count, const GLfloat *v); 在我看来,前者应该更快,因为值可以直接从寄存器传递,而无需从内存中获取。然而,我想听听很多意见。它们是不可比的。前者设置一个统一变量,它是一个标量(我指的是单个4向量),后者设置一个数组(4向量) 可以将长度为1的数

这是签名

glUniform4f(GLint location, GLfloat x, GLfloat y, GLfloat z, GLfloat w);
glUniform4fv(GLint location, GLsizei count, const GLfloat *v);

在我看来,前者应该更快,因为值可以直接从寄存器传递,而无需从内存中获取。然而,我想听听很多意见。

它们是不可比的。前者设置一个统一变量,它是一个标量(我指的是单个4向量),后者设置一个数组(4向量)

可以将长度为1的数组视为标量,反之亦然,但这是不正确的。因此,你永远不会在两者之间做出选择


如果你真的在讨论使用一个数组变量和几个标量之间的决定,那么只要你以相同的方式访问它们(即使用它们进行相同类型的计算),我会想象一个数组会更快,因为如果你使用多个标量,你必须自己做所有的数组算术,硬件在这方面可能会比您做得更好。

虽然
*v
变量主要用于设置数组类型的制服,但OpenGL规范明确允许您使用数组变量,也可以通过使用一个计数来设置标量值

让我引用OpenGL规范(我自己添加的):

glUniform{1 | 2 | 3 | 4}{f | i}v命令可用于修改单个统一变量 或统一变量数组。这些命令传递一个计数和一个指针,指向要执行的值 加载到统一变量或统一变量数组中应使用计数1 如果修改单个统一变量的值,且计数为1或更大 可用于修改整个阵列或阵列的一部分

这是来自的,但是对于的读取方式相同

事实上,反过来也是允许的。假设您有一个类型为
vec3 v[2]
的统一,并使用
glGetUniformLocation()
查询其位置,它可能返回
6
。这意味着
6
实际上是
v[0]
的位置


现在回到最初的问题:哪种变体更快? 很难说。它们可能同样快,或者一个可能比另一个快,这非常依赖于实现。实际上,我希望大多数实现在另一个实现之上实现这两个实现中的一个

例如考虑下面的代码:

void glUniform1f ( GLint location, GLfloat v0 ) {
    glUniform1fv(location, 1, &v0);
}
在这种情况下,阵列变体会更快。但是,也可以使用以下变体:

void glUniform1fv ( GLint location, GLsizei count, GLfloat * value ) {
    int i;

    for (i = 0; i < count; i++) {
        glUniform1f(location, *value);
        value++;
        location++;
    }
}
仅为了调用非数组函数而取消对vPtr的三次引用是相当愚蠢的,而且几乎不会比以下实现更快:

void setUniform ( GLint location, struct v3 * vPtr ) {
    glUniform3fv(location, 1, (const GLfloat *)vPtr);
}

此外,所有阵列变体始终有三个参数,而其他变体最多可以有五个参数。需要传递给函数的参数越多,当这些参数通过堆栈而不是在寄存器中传递时,函数调用本身的速度就越慢。函数调用具有的参数越多,就越不可能在具有混合调用方案的体系结构的寄存器中传递这些参数。因此,从普通CPU上的纯函数调用开销来看,对参数较少的函数的调用通常比对参数较多的函数的调用快,尽管只有在每秒执行数千次调用时,这种差异才会起作用,这通常不是统一值的情况。

首先问问自己为什么存在*v版本。 要理解答案,您需要知道CPU如何与GPU通信

现代计算机通过网络与GPU通信。这是另一个将内存从一个设备批量移动到另一个设备的硬件。你说,将所有内容从void*a移动到void*b,它就会离开并执行(没有CPU)。
然而,当您调用OpenGL函数时,它实际做的是将命令/数据写入一个特殊的预先分配的内存块,称为。在稍后的某个时间点,它会告诉DMA控制器移动它。它是这样工作的,因为…
a) 您可以在DMA传输结束前更改内存内容。复制内存会停止任何潜在的竞争条件。
b) 这意味着对于计算机程序来说,看起来像一块内存的东西实际上可能分散在实际的物理内存中。命令列表保证具有在物理内存中作为块的属性

对于大多数实现,一个调用应该映射到命令列表中的一个命令。如果是这样的话,如果命令是4字节,并且您正在设置浮点值。。。每个元素调用一个函数意味着命令缓冲区是50%的命令,50%的数据。调用向量函数意味着整个向量只有一个命令,而命令缓冲区几乎完全是数据

所以。。。
glUniform*v是存在的,因为在大多数实现中,非向量版本的效率低达50%。这并不奇怪。如果一个API提供了一个函数,这通常是因为用其他方法实现同样的功能是不可能的,或者成本太高。

Wow。我无法考虑“count”参数的作用。它用于批量加载,避免呼叫开销!我真傻!谢谢!OpenGL标准明确地允许使用数组函数使用1的计数来设置标量值,所以我认为你的答案是毫无意义的。当然,你可以。但如果你有一个标量变量,那么使用数组形式是有悖常理的。如果这些值已经作为指针可用,那么只调用非数组函数(可能多次)而不使用指针是非常不合理的,因为非数组函数的函数调用开销高于数组变量。
void setUniform ( GLint location, struct v3 * vPtr ) {
    glUniform3fv(location, 1, (const GLfloat *)vPtr);
}