GPUImage源碼解析 -- GLProgram

GPUImage的核心是圖片的處理和渲染镰禾,而這個(gè)過(guò)程主要是由OpenGL ES實(shí)現(xiàn)的象缀。但是OpenGL ES是一個(gè)C API的集合媒佣,用起來(lái)非常不方便跪削。GLProgram就提供了一個(gè)建立OpenGL ES program的面向?qū)ο蠓庋b。

一個(gè)OpenGL ES Program的創(chuàng)建過(guò)程主要分為以下步驟

  1. 創(chuàng)建一個(gè)EGLContext并設(shè)置成當(dāng)前的Context
  2. 創(chuàng)建Program
  3. 創(chuàng)建VertexShader和FragmentShader
  4. Link Program
  5. 確保Program的正確性
  6. 使用Program

創(chuàng)建EGLContext

在OpenGL中迂求,對(duì)于同一個(gè)Program的渲染必須發(fā)生在同一個(gè)EGLContext上碾盐。所以在整個(gè)OpenGL渲染過(guò)程中,我們需要先創(chuàng)建一個(gè)EGLContext并設(shè)置為currentContext揩局。

在iOS中毫玖,創(chuàng)建EGLContext相對(duì)比較容易,因?yàn)橐呀?jīng)有一個(gè)系統(tǒng)自帶的EAGLContext類(lèi)將EGL都封裝好了凌盯,只需要調(diào)用其initializer并且將這個(gè)Context設(shè)置成currentContext即可:

EAGLContext *context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];

[EAGLContext setCurrentContext:_context];

而在安卓中付枫,則創(chuàng)建Context要麻煩許多。因?yàn)榘沧恳笏械匿秩颈仨毎l(fā)生在同一個(gè)Context以及同一個(gè)線程中驰怎。如果不使用默認(rèn)的GLSurfaceView進(jìn)行渲染的話阐滩,則需要更復(fù)雜的創(chuàng)建Context的過(guò)程:

  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大小县忌,以上四項(xiàng)實(shí)際上指定了像素格式
            EGL10.EGL_DEPTH_SIZE, 16, //指定深度緩存(Z Buffer)大小
            EGL10.EGL_RENDERABLE_TYPE, 4, //指定渲染api類(lèi)別, 如上一小節(jié)描述掂榔,這里或者是硬編碼的4,或者是EGL14.EGL_OPENGL_ES2_BIT
            EGL10.EGL_NONE };  //總是以EGL10.EGL_NONE結(jié)尾


    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
    }

創(chuàng)建GLProgram

創(chuàng)建GLProgram包括了上述的步驟2-步驟5症杏。GLProgram的initializer有多個(gè):

- (id)initWithVertexShaderString:(NSString *)vShaderString 
            fragmentShaderString:(NSString *)fShaderString;
- (id)initWithVertexShaderString:(NSString *)vShaderString 
          fragmentShaderFilename:(NSString *)fShaderFilename;
- (id)initWithVertexShaderFilename:(NSString *)vShaderFilename 
            fragmentShaderFilename:(NSString *)fShaderFilename;

其實(shí)他們最終都是傳入了兩個(gè)參數(shù)装获,即Vertex Shader以及Fragment Shader。有了這兩個(gè)Shader之后厉颤,我們就可以開(kāi)始創(chuàng)建OpenGL ES的Program了穴豫。

  1. 創(chuàng)建program:創(chuàng)建一個(gè)OpenGL ES Program非常簡(jiǎn)單,只需要一個(gè)命令即可:
program = glCreateProgram();
  1. 創(chuàng)建Shader:分別根據(jù)兩個(gè)Shader String來(lái)創(chuàng)建兩個(gè)Shader走芋。但是要注意區(qū)別的是绩郎,兩個(gè)Shader的type對(duì)應(yīng)的GLEnum是不一樣的潘鲫。

創(chuàng)建并且compile shader的過(guò)程包括幾步:

  • 創(chuàng)建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的狀態(tài)溉仑;如果創(chuàng)建失敗挖函,則獲取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. 將生成的兩個(gè)Shader Attach到Program上:
        glAttachShader(program, vertShader);
        glAttachShader(program, fragShader);
  1. link program并且檢查program的狀態(tài),如果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中振定,實(shí)際上attribute的index并不是真正從program中獲取到的必怜,而是直接通過(guò)默認(rèn)的順序進(jìn)行排序的。因?yàn)槲覀兛梢哉嬲ㄟ^(guò)使用glBindAttribLocationglEnableVertexAttribArray來(lái)控制每個(gè)Attribute的位置后频。

