上节回顾

在之前的六节中,讲解了 EGL、GLSL 与 OpenGL ES 三个专业术语的概念以及它们的关系,串讲了整个绘制流程;之后分别讲解了 EGL 主要 API 的用处,以及 GLSL 的主要语法。现在,对 EGL 和 GLSL 有了比较全面的了解,那从这一节开始, 会根据 OpenGL ES 与 EGL 和 GLSL 的关系,按照绘制图片的整个流程,对 OpenGL ES 进行详细讲解。

之前提过 OpenGL ES 其实就是一个图形学库,由 109 个 API 组成,只要明白了这 109 个 API 的意义和用途,就掌握了 OpenGL ES。那么从这一节开始,将主要对 OpenGL ES API 进行详细介绍。


OpenGL ES 中的 Shader

通过 EGL、GLSL、OpenGL ES 的关系,可以看出 EGL 就是为绘制做准备工作的,比如关联一块显示屏,初始化 GPU 信息,准备一块绘制 buffer 等。EGL 做的事情相对独立,只有当 EGL 的 API 被执行完毕,准备工作做好之后,才轮到 OpenGL ES 和 GLSL 出场。所以 EGL 与 GLSL 和 OpenGL ES 没有特别多的交互。而 GLSL 主要就是书写一个个的 Shader,这些 Shader 将在 OpenGL ES 的 pipeline 中被调用使 用。所以,GLSL 与 OpenGL ES 存在着大量的交互,那么就先来说明,如何通过 OpenGL ES 的 API 指定 OpenGL ES 将要使用的 Shader。

Shader,在 OpenGL ES 中,就好比一块内存,这块内存由 OpenGL ES 创建, 使用 GLSL 语言书写的内容将其填充,然后由 OpenGL ES 触发 GPU 的 Shader 编译器对其进行编译,然后编译好的 Shader 就可以由 OpenGL ES 控制其输入,然后经过 Shader 生成的结果,也就是上一节说到的 Shader 中内置变量算出的那些结果,会被 OpenGL ES pipeline 的后半段操作所使用,最终结果会被保存在绘制 buffer 中。OpenGL ES 中 Shader 的操作,大概流程就是这样的,下面来详细介绍 一下 OpenGL ES 中对应的这些 API。


OpenGL ES API 详解

GLenum glGetError(void);

先说明一下,只有当 EGL 给 GL 创建好环境,也就是创建好一套适合 GL API 运行的 surface 和 context,并将它们 makecurrent 之后,GL 的 API 才可以被正常 调用。

当调用 GL 的 API 的时候,大部分 API 可以直接通过返回值,判断这个 API 执行的成功还是失败。如果是成功,那么情况只有一种,但是如果失败,则失败的可能性则有千万种,所以不可能只是简单的通过返回值获取失败的原因。所以, 还需要更详细的信息来判断为什么失败:是传入参数有误,还是发生了别的冲突之类的情况。glGetError 这个函数就提供了这个功能,每种错误的可能,都会被分配一个代码号。而这个 API 就是用于返回当前 thread 如果 GL 的 API 出错的话, 最近一个错误所对应的错误代码。

这个 API 功能很强大,而且经常被广泛使用,但是过多的错误检测会负面的影响到一个无错误程序的性能,所以尽量只在一些关键的地方使用这个 API。

这个函数的输入为空。因为这个 API 是针对目前结果进行判断的,所以不需要任何输入。在 GPU driver 中,当执行 GL API 的时候,如果出错了,会将错误代码号对应的标志位进行设置,然后当调用 glGetError 的时候,通过获取标志位的信息,可以获取到对应的代码号,所以不需要任何输入。然后该标志位就会被清零,如果以后再出现错误,标志位再被重设。

这个函数的输出是可以用来判断详细错误信息的错误代码。比如当返回 GL_NO_ERROR 的时候,说明自上次 glGetError 的执行以来,或者 GL 被初始化到目前为止,所有的 GL API 都运行正常,没有出错。如果返回其它标记位,就说明,在两次 glGetError 之间,或者 GL 被初始化之后, 调用的 GL API 出现了错误。

