GPUImage的核心是图片的处理和渲染,而这个过程主要是由OpenGL ES实现的。但是OpenGL ES是一个C API的集合,用起来非常不方便。GLProgram就提供了一个建立OpenGL ES program的面向对象封装。
一个OpenGL ES Program的创建过程主要分为以下步骤
- 创建一个EGLContext并设置成当前的Context
- 创建Program
- 创建VertexShader和FragmentShader
- Link Program
- 确保Program的正确性
- 使用Program
创建EGLContext
在OpenGL中,对于同一个Program的渲染必须发生在同一个EGLContext上。所以在整个OpenGL渲染过程中,我们需要先创建一个EGLContext并设置为currentContext。
在iOS中,创建EGLContext相对比较容易,因为已经有一个系统自带的EAGLContext类将EGL都封装好了,只需要调用其initializer并且将这个Context设置成currentContext
即可:
EAGLContext *context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
[EAGLContext setCurrentContext:_context];
而在安卓中,则创建Context要麻烦许多。因为安卓要求所有的渲染必须发生在同一个Context以及同一个线程中。如果不使用默认的GLSurfaceView进行渲染的话,则需要更复杂的创建Context的过程:
int[] attributes = new int[] {
EGL10.EGL_RED_SIZE, 8, //指定RGB中的R大小(bits)
EGL10.EGL_GREEN_SIZE, 8, //指定G大小
EGL10.EGL_BLUE_SIZE, 8, //指定B大小
EGL10.EGL_ALPHA_SIZE, 8, //指定Alpha大小,以上四项实际上指定了像素格式
EGL10.EGL_DEPTH_SIZE, 16, //指定深度缓存(Z Buffer)大小
EGL10.EGL_RENDERABLE_TYPE, 4, //指定渲染api类别, 如上一小节描述,这里或者是硬编码的4,或者是EGL14.EGL_OPENGL_ES2_BIT
EGL10.EGL_NONE }; //总是以EGL10.EGL_NONE结尾
public DHImageContext() {
int version[] = new int[2];
int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
mEGL = (EGL10)EGLContext.getEGL();
mEGLDisplay = mEGL.eglGetDisplay(EGL_DEFAULT_DISPLAY);
mEGL.eglInitialize(mEGLDisplay, version);
mEGLConfig = chooseConfig();
int[] attrib_list = {
EGL_CONTEXT_CLIENT_VERSION, 2,
EGL10.EGL_NONE
};
mEGLContext = mEGL.eglCreateContext(mEGLDisplay, mEGLConfig, EGL_NO_CONTEXT, attrib_list);
mGL = (GL10)mEGLContext.getGL();
frameBufferCache = new DHImageFrameBufferCache();
}
private EGLConfig chooseConfig() {
int[] attribList = new int[] {
EGL_DEPTH_SIZE, 0,
EGL_STENCIL_SIZE, 0,
EGL_RED_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_BLUE_SIZE, 8,
EGL_ALPHA_SIZE, 8,
EGL10.EGL_RENDERABLE_TYPE, 4,
EGL_NONE
};
int[] numConfig = new int[1];
mEGL.eglChooseConfig(mEGLDisplay, attribList, null, 0, numConfig);
int configSize = numConfig[0];
mEGLConfigs = new EGLConfig[configSize];
mEGL.eglChooseConfig(mEGLDisplay, attribList, mEGLConfigs, configSize, numConfig);
return mEGLConfigs[0]; // Best match is probably the first configuration
}
创建GLProgram
创建GLProgram
包括了上述的步骤2-步骤5。GLProgram
的initializer有多个:
- (id)initWithVertexShaderString:(NSString *)vShaderString
fragmentShaderString:(NSString *)fShaderString;
- (id)initWithVertexShaderString:(NSString *)vShaderString
fragmentShaderFilename:(NSString *)fShaderFilename;
- (id)initWithVertexShaderFilename:(NSString *)vShaderFilename
fragmentShaderFilename:(NSString *)fShaderFilename;
其实他们最终都是传入了两个参数,即Vertex Shader以及Fragment Shader。有了这两个Shader之后,我们就可以开始创建OpenGL ES的Program了。
- 创建program:创建一个OpenGL ES Program非常简单,只需要一个命令即可:
program = glCreateProgram();
- 创建Shader:分别根据两个Shader String来创建两个Shader。但是要注意区别的是,两个Shader的type对应的GLEnum是不一样的。
创建并且compile shader的过程包括几步:
- 创建OpenGL ES Shader:VertexShader的type是
GL_VERTEX_SHADER
;而FragmentShader是GL_FRAGMENT_SHADER
。
shader = glCreateShader(type);
- 加载Source String:
source = (GLchar *)[shaderString UTF8String];
glShaderSource(*shader, 1, &source, NULL);
- 编译Shader:
glCompileShader(*shader);
- 检查Shader的状态;如果创建失败,则获取log:
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);
}
}
- 将生成的两个Shader Attach到Program上:
glAttachShader(program, vertShader);
glAttachShader(program, fragShader);
- link program并且检查program的状态,如果link失败,则获取log;如果link成功,则表示GLProgram的初始化完毕:
glLinkProgram(program);
glGetProgramiv(program, GL_LINK_STATUS, &status);
if (status == GL_FALSE)
return NO;
if (vertShader)
{
glDeleteShader(vertShader);
vertShader = 0;
}
if (fragShader)
{
glDeleteShader(fragShader);
fragShader = 0;
}
self.initialized = YES;
Attribute的管理
在GPUImage中,实际上attribute的index并不是真正从program中获取到的,而是直接通过默认的顺序进行排序的。因为我们可以真正通过使用glBindAttribLocation
和glEnableVertexAttribArray
来控制每个Attribute的位置。
因此,如果需要写一个Filter的子类,需要先调用父类中的addAttributes方法,才能够保证正确的Attribute index。
- (void)addAttribute:(NSString *)attributeName
{
if (![attributes containsObject:attributeName])
{
[attributes addObject:attributeName];
glBindAttribLocation(program,
(GLuint)[attributes indexOfObject:attributeName],
[attributeName UTF8String]);
}
}
- (GLuint)attributeIndex:(NSString *)attributeName
{
return (GLuint)[attributes indexOfObject:attributeName];
}
在GPUImageFilter中,有两个attributes的位置是固定的:
- position的index为0;
- inputTextureCoordinate的index为1;
如果有多个input的话,则分别为inputTextureCoordinatei = i-1;
Uniform的管理
在OpenGL ES中,uniform的位置并不是固定的,必须在Program link完成之后才能够获得,因此,获取uniform的位置必须通过以下命令:
glGetUniformLocation(program, [uniformName UTF8String]);
使用OpenGL ES Program
由于在渲染过程中,很可能添加了多个Program,因此,很可能出现的情况就是需要在不同的Program之间进行切换。切换的命令很简单:
- (void)use
{
glUseProgram(program);
}
一旦调用了这个方法,就会将当前的program设置为正在使用中的,然后进行渲染。
默认的Shader
由于GPUImage默认是进行图片处理,其实它的VertexShader就相对简单很多,因此,GPUImage提供了一个默认的VertexShader:
attribute vec4 position;
attribute vec4 inputTextureCoordinate;
varying vec2 textureCoordinate;
void main()
{
gl_Position = position;
textureCoordinate = inputTextureCoordinate.xy;
}
同时,也提供了一个不进行任何像素操作的PassThrough FragmentShader:
varying highp vec2 textureCoordinate;
uniform sampler2D inputImageTexture;
void main()
{
gl_FragColor = texture2D(inputImageTexture, textureCoordinate);
}
GLProgram主要是提供了对OpenGL ES program创建以及使用的面向对象化封装。真正进行渲染的Shader都是由不同的Filter自己提供的。在了解了GLProgram之后,我们就可以开始正式进入主题GPUImageFilter了。