因此梳庆,如果需要寫(xiě)一個(gè)Filter的子類(lèi)暖途,需要先調(diào)用父類(lèi)中的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中驻售,有兩個(gè)attributes的位置是固定的:

  • position的index為0;
  • inputTextureCoordinate的index為1更米;
    如果有多個(gè)input的話欺栗,則分別為inputTextureCoordinatei = i-1;

Uniform的管理

在OpenGL ES中,uniform的位置并不是固定的征峦,必須在Program link完成之后才能夠獲得迟几,因此,獲取uniform的位置必須通過(guò)以下命令:

glGetUniformLocation(program, [uniformName UTF8String]);

使用OpenGL ES Program

由于在渲染過(guò)程中眶痰,很可能添加了多個(gè)Program瘤旨,因此,很可能出現(xiàn)的情況就是需要在不同的Program之間進(jìn)行切換竖伯。切換的命令很簡(jiǎn)單:

- (void)use
{
    glUseProgram(program);
}

一旦調(diào)用了這個(gè)方法存哲,就會(huì)將當(dāng)前的program設(shè)置為正在使用中的,然后進(jìn)行渲染七婴。

默認(rèn)的Shader

由于GPUImage默認(rèn)是進(jìn)行圖片處理祟偷,其實(shí)它的VertexShader就相對(duì)簡(jiǎn)單很多,因此打厘,GPUImage提供了一個(gè)默認(rèn)的VertexShader:

  attribute vec4 position;
 attribute vec4 inputTextureCoordinate;
 
 varying vec2 textureCoordinate;
 
 void main()
 {
     gl_Position = position;
     textureCoordinate = inputTextureCoordinate.xy;
 }

同時(shí)修肠,也提供了一個(gè)不進(jìn)行任何像素操作的PassThrough FragmentShader:

 varying highp vec2 textureCoordinate;
 
 uniform sampler2D inputImageTexture;
 
 void main()
 {
     gl_FragColor = texture2D(inputImageTexture, textureCoordinate);
 }

GLProgram主要是提供了對(duì)OpenGL ES program創(chuàng)建以及使用的面向?qū)ο蠡庋b。真正進(jìn)行渲染的Shader都是由不同的Filter自己提供的户盯。在了解了GLProgram之后嵌施,我們就可以開(kāi)始正式進(jìn)入主題GPUImageFilter了。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末莽鸭,一起剝皮案震驚了整個(gè)濱河市吗伤,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌硫眨,老刑警劉巖足淆,帶你破解...
    沈念sama閱讀 222,729評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異礁阁,居然都是意外死亡巧号,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,226評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)姥闭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)丹鸿,“玉大人,你說(shuō)我怎么就攤上這事棚品】炕叮” “怎么了弥姻?”我有些...
    開(kāi)封第一講書(shū)人閱讀 169,461評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)掺涛。 經(jīng)常有香客問(wèn)我庭敦,道長(zhǎng),這世上最難降的妖魔是什么薪缆? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 60,135評(píng)論 1 300
  • 正文 為了忘掉前任秧廉,我火速辦了婚禮,結(jié)果婚禮上拣帽,老公的妹妹穿的比我還像新娘疼电。我一直安慰自己,他們只是感情好减拭,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,130評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布蔽豺。 她就那樣靜靜地躺著,像睡著了一般拧粪。 火紅的嫁衣襯著肌膚如雪修陡。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,736評(píng)論 1 312
  • 那天可霎,我揣著相機(jī)與錄音魄鸦,去河邊找鬼。 笑死癣朗,一個(gè)胖子當(dāng)著我的面吹牛拾因,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播旷余,決...
    沈念sama閱讀 41,179評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼绢记,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了正卧?” 一聲冷哼從身側(cè)響起蠢熄,我...
    開(kāi)封第一講書(shū)人閱讀 40,124評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎穗酥,沒(méi)想到半個(gè)月后护赊,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體惠遏,經(jīng)...
    沈念sama閱讀 46,657評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡砾跃,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,723評(píng)論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了节吮。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片抽高。...
    茶點(diǎn)故事閱讀 40,872評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖透绩,靈堂內(nèi)的尸體忽然破棺而出翘骂,到底是詐尸還是另有隱情壁熄,我是刑警寧澤,帶...
    沈念sama閱讀 36,533評(píng)論 5 351
  • 正文 年R本政府宣布碳竟,位于F島的核電站草丧,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏莹桅。R本人自食惡果不足惜昌执,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,213評(píng)論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望诈泼。 院中可真熱鬧懂拾,春花似錦、人聲如沸铐达。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,700評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)瓮孙。三九已至唐断,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間杭抠,已是汗流浹背栗涂。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,819評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留祈争,地道東北人斤程。 一個(gè)月前我還...
    沈念sama閱讀 49,304評(píng)論 3 379
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像菩混,于是被迫代替她去往敵國(guó)和親忿墅。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,876評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容