有一种特殊情况,假如在调用这个 API 之前,出现过不止一次错误,那么调用这个 API 获取的将是最近一次错误的错误代码,并将该错误代码的标记重置。 然后再调用一次,获取的将是倒数第二次错误的错误代码,以此类推,直至所有被标记的错误代码全部被重置后,再调用这个 API,则返回 GL_NO_ERROR。 除了 GL_NO_ERROR 之外还有 5 个错误代码,标志着 5 种错误情况,这 5 种情况等说到对应 API 的时候再进行具体的解释说明。

那么当 GL API 出错了之后,下面执行的 GL 命令是否会被继续正常执行呢?

某种意义上可以认为,只有当出现了内存不够的情况的时候,也就是 glGetError 返回 GL_OUT_OF_MEMORY 的时候,剩下的 GL 操作的结果会被认为是 undefine。 而其他情况下,剩余的 API 应该会忽略这些错误,继续正常执行,但这个也需要 case by case 的看待,比如在创建 buffer 的 API 出错,buffer 没有被创建成功,那么下面当对这个 buffer 进行赋值的 API 的时候,肯定也是会出错的。

GLuint glCreateShader(GLenum shaderType);

OpenGL ES 中需要使用到 Shader,比如 OpenGL ES 会通过 API 给 Shader 传递传入参数,也会触发 GPU 去编译 Shader,那么首先,需要通过 API 创建一个 Shader 的 handle,然后把 GLSL 书写的 shader source 传给这个 shader handle,然后才能触发 GPU 去编译 shader 等一系列的操作,而 glCreateShader 这个 API,就是用于创建一个 shader 的 handle。

这个函数的输入为 shader type,指定了哪种 shader object 被创建,比如创建了一个 Vertex shader,或者是创建了一个 fragment shader。这里的 shader type 输入,只支持 GL_VERTEX_SHADER 和 GL_FRAGMENT_SHADER,如果使用其他 token,那么就会报告 GL_INVALID_ENUM 的错误。在这里解释一下 GL_INVALID_ENUM,出现这个错误代码,说明原本需要输入某些特定的 enum 参数,但是却输入了其他的不支持的参数,这样就会报这个错误。而这种错误可以被忽略,除了这个 API 要执行的操作没有成功执行,以及 GPU driver 中被打上了标记之外,其他方面不会产生任何影响。

这个函数的输出是一个非负数字,用于指定返回的 shader object,当创建失败的话,返回 0。这个 shader object 就是我们刚才说的 shader 的 handle,用于存放构建 shader 所使用的 shader source。之后使用 shader 的时候,就可以通过这个非 0 的名字来访问对应的 shader。刚被创建的 shader object 为空。

shader 使用的 namespace 和之后我们要介绍的 texture、program 一致,可以被多个共享的 context 所共享。当这些东西被 share 的时候,它们的内容也是会被共享的。也就会出现了一个 context 创建 shader,然后被另外一个 share_context 去使用的情况。可以想象到,当 share 这些东西的时候,需要开发者自己注意数据读写顺序的问题。

void glShaderSource(GLuint shader, GLsizei count, const GLchar **string, const GLint *length);

当一个 Shader Object 被创建之后,初始为空的,里面没有内容,那么需要先把 GLSL 书写的内容输入到这个 Shader Object 中,那么这个 glShaderSource 的 API,就是往一个 shader 的 handle 中传递 shader source。

这个函数输入的第一个参数是 Shader Object,也就是刚才通过 glCreateShader 创建,所得到的那个非 0 值。比如刚才通过 glCreateShader 创建了一个 Shader,名字为 1,那么在这里就要传入 1。假如传入的并非是一个合法的 Shader Object,比如传入一个 2,或者传入一个别的值,就会出现 INVALID_VALUE 的 error。在这里解释一下 GL_INVALID_VALUE,出现这个错误代码,说明原本需要输入某些特定的数值参数,但是却输入了其他的不支持的参数,这样就会报这个错误。而这种错误可以被忽略,除了这个 API 要执行的操作没有成功执行,以及 GPU driver 中被打上了标记之外,其他方面不会产生任何影响。

