GPUImage源码解析 -- GLProgram

GPUImage的核心是图片的处理和渲染,而这个过程主要是由OpenGL ES实现的。但是OpenGL ES是一个C API的集合,用起来非常不方便。GLProgram就提供了一个建立OpenGL ES program的面向对象封装。

一个OpenGL ES Program的创建过程主要分为以下步骤

  1. 创建一个EGLContext并设置成当前的Context
  2. 创建Program
  3. 创建VertexShader和FragmentShader
  4. Link Program
  5. 确保Program的正确性
  6. 使用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了。

  1. 创建program:创建一个OpenGL ES Program非常简单,只需要一个命令即可:
program = glCreateProgram();
  1. 创建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);
        }
}
  1. 将生成的两个Shader Attach到Program上:
        glAttachShader(program, vertShader);
        glAttachShader(program, fragShader);
  1. 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中获取到的,而是直接通过默认的顺序进行排序的。因为我们可以真正通过使用glBindAttribLocationglEnableVertexAttribArray来控制每个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了。

你可能感兴趣的:(GPUImage源码解析 -- GLProgram)