为什么Webgl在设置uniform时没有调用gl.useProgram会发出警告

问题描述 投票:0回答:1

如果我从一开始就调用

gl.useProgram
,我的代码就可以正常工作。另一方面,如果我从不调用
gl.useProgram
,则不会显示任何内容并在控制台中收到两种警告:

第一:

WebGL: INVALID_OPERATION: uniformMatrix4fv: location is not from current program

第二:

WebGL: INVALID_OPERATION: drawElements: no valid shader program in use

第二个警告对我来说很有意义,这是

drawElements

的签名
drawElements(mode: GLenum, count: GLsizei, type: GLenum, offset: GLintptr):

当您发出绘制调用时,您需要知道要运行哪个程序,但调用签名没有程序的提示,因此需要一个全局程序,所以我将

gl.useProgram
放在
gl.drawElements
之前,一切正常,但是第一种警告仍然存在,这对我来说毫无意义!让我们看看 set Uniform 的签名

vertexAttrib4fv(index: GLuint, values: Iterable<GLfloat>): void;

第一个实际上是程序在运行时可以引用的内存中的地址,并且应该从程序中获取该地址,例如

getUniformLocation(program: WebGLProgram, name: string): WebGLUniformLocation | null;

程序已经用来获取地址了,为什么在设置uniform时需要当前的全局状态呢?我是否遗漏了任何会导致问题的案例?

webgl
1个回答
0
投票

统一状态(您使用

gl.uniformXXX
设置的值)是程序状态,因此如果您没有使用
gl.useProgram
设置程序,那么WebGL不知道您要为哪个程序设置状态。

我明白你的观点,但它应该知道哪个程序,因为它知道该位置来自哪个程序,所以为什么不能使用该信息来设置正确的程序状态。

问题是 WebGL 基于 OpenGL ES,虽然有些事情发生了变化,但它的目标是在网络的某些限制下尽可能接近原始 API。

在 OpenGL 中,统一位置是整数,而不是特殊对象。

 int someLocation = glGetUniformLocation(someProgram, "myUniform");
 printf("%d\n", someLocation);

上面的代码可能会打印

0
1
37
99
等......只是一个数字。 WebGL 决定将
gl.getUniformLocation
更改为
WebGLUniformLocation
对象而不仅仅是整数的原因是网页需要可移植,而程序员经常假设他们不应该做的事情。所以,他们可能会宣布两套制服

  uniform float a;
  uniform float b;

然后对这样的位置进行数学计算

 int locationA = glGetUniformLocation(prg, "a");
 int locationB = locationA + 1;

这可能在他们的机器上工作,但无法移植,因为其他驱动程序上的其他机器将使用不同的号码。事实上,他们甚至可能不会查找位置,只是假设第一个制服的位置为 0,第二个制服的位置为 1,并且这可能再次发生在工作或他们的机器上。

此外,他们可能会制作一个程序

 uniform float a;
 uniform float b;

还有另一个程序

 uniform float a;
 uniform float b;
 uniform float c;

并假设第一个程序中的

a
b
的位置在第二个程序中是相同的,并且这可能恰好在他们的机器上工作,但在其他地方不起作用。

但是,网络的设计是,理想情况下,您应该能够编写一次代码并让它在所有设备上的任何地方运行,至少尽可能地如此。这就是为什么 WebGL 决定将这些整数位置包装在不透明对象中并跟踪它们来自哪个程序。防止程序员用位置做不可移植的事情。

现在你问,好吧,所以我们有一个不透明的位置对象,并且该对象知道它来自哪个程序,那么为什么它不能尽一切努力在正确的程序上设置正确的制服。

原因是,WebGL 试图尽可能接近 OpenGL ES 2.0。这使得将 OpenGL ES 代码从其他语言移植到 Web 中变得相对容易。例如,Unity 能够导出到网页,这主要是通过他们已经为移动设备编写的 OpenGL ES 支持实现的。

将这些函数的功能更改为与 OpenGL ES 中的功能不同会破坏这种可移植性。

如果你真的想实现这个,那么

gl.uniformXXX
必须在内部执行此操作。

假设

WebGLUniformLocation
是这样的。 实际上由于原因更复杂,但为了保持简单

 class WebGLUniformLocation {
  public:
   int location;   // the location as returned from `glGetUniformLocation`
   int program;    // the program as from `gl.createProgram`
 };

然后,要随时为任何程序设置制服而不必先调用

gl.useProgram
,您需要这样的代码

 void uniform1f(const WebGLUnformLocation& loc, float v) {

   // save the current program
   int currentProgram;
   glGetIntegerv(GL_CURRENT_PROGRAM, &currentProgram);

   // Set the program to the one needed by loc
   glUseProgram(loc.program);

   // Set the uniform
   glUniform1fv(loc.location, v);

   // Restore the current program
   glUseProgram(currentProgram);
 }

所以现在

uniform1f
调用 4 个函数而不是 1 个。很容易慢 4 倍。您可以添加优化,例如当前程序是否与
loc.program
相同,然后跳过对
glUseProgram
的前 2 次调用。您还可以跟踪当前的节目,这样您就不必调用
glGetIntegerv
。这些都是可以使其接近 1 次调用的优化,但仍然存在必须解决的问题。

例如,如果一个程序是当前程序,并且您重新链接它(调用

glLinkProgram
并且链接失败,则旧程序仍然是当前程序。但是,您不能再使用该值调用
glUseProgram
。这意味着如果有这样的代码,上面的代码会对规范进行不兼容的更改。

glUseProgram(somePrg); // somePrg is good glLinkProgram(somePrg); // this link fails glUniform1f(locationA); // This works, it sets the previous valid somePrg glDrawArrays(); // This works, it uses the previous valid somePrg
但是随着 

gl.uniform1f

 的更改,我们上面建议的上述代码不再根据规范工作

glUseProgram(somePrg); // somePrg is good glLinkProgram(somePrg); // this link fails // glUniform1f(locationA); ---- impl glGetIntegerv(GL_CURRENT_PROGRAM, &currentProgram); // returns somePrg glUseProgram(loc.program); // works glUniform1fv(loc.location, v); // works glUseProgram(currentProgram); // fails!!!!!! somePrg is not // allowed to be set as the // current program because the // link failed glDrawArrays(); // fails, because the current program is // is still `loc.program` on `somePrg`
    
© www.soinside.com 2019 - 2024. All rights reserved.