第二个参数和第三个参数的意思是:string 是一个由 count 个指针元素组成的数组,每个数组元素都是一个无终结的字符串。那么也就是说,可以把一个完整的 GLSL 书写的 shader source 写在一个字符串中,那么 count 就为 1,string 就是一个只有一个指针元素的数组。也可以把一个完整的 GLSL 书写的 shader source 写在多个字符串中,那么 count 就不是 1,string 就是由多个指针元素组成的数组。其实由于 GLSL 书写的 shader source 有很多格式化的内容,比如之前在说 GLSL 语法的时候就说到,fragment shader 中需要指明 float 的默认 precision,比如: precision lowp float; 。像这种内容基本每个 fragment shader 都需要写一遍,那么就可以把它拿出来,当在一个程序中需要创建多个 shader 的时候,就可以在传 shader source 的时候,将每个 shader 的 shader source 都分成 2 个或者 2 个以上 的字符串,其中一个字符串就是用于保存这些格式化的内容,这样就不需要在代码中多次创建和书写冗余的字符串了。这种编程技巧基本一直在使用。GPU 会在执行这个 API 的时候把 shader source copy 过去,也就是说,当执行完这个 API之后,string 这个用于保存 shader source 的字符串数组就可以被 free 了。 第四个参数也是一个数组,它对应于 string,用于限制 string 数组中对应的数组元素的字符串的长度。如果 count 为 2,那么 string 中也就有 2 个数组元素, 也就对应于 2 个字符串,那么 length 这个数组也应该有 2 个元素,每个元素都是一个数值,用于指定对应的 string 中的两个字符串的长度。假如数值为负,那么对应的字符串的长度为无限长,如果为 NULL,那么 string 中所有的字符串的长度都是无限长。

现在已经知道了这个命令是将 shader object 中的内容设置为 string 中的内容,但是假如这个 shader object 本身就有内容,那么原来的内容会被覆盖掉。在这里,string 里的内容是会直接被 copy 到 shader object 中的,不会经过 scan 或 者 parse 等其他检查操作。所以默认会认为 string 中的内容组合成的 shader source 是一段由 GLSL 书写的正确的 shader source。而假如其中有错误,那么在这里不会被发现,而在之后编译 shader 或者其他操作中才会被发现。

需要注意的是,有可能某些 GPU 并不支持 shader compiler,那么可以通过 glGet 这个 API,输入 GL_SHADER_COMPILER 这个参数来查询,假如 GPU 不支持 shadercompiler 的 话 , 那 么 glshadersource 、 glCompilerShader 、 glGetShaderPrecisionFormat 、 glReleaseShaderCompiler 会报 GL_INVALID_OPERATION 的错误。在这种情况下,虽然 GPU 不支持 shadercompiler, 而 shader 又是必不可少的一步,那么这种 GPU 一般都会支持 glShaderBinary,可以支持 load 一个已经编译好的 shader binary,这个 API 我们一会再说。这里再解释一下 GL_INVALID_OPERATION,出现这个错误代码,说明在当前状态下该操作不被允许,刻意的非要执行的话,就会报这个错误。而这种错误可以被忽略,除了这个 API 要执行的操作没有成功执行,以及 GPU driver 中被打上了标记之外, 其他方面不会产生任何影响。

这个函数没有输出,但是有以下几种情况会出错,除了刚才说的传入的 shader object 不是一个合法的 shader 会报 INVALID_VALUE 的错,以及如果 GPU 不支持 shadercompiler,执行指定的那几个 API 会报 GL_INVALID_OPERATION 之外, 还有如果函数的第二个参数 count 小于 0,则出现 INVALID_VALUE 的 error。 INVALID_VALUE 我们刚才已经解释过了,以上那些已经解释过的 error,在之后就不做重复解释了。

void glCompileShader(GLuint shader);

一个程序必须编译之后才能运行,shader 也是。区别在于,一般的程序是在 CPU 进行编译,而 Shader 则是在 GPU 中进行编译的。那么 glCompileShader 这个 API,就是把一个已经包含 shader source 内容的 shader 发给 GPU 进行编译。

这个函数的输入参数也是 Shader Object,和 glShaderSource 的第一个参数一样,也就是刚才通过 glCreateShader 创建所得到的那个非 0 值。同样的,如果我们传入一个不合法的 shader object 的数值,就会出现 INVALID_VALUE 的 error。

