连接程序(program)用于连接你的shader和OpenGL es程序,并处理两者之间的数据交互。本文通过分析GPUImage库的GLProgram类,整理下相关知识。
1.创建program
GLuint program = glCreateProgram();
该函数将创建一个空的program对象,并返回一个非零的引用标识。program对象可以附加和移除着色器对象,一般包含顶点着色器和片元着色器。program会根据附加的着色器对象创建一些可执行文件,并使之成为当前渲染环境的一部分。
2.附加着色器
- (BOOL)compileShader:(GLuint *)shader
type:(GLenum)type
string:(NSString *)shaderString
{
// CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();
GLint status;
const GLchar *source;
source =
(GLchar *)[shaderString UTF8String];
if (!source)
{
NSLog(@"Failed to load vertex shader");
return NO;
}
*shader = glCreateShader(type);
glShaderSource(*shader, 1, &source, NULL);
glCompileShader(*shader);
glGetShaderiv(*shader, GL_COMPILE_STATUS, &status);
if (status != GL_TRUE)
{
GLint logLength;
glGetShaderiv(*shader, GL_INFO_LOG_LENGTH, &logLength);
if (logLength > 0)
{
GLchar *log = (GLchar *)malloc(logLength);
glGetShaderInfoLog(*shader, logLength, &logLength, log);
if (shader == &vertShader)
{
self.vertexShaderLog = [NSString stringWithFormat:@"%s", log];
}
else
{
self.fragmentShaderLog = [NSString stringWithFormat:@"%s", log];
}
free(log);
}
}
// CFAbsoluteTime linkTime = (CFAbsoluteTimeGetCurrent() - startTime);
// NSLog(@"Compiled in %f ms", linkTime * 1000.0);
return status == GL_TRUE;
}
glCreateShader函数创建一个空的着色器对象,并返回一个非零的引用标识。着色器对象用于维护定义着色器的源代码字符串。可以传入GL_VERTEX_SHADER或GL_FRAGMENT_SHADER来创建顶点着色器和片元着色器。着色器对象和与之关联的数据,可以在共享上下文的环境中共享。
glShaderSource(shader,arrCount,stringArr,lengthArr)将着色器对象的源码指定为目标字符串数组中的源码,着色器对象的原有内容将被完全替换。如果长度数组为null,则假定每个字符串都是空终止的。如果不是null,源代码字符串将不被扫描或解析,只是复制到指定的着色器对象中。OpenGL复制着色器源代码字符串,因此你可以在函数返回后立即释放它。
但是,请注意Shader编译器支持是可选的,如果不确定是否支持,可以使用参数GL_SHADER_COMPILER调用glGet来查询。glShaderSource,glCompileShader,glGetShaderPrecisionFormat和glReleaseShaderCompiler将在shader编译器不支持时,生成GL_INVALID_OPERATION。
glCompileShader会编译已存储在由着色器指定的着色器对象中的源代码字符串,并将编译结果保存。你可以通过glGetShaderiv来查询。无论编译是否成功,有关编译的信息都可以通过调用glGetShaderInfoLog从着色器对象的信息日志中获取。
glGetShaderiv可以查询shader对象的状态信息。参数包括GL_SHADER_TYPE,GL_DELETE_STATUS,GL_COMPILE_STATUS,GL_INFO_LOG_LENGTH
(存储信息日志所需的字符缓冲区大小),GL_SHADER_SOURCE_LENGTH(存储着色器源码所需的字符缓冲区的大小)。
glAttachShader(program, vertShader);
glAttachShader将着色器对象附加到指定的program对象上,以便在连接时生成可执行文件。附加操作在着色器对象生成后,就可以进行。这意味着你可以着色器对象加载源码之前,就将它附加到program对象。
多个相同类型的着色器对象可能不会附加到单个程序对象。但是,单个着色器对象可能会附加到多个程序对象。如果着色器对象在附加到程序对象时被删除,它将被标记为删除,并且直到调用glDetachShader才能将其从它所连接的所有程序对象中分离出来,删除将不会发生。
3.连接program并使用它
glLinkProgram(program);
glGetProgramiv(program, GL_LINK_STATUS, &status);
glUseProgram(program);
glLinkProgram执行链接操作,并保存链接状态。shader将根据源码创建可执行文件,所有与program相关的用户定义的uniform将被初始化为0,并且生成一个可以访问的地址,你可以通过调用glGetUniformLocation来查询。所有未绑定到顶点属性索引的attribute,此时都将被链接器绑定。分配的位置可以通过调用glGetAttribLocation来查询。链接操作之后,program可以自由修改附加的着色器对象,编译附加的着色器对象,分离着色器对象,删除着色器对象以及附加其他着色器对象。
在调用glUseProgram之后(program已在使用中),执行链接操作,如果链接成功,glLinkProgram还会将生成的可执行文件安装为当前渲染状态的一部分;如果链接失败,其链接状态将设置为GL_FALSE,但可执行文件和关联状态将保持为当前状态的一部分,直到随后对glUseProgram的调用将其删除而不再使用为止。从使用中删除后,在成功重新链接之前,它不能成为当前状态的一部分。
由于OpenGL ES着色语言规范中规定的多种原因,以下情况链接可能会失败:顶点着色器和片段着色器都不在程序对象中、已超过实施支持的活动属性变量的数量、统一变量的存储限制已被超出、实施支持的有效统一变量的数量已被超过、顶点着色器或片段着色器缺少主要功能、在片段着色器中实际使用的变量变量在顶点着色器中没有以相同方式声明(或者根本没有声明)、对函数或变量名称的引用未解决、共享的全局声明有两种不同的类型或两种不同的初始值、一个或多个附加的着色器对象尚未成功编译(通过glCompileShader),或者加载了预编译的着色器二进制文件(通过glShaderBinary)、绑定通用属性矩阵会导致矩阵的某些行落在允许的最大值GL_MAX_VERTEX_ATTRIBS之外、找不到足够的连续顶点属性槽来绑定属性矩阵。
glUseProgram设置program为当前渲染状态的一部分。如果program为0,当前渲染状态指向无效的program,任何glDrawArrays或glDrawElements命令都会提示未定义。program对象和与之关联的数据,可以在共享上下文的环境中共享。
- 通过program获取着色器的属性的位置
- (void)addAttribute:(NSString *)attributeName
{
if (![attributes containsObject:attributeName])
{
[attributes addObject:attributeName];
glBindAttribLocation(program,
(GLuint)[attributes indexOfObject:attributeName],
[attributeName UTF8String]);
}
}
// END:addattribute
// START:indexmethods
- (GLuint)attributeIndex:(NSString *)attributeName
{
return (GLuint)[attributes indexOfObject:attributeName];
}
- (GLuint)uniformIndex:(NSString *)uniformName
{
return glGetUniformLocation(program, [uniformName UTF8String]);
}
glBindAttribLocation(program,index,name)用于将由program对象中的用户定义的属性变量与通用顶点属性索引相关联。name为以空结尾的字符串。index指定要绑定到此变量的通用顶点属性索引。该命令使顶点着色器可以为属性变量使用描述性名称,而不是从0到GL_MAX_VERTEX_ATTRIBS -1编号的通用变量。当程序成为当前状态的一部分时,通过通用顶点属性索引提供的值将修改由name指定的用户定义属性变量的值。发送到每个通用属性索引的值是当前状态的一部分,就像标准顶点属性(如颜色,法线和顶点位置)一样。如果通过调用glUseProgram使一个不同的程序对象成为当前对象,那么新program对象中也会绑定到索引的属性到相同的值。
如果name引用矩阵属性变量,则index引用矩阵的第一列。其他矩阵列然后自动绑定到index+n。通过调用glBindAttribLocation,可以随时显式分配程序对象的属性变量名称到通用属性索引绑定。在调用glLinkProgram之前,属性绑定不会生效。程序对象成功链接后,通用属性的索引值保持不变,直到下一次执行链接命令。应用程序不允许使用此命令绑定任何标准OpenGL顶点属性,因为它们在需要时会自动绑定。在程序对象链接后发生的任何属性绑定将在下次链接程序对象时才生效。
如果name之前被绑定,再次绑定时,以前的绑定信息将丢失。因此,不能将一个用户定义的属性变量绑定到多个索引,但可以将多个用户定义的属性变量绑定到同一个索引。
glGetUniformLocation返回program对象内指定名称的uniform的位置,名称必须是不包含空格的空终止字符串。如果uniform不存在,或者名称以"gl"开头,则返回-1;在program成功链接之前,分配给统一变量的实际位置是未知的。发生链接后,可以使用命令glGetUniformLocation来获取统一变量的位置。然后可以将此位置值传递给glUniform以设置统一变量的值或glGetUniform以查询统一变量的当前值。程序对象成功链接后,统一变量的索引值保持固定,直到发生下一个链接命令。如果链接成功,则只能在链接后查询统一的变量位置和值。