美文网首页
GPUImage源码解析 -- GLProgram

GPUImage源码解析 -- GLProgram

作者: Danielhhs | 来源:发表于2018-01-16 17:39 被阅读0次

    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

          本文链接:https://www.haomeiwen.com/subject/efksoxtx.html