这个函数没有输出参数。但是每个 shader object 都有一个布尔值 Compile_status,这个值会根据编译的结果进行修改,比如 shader 编译成功没有问题且可以使用,那么这个状态将会被设为 TRUE,否则则为 FALSE。这个 status 值可以通过 GetShaderiv 这个 API 查询。根据学过的 GLSL 语法,编译失败的原因有很多,在这里就不详细进行一一说明了。如果编译失败,则之前编译的所有信息都将丢失。也就是说编译失败之后,该 shader 的状态不会回滚到编译之前的旧的状态。通过 glshadersource 改变 shader object 的内容,并不会改变其编译状态或编译出来的 shader code。只有当再执行一次 glCompileShader,且编译成功, 才会改变该 shader 的编译状态。

每个 shader object 也都有一个 information log,是一个 string,每次编译都会被重写,这个 log 包含很多编译信息,且可以通过 glGetShaderInfoLog 查看。

void glShaderBinary(GLsizei n, const GLuint *shaders, GLenum binaryformat, const void *binary, GLsizei length);

预编译的 shader binary 可以通过 glShaderBinary 这个 API 传给 Shader object, 也就是刚才说的如果 GPU 不支持 compileShader 的话,可以通过这个 API 把 shader binary 传给 shader object。这样会比较省时,节约了 GPU 编译 shader 的时间。 这个函数输入的第一个参数和第二个参数的意思是:shaders 是包含了 n 个 shader object 的数组,每个 shader object 都有自己的类型,比如是 vertex shader 或者是 fragment shader。但是如果 shader 中存放了两个相同类型的 shader,比如存放了两个 vertex shader 的 object,那么就会出现 INVALID_OPERATION 的错误。 第三个参数 binaryformat 指出了预编译 shader 的格式,到时候 shader 会根据 binaryformat 来进行解码,解码后才类似于一个编译好之后的 shader,然后交给 GPU 使用。第四个参数 binary 是一个指向预编译的 shader binary 的指针,第五个参数 length 限定了这个 shader binary 的长度。GPU 支持的 shaderbinary 格式可以由参数 NUM_SHADER_BINARY_FORMATS 和 SHADER_BINARY_FORMATS 查询到, 第一个是用于查询支持的 shader binaryformat 的数量,第二个是查询到具体哪些 shader binaryformat 所被支持。根据 shader 的类型,shaderbinary 会读取 binary vertex 或者 fragment shader。又或者是一个可执行的二进制文件,其中包含了一个优化好的 vertex shader 和 fragment shader 的组合。

这个函数没有输出参数。但是有以下几种情况会出错,除了刚才说到的 shaders 中不能保存两个或者两个以上相同类型的 shader,否则会出现 INVALID_OPERATION 之外,还有比如第一个参数 n 或者最后一个参数 length 为负, 则报INVALID_VALUE,如果第二个参数shaders中保存的shader object不是一个合法的 shader 会报 INVALID_VALUE 的错。第三个参数 binaryformat 如果不被支持, 也就是说不存在于 SHADER_BINARY_FORMATS 之中,那么会出现 INVALID_ENUM 错误。比如第四个参数 binary 指向的数据不符合第三个参数的 binaryformat,那 么会出现 INVALID_VALUE 的错误。并且,可能根据不同的 binaryformat 的定义, 还会出现不同的错误。

如果 glShaderBinary 失败,那么该 shader 的状态不会回滚到编译之前的旧的状态。

需要注意的是,如果 GPU 支持 ShaderBinary 这种机制,那么要求如果 shaderbinary是一组被优化了的vertexshader和fragment shaderbinary,它们必须被用在一起。这种限制一般会写在 binaryformat 的定义文档中。

需要注意的是,有可能某些 GPU 并不支持 glShaderBinary 这种机制,据我所知,这种 GPU 不在少数,那么可以通过 glGet 这个 API,输入 NUM_SHADER_BINARY_FORMATS 和 SHADER_BINARY_FORMATS 这两个参数来查询,假如 GPU 不支持 glShaderBinary 的话,那么执行 glShaderBinary 这个 API 会报 GL_INVALID_OPERATION 的错误。在这种情况下,虽然 GPU 不支持 glShaderBinary,而 shader 又是必不可少的一步,那么这种 GPU 一般都会支持 glShaderSource 和 glCompileShader 那种机制。

GPU 会在我们执行这个 API 的时候就把 shader binary copy 过去了,也就是说, 当执行完这个 API 之后,binary 这个用于保存 shader binary 的指针就可以被 free 了。

GLuint glCreateProgram(void);

在一个完整的 OpenGL ES 的 pipeline 中,需要使用两个不同类型的 shader, 一个是 vertex shader,用于处理顶点坐标,一个是 fragment shader,用于处理顶点颜色。可以在代码中创建无数个 shader,但是最终交给 GPU 真正使用的,一次只能是一组两个不同类型的 shader。这两个 shader 将被放在一个叫做 program 的 object 中,然后把 program object 交给 GPU。而 glCreateProgram 就是用于创 建一个 program object 的。

这个函数没有输入参数。

这个函数的输出是一个非负整数,用于指定返回的 program object,当创建失败的话,返回 0。之后使用 program 的时候,就可以通过这个非 0 的名字来访问对应的 program。刚被创建的 program object 为空。

刚才也说过了,program object 和 shader object 使用的是同一个命名空间, 也就是可以被多个共享的 context 所共享。当被 share 的时候,它们的内容也是会被共享的。

void glAttachShader(GLuint program,GLuint shader);

刚才说需要把两个不同类型的 shader 放在一个 program 中之后,program 才能被交给 GPU,那么 glAttachShader 这个 API,就是把一个 shader 关联到该 program object 上。

这个函数的第一个输入参数为 program object,也就是刚才通过 glCreateProgram 创建,所得到的那个非 0 值。第二个输入参数为 shader object, 也就是通过 glCreateShader 创建,所得到的那个非 0 值。如果传入一个不合法的 program object 数值或者 shader object 数值,就会出现 INVALID_VALUE 的 error。 需要注意的是,在这里被传入的 shader object,在执行这个 API 的时候,可以还没有被赋予其 shadersource,也可以在该 shader 没有被编译之前。一个 shader object 可以被 attach 到多个 program 上。

这个函数没有输出参数,但是有以下几种情况会出错,除了刚才说的传入的shader object 不是一个合法的 shader,或者 program object 不是一个合法的 program 会报 INVALID_VALUE 的错之外,如果一个 shader object 已经被关联到某个 program 上之后,再将其重复关联到那个 program 上,就会出现 INVALID_OPERATION 的错误。或者如果一个 program 上已经关联了某种类型的 shader,那么再在该 program 上关联一个相同类型的 shader 的话也会出现 INVALID_OPERATION 的错误。

void glDetachShader(GLuint program, GLuint shader);

刚才说了如果一个 program 上 attach 了一个某种类型的 shader,再 attach 上一个相同类型的 shader 的时候就会报错,那么为了能进行同种类型 shader 的 替换,所以有了 glDetachShader 这个操作,这个 API 就是把该 shader 从该 program 上进行解绑,此后该 shader 和该 program 就不再有关系了,而这个时候这个 program 就可以 attach 其他该类型的 shader 了。

这个函数的输入参数和 glAttachShader 一样,第一个输入参数为 program object,第二个输入参数为 shader object,如果我们传入一个不合法的 program object 数值或者 shader object 数值,就会出现 INVALID_VALUE 的 error。

这个函数也没有输出参数,但是有以下几种情况会出错,除了刚才说的传入 的 shader object 不是一个合法的 shader,或者 program object 不是一个合法的 program 会报 INVALID_VALUE 的错之外,如果该 shader object 原本就没有被 attach 到该 program 上的话,强行执行这个 API,就会报 INVALID_OPERATION 的错误。

另外需要注意的是:

如果一个 shader 已经被 attach 到 program 上了,然后删除该 shader,那么不会立即删除该 shader,而是对该 shader 做一个标记,当这个 shader 从 program 上 detach,且确保该 shader 没有 attach 到其他 program 上之后,删除操作才会被执行。

void glLinkProgram(GLuint program);

program 交给 GPU 使用的时候,需要上面 attach 了一个 vertex shader 和一个 fragment shader,然而可以通过 attachShader 和 detachShader 不停的往 program 中放入和取出 shader,那么 glLinkProgram 这个 API,就好比把 program 封住,使得其中的 vertex shader 和 fragment shader 组成一对,形成一个新的完整的个体。

这个函数的输入参数为 program object,如果传入一个不合法的 program object 数值,就会出现 INVALID_VALUE 的 error。

这个函数没有输出参数。但是每个 program object 都有一个布尔值 Link_status,这个值会根据 link 的结果进行修改,比如 program 链接成功,且一个有效的可执行文件被创建,那么这个状态将会被设为 TRUE,否则则为 FALSE。 这个 status 值可以通过 glGetProgramiv 这个 API 查询。根据我们学过的 GLSL 语法, 链接失败的原因有很多,比如 program 中的 shader object 没有被成功编译,比如 program 中没有 vertex shader 或者 fragment shader,比如 shader 中使用了超出限制的 uniform 或 sample 变量,比如 shader object 是通过预编译的 shader binary 读取生成的等等,在这里就不详细进行一一说明了。如果 link 失败,则之前 link 的所有信息都将丢失。也就是说链接失败之后,该 program 的状态不会回滚到链接之前的旧的状态。而有一些信息还是能被找回来的,这些信息是 attribute 和 uniform 相关的信息,这个下个课时我们再详细说明。

每个 program object 也都有一个 information log,每次 link 都会被重写,这个 log 包含很多链接信息,且可以通过 glGetProgramInfoLog 查看。

link 成功之后,所有 shader 中开发者自定义的 active 的 uniform 都会被初始化为 0,然后会被分配一个地址,该地址可以通过 glGetUniformLocation 这个 API 来获取。同样的,shader 中所有开发者自定义的 active 的 attribute,如果没有被于一个指定的 index 绑定,在这个时候就会给它分配一个 index。这个 index 可以 通过 glGetAttribLocation 这个 API 来获取,该两个 API 我们会在下个课时进行详细说明。

当 program 被 link 之后,该 program 对应的 shader 可以被修改、重新编译、 detach、attach 其他 shader 等操作,而这些操作不会影响 link 的 log 以及 program 的可执行文件。

void glUseProgram(GLuint program);

当一个 program 被成功链接之后,也就说明了该 program 已经准备就绪,可以被使用了,一个程序中可以有很多链接好的 program,但是同一时间只能有一个 program 被使用,那么 glUseProgram 这个 API 就是指定了哪个 program 会被使用,也就是把该 program 的可执行文件当作当前 rendering state 的一部分。

这个函数的输入参数为 program object,一个合法的 program object 为非 0, 那么假如传入 0,也不会出错,但是当前的 rendering state 则使用的是一个不合法的 program,如果执行绘制命令,则会出现 undefine 的结果。而如果传入了不是一个合法的 program,也不是 0 的话,则出现 INVALID_VALUE 的错误。如果传入的 program 还没有被成功 link,则出现 INVALID_OPERATION 的错误。而当前的 rendering state 不会做任何更改。

这个函数没有输出参数。当一个合法且 link 成功的 program 被 use 之后,该 program 对应的 shader 可以被修改、重新编译、detach、attach 其他 shader 等操 作,而这些操作不会影响 link 的 log 以及 program 的可执行文件。如果被使用的 program 被 relink 成功,则该 program 新生成的可执行文件会替换掉当前 rendering state 中使用的可执行文件。如果被使用的 program 被 relink 失败,那么该 program 的 linkstatus 会被设置为 false,而 program 依然可以被使用而且当前 rendering state 中的可执行文件依然是之前可用的那个可执行文件,直到另外一个 program 被 use。当它被替换掉之后,除非是再 relink 成功,否则将不再能被 use。

void glDeleteProgram(GLuint program);

当 program 不再被需要的时候,则可以通过 glDeleteProgram 这个 API 把 program 删除。

这个函数的输入参数为 program object,可以传入 0,传入 0 的时候,这个命令会被忽略掉,如果我们传入的不是一个合法的 program object 数值或者 0 的时候,就会出现 INVALID_VALUE 的 error。

这个函数没有输出参数。如果 program 不是任何 GL Context 的当前 program, 也就是没有被任何 GL Context use,则它会被立即删除。否则的话,该 program 会被做一个标记,当这个 program 不再被任何 GL Context use 的时候,该 program 的删除操作才会被执行。可以通过 glGetProgramiv 这个 API,传入参数 GL_DELETE_STATUS 这个参数,查询该 program 是否被打上 delete 的标记。当 program 被删除之后,所有的 shader object 都会被 detach。

void glDeleteShader(GLuint shader);

当 shader 不再被需要的时候,则可以通过 glDeleteShader 这个 API 把 shader 删除。

这个函数的输入参数为 shader object,可以传入 0,传入 0 的时候,这个命令会被忽略掉,如果我们传入的不是一个合法的 shader object 数值的时候,就会出现 INVALID_VALUE 的 error。

这个函数没有输出参数。刚才在说 detach shader 的时候已经说了,如果一个 shader 已经被 attach 到 program 上了,然后删除该 shader,那么不会立即删除该 shader,而是对该 shader 做一个标记,当这个 shader 从 program 上 detach, 且确保该 shader 没有 attach 到其他 program 上之后,删除操作才会被执行。可以通过 glGetShaderiv 这个 API,传入参数 GL_DELETE_STATUS 这个参数,查询该 shader 是否被打上 delete 的标记。

void glReleaseShaderCompiler(void);

释放 ShaderCompiler 相关的资源。当预计之后都不会在编译 shader,或者最起码一段时间之内不会再编译 shader 了,那么就可以把 shader 编译器相关的资源释放掉,然后把这些资源拿去更有效的利用起来。

这个函数没有输入参数。

这个函数也没有输出参数。但是假如在执行了这个 API 之后,我们又需要使用 glCompileShader 这个 API 去编译 shader,那么 shader 编译器就会被重新组装起来去编译 shader,就仿佛没有被释放掉一样。刚才也说了,当 GPU 不支持 shadercompile 的时候,执行这个 API 会出现 INVALID_OPERATION 的错误。

以上这些 API 就是 OpenGL ES 绑定 shader 的主要 API,除此之外,还有很多 API 是用于查询服务的。比如 glValidateProgram 这个 API 是用于查询 program 是否包含一个可执行文件,且可以被用于当前 renderingstate 的。如果我们 linkprogram 成功的话,这个查询结果就会是正确的,查询结果会放在 program object 的布尔值 GL_VALIDATE_Status 中。再比如 API glGetAttachedShaders 用于获取 program 当前 attach 的 shader。比如 API glGetShaderPrecisionFormat,用于获取指定 shader 类型中某种格式的精度和范围,比如查询 VS 中 high float 的精度和范围。再比如 API glGetShaderSource 用于获取 shader object 中包含的 shader source。比如 API glIsProgram 和 glIsShader 用于查询传入的参数,是否是一个合法的 program 或者 shader。最后,再比如这一节提到过的 glGetProgramInfoLog,glGetShaderInfoLog,glGetProgramiv,glGetShaderiv 用于获取 program 和 shader 的 log,以及其他参数信息,以及 glGet 这个用于获取 GPU 常规信息的 API,在这一节用这个 API 查询了 GPU 是否支持 shadercompiler 和 shaderbinary,在之后的课时里还会使用这个 API 查询更多其他的信息。

本节教程就到此结束,下一节将学习如何通过 OpenGL ES 传入绘制信息,希望大家继续阅读我之后的教程。

谢谢大家,再见!

(3.0)void glGetProgramBinary(GLuint program, GLsizei bufsize, GLsizei *length, GLenum *binaryFormat, void *binary);

用于将已经生成的program生成program binary,这样,在下次使用的时候,就不需要经过上面一系列生成shader、编译shader、link program等操作,而直接通过program binary生成program即可使用。

这个函数一共有5个输入参数,第一个输入参数是指定一个已经生成的program。第二个参数是用于存放binary的第五个参数,所能存放的最大尺寸,如果这个尺寸不够,则会出现 INVALID_OPERATION 的报错。如果顺利生成,binary被保存在第五个参数binary中,相应的尺寸被存在第三个参数length中,其格式也被保存在第四个参数binaryFormat中。如果第三个参数length为NULL,则尺寸就没有保存下来。这里的最后三个参数,在后面使用program binary的时候都会被用上,对应的API为glProgramBinary

这个函数没有输出参数。program binary的尺寸可以通过glGetProgramiv,通过参数 PROGRAM_BINARY_LENGTH 获取到。如果program本身的 LINK_STATUS 为false,则其program binary的length为0,如果通过glGetProgramBinary的时候会出现 INVALID_OPERATION 的错误

(3.0)void glProgramBinary(GLuint program, GLenum binaryFormat, const void *binary, GLsizei length);

将glGetProgramBinary获取到的program binary直接用于生成program,这样就省去了上面一系列生成shader、编译shader、link program等操作。

这个函数一共有4个输入参数,第一个输入参数是指定一个将要被生成的program。第二、三、四参数是通过glGetProgramBinary获取到的,第四个参数length也可以通过glGetProgramiv,通过参数 PROGRAM_BINARY_LENGTH 获取到。如果上述参数有设置不对的地方,该函数就会失败,对应program的 LINK_STATUS 也将被设置为false。

如果硬件或者软件有变化也可能会因为生成program binary的时候使用的是不同版本的编译器,导致program binary load fail。这种情况下只能fallback到老的流程,生成program。

glLinkProgram和glProgramBinary都会更新program的状态,会根据这两个API的结果,将program的 LINK_STATUS 设置为 true或者false。LINK_STATUS可以通过glGetProgramiv查询,对应的log可以通过glGetProgramInfoLog获取。

当这个API执行成功,会将所有的uniform变量设置为默认值。bool的设置为false,其它的设置为0。

当这个API执行成功,之前bind的point就都会恢复了

当这个API执行失败,不会出现错误,但是之前link的数据就都丢了。但是不会影响之前关联的shader,以及attribute的bind等。

OpenGL ES没有指定需要支持什么binary format,可以通过NUM_PROGRAM_BINARY_FORMATS和PROGRAM_BINARY_FORMATS返回当前设置支持的binary format。glGetProgramBinary返回的binary format一定在这个list中。

glGetProgramBinary获取的binary在相同的配置下,一定可以成功被glProgramBinary调用。如果某个GL实现需要recompile,则相应的glGetProgramBinary需要保存足够的信息,以实现recompile。如果需要标记一个program binary需要被retrieve,需要调用glProgramParameteri,将GL_PROGRAM_BINARY_RETRIEVABLE_HINT设置为true。这个设置将在下次glLinkProgram或者glProgramBinary的时候生效。除此之外,还有一些实现,可以在programbinary中包含更多的信息,使得其recompile尽可能最小化,相应的glGetprogramBinary的使用也有些区别。

如果第一个参数program不合法,则会出现 GL_INVALID_OPERATION 的错误

如果第二个参数binaryFormat 不支持,则会出现 GL_INVALID_ENUM 的错误


写在末尾的话

要站在巨人的肩膀上做创新,每一次你写一行新的代码,第一要做的,先想一想你这行代码值得不值得写,是不是有人已经做了同样的工作,可能做得比你还好一点。有没有其他人已经解决这个问题,然后你可以把你的时间放在更好的创新上。

游戏引擎中涉及到太多模块,文件压缩、图片解析、网络支持、物理引擎、音频等,这些都有非常优秀的第三方库,所以多数游戏引擎都会直接使用第三方库,使得其更专注于游戏引擎最核心的渲染模块。作为游戏开发者或者游戏引擎开发者,在游戏引擎中最需要了解的就是渲染引擎部分,而渲染引擎的目的就是将游戏逻辑层上的元素绘制到屏幕上,那么会调用到用于跟手机芯片GPU沟通的底层代码,这块底层代码在端游游戏引擎中为 DirectX(for windows),OpenGL(For Unix),而在手游的游戏引擎中绝大多数为 OpenGL ES,所以学习游戏引擎,一定要掌握 OpenGL ES 。

学习OpenGL ES,可以让我们清楚的直到每个元素是怎样被绘制,知道如何使用它们才能达到最高性能,知道如何使用着色器来增强画面表现力,也更能看清楚所使用引擎的不足之处以及判断其发展趋势。


原创技术文章,撰写不易,转载请注明出处:电子设备中的画家|王烁 于 2017 年 7 月 12 日发表,原文链接(http://geekfaner.com/shineengine/blog8_OpenGLESv2_7.html)